这里列一些我在面试时最常问到的,都是老生常谈的问题:

什么是闭包?
什么是面向对象?
JS如何实现类,对象之间的继承?
什么是冒泡和捕获以及事件委托?
JS有哪些数据类型?
Null和Undefined的区别?
判断时if是false的值?
isNaN()的作用?
JS对象中的Array对象和String对象的各种方法 
this关键字在不同环境下的指向   
JS的作用域
setTimeout和setInterval
了解CSS3或HTML5吗,都用过哪些

精通原生 JS 闭包,异步编程,原型继承,类型数组,事件代理,原生 ajax。

都用过哪些计算机基础:
主要是计算机网络、算法、数据结构方面的知识,这一块儿是我最薄弱的,而运气好的是面试官问的也较少,更多的是询问排序查找相关的一些,经验不足就不多说。

技能延伸:前面两者技术的基础上对技能的延伸能增加获得 offer 的机会,比如了解模板引擎、熟悉比较火的前端框架或前端技术(JQuery、Bootstrap、Zepto、Node,Angular.JS),并且研究过源码、对 svn 或 git 等版本控制软件的操作、 长期更新博客、 在 github 上有所贡献等,这些都是绝对的加分点,这些并不是说都要做,如果能选中其中一两项有所成果应该是能加分的。  
因为面试已经隔得太久,最近也比较懒,能想起的大概就这么多,有想到的再上来更新吧。

请问JS中的基本数据类型有几种?

七种

1
2
3
4
5
6
7
Boolean
Null
Undefined
Number
String
Symbol (es6新增)
BigInt (es10新增)

[] == ![] 结果是什么?

类型转换都是先 valueOftoString;

右边

  1. 由于 ! 优先级比 == 高,先执行 !
  2. ![] 得到 false
  3. 进行 相等性判断
  4. false 转化为数字 0

左边

  1. 执行 [].valueOf() 原始值 还是 []
  2. 执行 [].toString() 得到 ‘’ #[].toString() == 0 //true
  3. '' 转化为数字 0

所以:0 == 0 ,答案是 true

验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
let arr1 = [];
let arr2 = [];

console.log(arr1 == !arr2) // -> true

arr1.toString = () => {
console.log(111)
return 1
}

console.log(arr1 == !arr2)
// -> 111
// -> false

[‘1’, ‘2’, ‘3’].map(parseInt)

map语法: Array.prototype.map(callback[, thisArg])

callback包括三个值:value(遍历值), index(下标), arr(数组本身)

1
2
['1', '2', '3'].map(parseInt)
// [1, NaN, NaN]

其实在使用map时,map的callback的第二个参数index引索值就成为parseeInt的radix值。[‘1’, ‘2’, ‘3’].map(parseInt)在遍历的过程。其实是经历了下面的过程。

1
2
3
parseInt('1', 0);	//数组下标的值就是parseInt的第二参数
parseInt('2', 1);
parseInt('3', 2);
  • parseInt(‘1’, 0):radix的值为0,判断字符串发现介于1~9,用10进制转换,结果为1.

  • parseInt(‘2’, 1):radix的值为1,如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。

  • parseInt(‘3’, 2): radix的值为2,这就意味着字符串将被解析成字节数,也就是仅仅包含数值0和1。parseInt的规范指出,它仅尝试分析第一个字符的左侧。这个字符串的第一个字符是“3”,它并不是基础基数2的一个有效数字。所以这个子字符串将被解析为空。如果子字符串被解析成空了,函数将返回为NaN。

1
2
3
4
5
['1', '1', '1','10','10'].map(parseInt)
// [1, NaN, 1, 3, 4]

['1', '1', '1','13','13'].map(parseInt)
// [1, NaN, 1, 1, 7]

‘1 2 4 6 5 3’.replace(/\d/g, parseInt) // “1 NaN NaN NaN 5 3”

如何让 (a == 1 && a ]== 2)条件成立?

依然是类型转换逻辑:基础类型通过 valueOf 进行隐式转换

更改 valueOf 方法就可以实现

1
2
3
4
5
6
7
8
9
   let a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
console.log(a == 1 && a == 2); //true #每次执行==就运行一次valueOf方法
console.log(a == 3 && a == 4); //true

回调函数

什么是回调地狱(函数作为参数层层嵌套)

什么是回调函数(一个函数作为参数需要依赖另一个函数执行调用)

简单地说: 一个回调函数,就是在另外一个函数(通常是异步的)执行完之后再执行的函数,因而被命名为——回调。

更进一步地说: 在 JavaScript 中,函数是对象。正因如此,一个函数可以被其他函数作为参数(传入),也能被其他函数作为返回值返回。这种函数(译者注:起码要满足如下条件之一:1.接受一个或多个函数作为参数,2.将一个函数作为返回值返回)被称为高阶函数。任何函数,只要它作为参数传入且随后被调用,都可称之为回调函数。

如何解决回调地狱?

保持你的代码简短(给函数取有意义的名字,见名知意,而非匿名函数,写成一大坨)

模块化(函数封装,打包,每个功能独立,可以单独的定义一个 js 文件 Vue,react 中通过 import 导入就是一种体现)

处理每一个错误

创建模块时的一些经验法则

承诺/生成器/ES6 等

1
2
3
4
5
6
7
8
T.get("search/tweets", params, function(err, data, response) {
//最后一个则是一个匿名的回调函数。
if (!err) {
// This is where the magic will happen
} else {
console.log(err);
}
});

在这里,回调函数是十分重要的,因为我们需要等待服务器返回数据后,才能继续执行余下代码。我们并不知道我们请求 API 后,服务器是否成功地返回数据。因此,当我们通过 GET 请求请求 search/tweets 后,会等待服务器返回信息。一旦推特(的服务器)返回相应数据,我们的回调函数将会执行。

强耦合和弱耦合

耦合分析通常分两种方法:强耦合(或称紧耦合)和弱耦合(或称松耦合)

软件设计中的“耦合”指,两个功能函数之间的依赖程度。

比如,你设计的一个程序,需要你编写 10 个功能函数来实现。如果这 10 个功能中,有 9 个功能都要调取第 10 个功能函数 X10,那么,当你在修改 X10 时,你就要考虑修改完成后,是否会对其它 9 个功能函数有影响,为了查看是否有不好影响,你就要对其它 9 个功能函数,一个一个进行测试。所以,为了避免产生这种后期修改的劳动量。就提倡【松耦合】,就是,功能函数之间,尽量依赖程度不要太高。否则,修改完一个底层函数后,会对多个上层函数,进行大量的测试。
【松耦合】的方法,一般是底层函数,功能尽量单一,尽量避免修改底层函数。功能相近的函数,可以设计 2 个以上,不要为了减少代码量,把一个函数的功能设计的太多。

说说你对闭包的理解

闭包是 Javascript 语言特有的”链式作用域”结构(chain scope)变量的作用域有三种:全局作用域和局部作用域以及块作用域(ES6)。,子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

闭包:JavaScript 高级程序设计里写闭包是有权访问另一个函数作用域中的变量的函数,使作用域得到了延长。我们有时候在函数外部需要得到函数内的局部变量。而闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的优点:

  • 是闭包封住了变量作用域,有效地防止了全局污染
  • 可以读取其他函数内部的变量,让这些变量的值始终保持在内存中,不会随着函数的结束而自动销毁。
  • 可以很巧妙地实现静态私有变量、私有函数方法等
  • 匿名自执行函数可以减少内存消耗

闭包的缺点: 由于闭包会使得函数中的变量都被保存在内存中,所以存在内存泄漏的风险

  • 被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为 null;

其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

解决方案:

  • 在浏览器端可以通过强制刷新解决,对用户体验影响不大
  • 在服务端,由于 node 的内存限制和累积效应,可能会造成进程退出甚至服务器沓机

使用场景 :函数内部变量只初始化一次

解决方法是显式对外暴露一个接口,专门用以清理变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/*1.清除失败,因为每次先执行mockData()后才会执行闭包,所以每次都会在局部作用域创建常量mem*/
function mockData() {
//私有常量
const mem = { name: "lucas", age: 22 };

return {
clear: () => {
//把有权访问私有变量的公有方法称为特权方法
for (let i in mem) {
delete mem[i];
}
}, // 显式暴露清理接口
get: page => {
if (page in mem) {
return mem[page];
}
mem[page] = Math.random();
}
};
}
console.log(mockData().get("name")); //lucas
mockData().clear(); //清理变量
console.log(mockData().get("name")); //lucas
/* 输出结果
这里执行多次
lucas
这里执行多次
这里执行多次
lucas
*/

/*2.成功清除但代码不复用*/
const mem = { name: "lucas", age: 22 }; //卸载外面
function mockData() {
console.log("这里执行多次");
return {
clear: () => {
for (let i in mem) {
delete mem[i];
}
}, // 显式暴露清理接口
get: page => {
if (page in mem) {
return mem[page];
}
mem[page] = "dwdwd";
}
};
}

console.log(mockData().get("name")); //lucas
mockData().clear(); //清理变量
console.log(mockData().get("name")); //undefined

/*
这里执行多次
lucas
这里执行多次
这里执行多次
undefined
*/

/*3.最好写法*/
function mockData() {
const mem = { name: "lucas", age: 22 };
console.log("执行一次");
return {
clear: () => {
for (let i in mem) {
delete mem[i];
}
}, // 显式暴露清理接口
get: page => {
if (page in mem) {
return mem[page];
}
mem[page] = "dwdwd";
}
};
}

var result = mockData(); //也可以mockData()()执行闭包

console.log(result.get("name")); //lucas
result.clear(); //清理变量
console.log(result.get("name")); //undefined

/*
执行一次
lucas
undefined
*/

例如”变量只初始化一次”这样的需求可以使用下面的例子

销毁闭包产生的变量,实现递增例子 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//通过匿名函数可以实现闭包,简称匿名闭包函数

var foo = (function(varr) {
var n = varr || 0; //私有变量
return {
add: function() {
//这样写称为闭包特权方法
return ++n;
},
clearVariable: function() {
n = null;
}
};
})(20); //由于匿名立即执行函数只会执行一次,所以这里实参数只能传一次(若需要传多次请参考例子2)
foo.add(); //21
foo.add(); //22
foo.add(); //23
foo.clearVariable(); //n变量被销毁
foo.add(); //1

销毁闭包产生的变量,实现递增例子 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*写法1*/
function create_counter(initial) {
var x = initial || 0; //变量只会初始化一次
return {
inc: () => {
return x++;
},
clear: () => {
x = null;
}
};
}

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
c2.clear(); //x变量被销毁
c2.inc(); // 1

/*写法2:这样写不方便销毁变量*/
function create_counter(initial) {
var x = initial || 0; //变量只会初始化一次
function add() {
return ++x;
}
return add; //也可以返回执行函数后的内容add()
}

var c2 = create_counter(10);
c2(); //11
c2(); //12
c2(); //13
c2() = null; //清除函数也清除了变量
c2(); //报错不存在函数
var c2 = create_counter(20);
c2(); //21
c2 = null; //释放对闭包的引用

//在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
//如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

销毁闭包产生的变量,实现递增例子 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Class() {
this.n = 0;
this.func = () => {
this.n++;
return this.n; //闭包产生的变量需手动清除
};

this.clear = () => {
return (this.n = null); //销毁函数内部的变量,避免内存泄漏
};
}
var obj = new Class();
obj.func(); //1
obj.func(); //2
obj.func(); //3
obj.clear(); //n变量被销毁
obj.func(); //1

后者的可扩展性更好. 当需要实现对一个变量的不同操作时, 后一种可以只需要再定义一个不同的函数(也就是简单线性扩展), 而前一种(闭包)则需要完全重写。

如果仅仅是做一个简单的计数器,大可不用这样麻烦。下面这简短的代码就能轻松实现。

1
2
3
4
5
var a = 0;
function myFunction() {
a++;
document.getElementById("demo").innerHTML = a;
}

匿名闭包函数

1
2
3
4
var a = 1;
(function test() {
alert(a);
})();

上面的 function 都可以称之为闭包(匿名闭包函数)。

闭包其实还有很多实用场景,比如,我们想在页面上添加一些可以调整字号的按钮

1
2
3
4
5
6
7
8
9
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + "px";
};
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

实现私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*例子1*/
var db = (function() {
// 创建一个隐藏的object, 这个object持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个函数, 这个函数提供一些访问data的数据的方法
return function(key, val) {
if (val === undefined) {
return data[key];
} // get
else {
return (data[key] = val);
} // set
};
// 我们可以调用这个匿名方法
// 返回这个内部函数,它是一个闭包
})();

db("x"); // 返回 undefined
db("x", 1); // 设置data['x']为1
db("x"); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员

/*例子2*/
var foo = (function() {
var secret = "11111111";
return {
//特权方法
getSecret: function() {
return secret;
},
setSecret: function(new_secret) {
secret = new_secret;
}
};
})();
alert(foo.secret); //undefined不可访问
alert(foo.getSecret()); //11111111
foo.setSecret("22222222"); //set
alert(foo.getSecret()); //22222222

实现私有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*例子1*/
var book = (function() {
var page = 100;
return function() {
this.auther = "dava";
this.price = 200;
this._page = function() {
alert(page); //想访问page属性,那就得依靠闭包了
};
};
})();

var a = new book();
a.auther; //"dava"
a.price; //200
a.page; //undefined,page变量不指向实例
a._page(); //100 //私有方法

//当然也可以使用上面递增例子1~3方法

/*例子2*/
function Demo() {
// 这是一个公有方法
function a() {
console.log("I'm public a function !");
}
//只需利用 javascript闭包的特性
//b 是一个私有方法,我们用下划线代表b是一个私有方法
this._b = function() {
alert("I'm private b function !");
};
//在生命这个类对象的时候调用一次
}
//这样,我们就实现了一个只会被调用一次的私有方法
var demo = new Demo();
demo._b();

使用对象实现私有属性和私有方法
在 createNew()方法中,只要不是定义在 cat 对象上的方法和属性,都是私有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Cat = {
createNew: function() {
var cat = {};

var sound = "喵喵喵";

cat.makeSound = function() {
alert(sound);
};

return cat;
}
};

//上例的内部变量sound,外部无法读取,只有通过cat的公有方法makeSound()来读取。

var cat1 = Cat.createNew();

alert(cat1.sound); // undefined

alert(cat1.makeSound()); //喵喵喵 //只能这样读取私有属性

闭包的另一个用处,是封装对象的私有属性和私有方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}

return {
name: name,
getAge: getAge,
setAge: setAge
};
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25

实际开发中闭包的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//闭包实际应用中主要用于封装变量,收敛权限
function isFirstLoad() {
var list = [];
return function(id) {
if(list.indexOf(id) >= 0){
return false;
} else {
list.push(id);
return true;
}
}
}

let firstLoad = isFirstLoad();
firstLoad(10); //true;
firstLoad(20); //true;
firstLoad(10); //false;

来一道有关闭包的面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function fun(n, o) {
console.log(o);
return {
fun: function(m) {
return fun(m, n);
}
};
}

var a = fun(0); // undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0

var b = fun(0)
.fun(1)
.fun(2)
.fun(3); // undefined \n 0 \n 1 \n 2

var c = fun(0).fun(1); //undefined \n 0
c.fun(2); // 1
c.fun(3); // 1

为什么 js 会有闭包?

当别人问你为什么会有闭包这东西的时候,其实是在问,闭包的形成机制。

当我们调用一个闭包函数时(比如上面的 getInnerDate 函数),因为函数执行时,其上下文有个 Scope 属性,该属性作为一个作用域链包含有该函数被定义时所有外层的变量对象的引用,所以定义了闭包的函数虽然销毁了,但是其变量对象依然被绑定在函数 inner 上,保留在内存中。

事实上,只要代码保持对 getInnerDate 函数的引用,函数自身的[[scope]]属性就绑定着闭包的活动对象。

但要留意的是,基于 js 的垃圾回收机制,outer 的变量对象里,只有仍被引用的变量会继续保存在内存中:

在 javascript 中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;

如果两个对象互相引用,而不再被第 3 者所引用,那么这两个互相引用的对象也会被回收。

this 关键字的理解

this 是 JavaScript 语言的一个关键字。

它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。

那么,this 的值是什么呢?

函数的不同使用场合,this 有不同的值。总的来说,this 就是函数运行时所在的环境对象。下面分四种情况,详细讨论 this 的用法。

情况一:纯粹的函数调用(指向全局对象)
情况二:作为对象方法的调用(指向这个上级对象)
情况三: 作为构造函数调用(指这个实例对象)
情况四: apply 调用(指向 apply()第一个参数,若 apply()参数为空时,默认调用全局对象),call、apply 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

还有 es6 箭头函数: 它本身没有 this,会沿着作用域向上寻找,直到 global / window

说说你对前端模块化的理解

什么是模块化

在 js 出现的时候,js 一般只是用来实现一些简单的交互,后来 js 开始得到重视,用来实现越来越复杂的功能,而为了维护的方便,我们也把不同功能的 js 抽取出来当做一个 js 文件,但是当项目变的复杂的时候,一个 html 页面可能需要加载好多个 js 文件,而这个时候就会出现各种命名冲突,如果 js 也可以像 java 一样,把不同功能的文件放在不同的 package 中,需要引用某个函数或功能的时候,import 下相关的包,这样可以很好的解决命名冲突等各种问题,但是 js 中没有模块的概念,又怎么实现模块化呢

模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块,但是模块开发需要遵循一定的规范,否则就都乱套了,因此,才有了后来大家熟悉的 AMD 规范,CMD 规范

前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理。

模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块,但是模块开发需要遵循一定的规范,否则就都乱套了,因此,才有了后来大家熟悉的 AMD 规范,CMD 规范。

ES6 考虑了模块化,使用 import 和 export,但是目前浏览器还不支持,这个标准也只是个雏形。

模块的定义:
所谓的模块化开发就是封装细节,提供使用接口,彼此之间互不影响,每个模块都是实现某一特定的功能。模块化开发的基础就是函数

为什么要使用模块化:

  1. 可维护性

  2. 命名空间

  3. 可复用性

  4. 每个文件是一个模块,有自己的作用域

  5. 再模块内部 module 变量代表模块本身

  6. module.exports 属性代表模块对外接口

模块化规范

CommonJS
AMD
UMD
CMD
Module(es6)

web 安全攻击手段有哪些?以及如何防范?

常见的有 xss, csrf, sql 注入

xss(cross site scripting) 跨站脚本攻击

定义: 指攻击者在网页嵌入脚本,用户浏览网页触发恶意脚本执行
XSS 攻击分为 3 类:存储型(持久型)、反射型(非持久型)、基于 DOM

如何防范:

设置HttpOnly以避免cookie劫持的危险
过滤,对诸如`<script>、<img>、<a>`等标签进行过滤
编码,像一些常见的符号,如<>在输入的时候要对其进行转换编码
限制,对于一些可以预期的输入可以通过限制长度强制截断来进行防御

csrf(cross site request forgery) 跨站请求伪造

定义: 是一种劫持受信任用户向服务器发送非预期请求的攻击方式

如何防范:

验证 HTTP Referer 字段
请求地址中添加 token 并验证
HTTP 头中自定义属性并验证

sql 注入(SQL injection)

定义: 在未授权情况下,非法访问数据库信息

如何防范:

杜绝用户提交的参数入库并且执行
在代码层,不准出现sql语句
在web输入参数处,对所有的参数做sql转义
上线测试,需要使用sql自动注入工具进行所有的页面sql注入测试

let 和 var 区别

1.函数作用域 vs 块级作用域 2.变量提升 VS 暂时性死区 3.不允许重复声明变量 4.全局变量 vs 全局对象的属性 (不能使用 this.和 window 取值)

Node 的优缺点?

1.node是基于事件驱动和无堵塞模式的,因此适合在并发请求
2.node构建代理服务器相比较其他技术语言的要好的多,
3.node和前端的代码都是用javascript写的,这是非常好的
4.node是一个全新的开源项目,相比较其他语言有些不稳定
5.第三方库没有其他语言的多

什么是事件驱动?

一个触发动作引起的操作(例如点击按钮后弹出一个对话框)

总结基本数据类型和引用数据类型区别

1、声明变量时内存分配不同

*原始类型:在栈中,因为占据空间是固定的,可以将他们存在较小的内存中-栈中,这样便于迅速查询变量的值

*引用类型:存在堆中,栈中存储的变量,只是用来查找堆中的引用地址。

这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响

1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的 value 而已。

2)引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,

函数节流 throttle

函数节流与函数防抖是我们解决频繁触发 DOM 事件的两种常用解决方案

原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率

举个栗子:还是以 scroll 滚动事件来说吧,滚动事件是及其消耗浏览器性能的,不停触发。以我在项目中碰到的问题,移动端通过 scroll 实现分页,不断滚动,我们不希望不断发送请求,只有当达到某个条件,比如,距离手机窗口底部 150px 才发送一个请求,接下来就是展示新页面的请求,不停滚动,如此反复;这个时候就得用到函数节流。

函数防抖 debounce

原理:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次!!!!!!!!!!)。

每次调用 debounce 函数都会将前一次的 timer 清空,确保只执行一次

举个栗子:滚动 scroll 事件,不停滑动滚轮会连续触发多次滚动事件,从而调用绑定的回调函数,我们希望当我们停止滚动的时,才触发一次回调,这时可以使用函数防抖。

alphabet 滑动逻辑

上下滑动时,取字母位置逻辑:

获取 A 字母距离顶部高度

滑动时,取当前位置距离顶部高度

计算差值,得到当前手指位置与 A 字母顶部差值

除以每个字母高度,得出当前字母,触发 change 事件给外部

什么是基于事件驱动的回调?

为了某个事件注册了回调函数,但是这个回调函数不是马上执行,只有当事件发生的时候,才会调用回调函数,这种函数执行的方式叫做事件驱动~这种注册回调就是基于事件驱动的回调,如果这些回调和异步 I/O(数据写入、读取)操作有关,可以看作是基于回调的异步 I/O,
只不过这种回调在 nodejs 中是有事件来驱动的

js 类型

基本数据类型有(又称简单类型):String,Number,Boolean,Null,Undefined,Symbol
引用类型(又称复杂类型):function,Array,Object

例举 3 种强制类型转换和 2 种隐式类型转换

parseInt,parseFloat,Number

== , ===

数组常用增删操作

pop ==>删除最后一个数组
push ==>在数组最后增加一个数组
unshift ==>在数组第一个增加一个数组
shift ==> 删除第一个数组
splice(index,endDelete,replaceString)

js 模式有哪些?

标准模式又称严格模式,是以排版和JS运作模式都是以浏览器支持的最高标准来运行
兼容模式又称宽松模式,页面以宽松的向后兼容的方式显示,防止老版本浏览器不能运行
1.cookie 每次都在服务器和客户端来回传递,而后俩者不会
2.后俩者存储空间更大
3.后俩者支持的api接口更多更丰富
4.后俩者都有各自独立的存储空间
5.cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的,而本地存储可以。
6.作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。

什么是回调?

回调是异步编程时的基础,将后续逻辑封装成起始函数的参数,逐层嵌套。
回调函数是闭包的简单使用,也就是说它能访问到其外层定义的变量。

0.1+0.2 等于?

ES6 在 Number 对象上面,新增一个极小的常量 Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。

由于 JavaScript 采用 IEEE 754 标准,数值存储为 64 位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第 54 位及后面的位就会被丢弃。

对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的 1.00..001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方。

1
2
3
4
5
6
7
8
9
10
11
12
0.1 + 0.2 === 0.3; // false  是IEEE754标准的问题
Number.isInteger(3.0000000000000002); // true

function withinErrorMargin(left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}

0.1 + 0.2 === 0.3; // false
withinErrorMargin(0.1 + 0.2, 0.3); // true

1.1 + 1.3 === 2.4; // false
withinErrorMargin(1.1 + 1.3, 2.4); // true

解决了上面的误差浮点数检查函数

0.1 加上 0.2!=0.3 是因为将 0.1 转换成为二进制加上 0.2 的二进制会是 53 位但是二进制的最大位数是 52 取近似值。导致的

别忘了:JS的精确度区间 约为正负 2^53,超出限制会截断。

创建实例对象的四种方法

引自<JavaScript 高级程序设计(第三版)>中译本 第6.2.3节, 原型模式.

也就是JavaScript的prototype是仅函数拥有, 而对象也拥有prototype是源于其constructor属性所拥有的prototype.

1.字面量

1
2
3
4
5
let obj={'name':'root'};

obj.__proto__.mod=function(){console.log('mod')}; //不等价于obj.mod=function(){console.log('mod')}
obj.mod() //报错调用
obj.__proto__.mod() //正确调用

2.Object构造函数创建

1
2
3
4
5
let obj1=new Object()
obj1.name='root'

obj1.__proto__.mod=function(){console.log('mod')} //不等价于obj.mod=function(){console.log('mod')}
obj1.__proto__.mod(); //mod

3.使用工厂模式创建对象

1
2
3
4
5
6
7
8
9
function createPerson(name){
var o = new Object();
o.name = name;
return o;
}
var person1 = createPerson('root');

person1.__proto__.mod=function(){console.log('mod')} //不等价于obj.mod=function(){console.log('mod')}
person1.__proto__.mod(); //mod

4.使用构造函数创建对象

1
2
3
4
5
6
7
function Person(name){
this.name = name;
}
var person1 = new Person('root');

Person.prototype.mod=function(){console.log('mod')}
person1.mod(); //mod

除了方法4有prototype属性,其他三种均只有__proto__属性,他们都是可以添加方法以及属性的

new 运算符

使用new关键字实例化的过程:

例如:let prince = new Prince();

创建一个空的简单JavaScript对象(即{});
链接该对象(即设置该对象的构造函数)到另一个对象 ;
将步骤1新创建的对象作为this的上下文 ;
如果该函数没有返回对象,则返回this。

1.第一步,创建一个空对象。如var prince={}
2.第二步,将构造函数Prince()中的this指向新创建的对象prince。(任何普通函数都具有prototype属性)(只有对象才会有this)
3.第三步,将prince的_proto_属性指向Prince函数的prototype,创建对象和原型间关系 (var prince={}实例对象是__proto__属性)
4.第四步,执行构造函数Prince()内的代码。  

我们可以把他们假想为:
let prince={
      Prince:function(){  //this指向新创建的对象prince
                        //这里面写的所有prototype添加的方法都在里面
                        //通过Prince.prototype.mod=function(){}创建的方法,prinice对象也能访问
      }
}

为了验证我上面的想法,我使用实例化后的对象增加方法如:prince.add=function(){}
在控制台中验证:
let prince={
      Prince:function(){  //this指向新创建的对象prince
                        //这里面写的所有prototype添加的方法都在里面
                        //通过Prince.prototype.mod=function(){}创建的方法,prinice对象也能访问
      },
    add:function(){}
};

//上面Prince构造函数显示为__proto__属性Object对象(其实还要在下一层,这里不多写了,自行脑补下面的代码)
这就是为什么prince.__proto__ === Prince.prototype  //true
这也是为什么prince.__proto__.add === prince.prototype.add  //true
再看下我下面写的例子:
let obj1 = {};  //实例化,得到__proto__属性
function test1(name){  this.name = name; console.log('Im is test1') }
test1.prototype.mod1 = function(){ console.log('Im is mod1') }
obj1.__proto__ = test1.prototype; //仔细看上面的第三步骤:将prince的_proto_属性指向Prince函数的prototype

obj1.mod1();  //Im is mod1 #到这里你可能会想,我使用let b = new test1('root'); //还会输出test1函数里面的值Im is test1 以及{name: "root"}

到这里再回看上面new运算符的第四步骤,还执行了构造函数Prince()内的代码。 

任何普通函数都具有prototype属性,func.prototype.med=function(){} //但直接通过func.med()调用会报错,因为函数没有通过new实例化,但可以通过func.prototype查看新增的方法

我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,

    1.普通函数 :
    (1) 调用 fun()
    (2)调用函数,不会创建新对象
    (3)函数内部this指向调用它的对象,没有指明调用对象,指向window
    (4)普通函数大多以小写字母开头
    (5)没有返回值

    2.构造函数 :
    (1) 调用 f = new fun(); 使用new操作符
    (2)调用函数需要创建实例对象
    (3)函数内部this指向创建的对象 f
    (4)构造函数都应该以大写字母开头
    (5)有默认的返回值,即新创建的实例对象

详细链接https://blog.csdn.net/u013789656/article/details/80942385

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*非实例化函数的prototype属性*/
function a1(){console.log('Im is a1')}
a1.prototype.mod1=function(){console.log('Im is mod1')} //测试a1.mod1=function(){}也可以
a1.mod1(); //error #若采用let obj1=Object.create(a1.prototype)则可以通过obj1.mod()访问
a1.prototype.mod1();
/*
Im is mod1
*/


console.log(a1.prototype.constructor === a1) //true


//只有new运算符后的才是真正的prototype原型,普通函数的不是

/*实例化函数的prototype属性*/
function a2(){console.log('Im is a2')}
a2.prototype.mod2=function(){console.log('mod2')}
let b=new a2(); //a函数在b的原型链里
b.mod2(); //若不用new实例化,则不存在prototype属性
/*
Im is a2
Im is mod2
*/

console.log(b.__proto__ === a2.prototype) //true
1
2
3
4
5
let obj1 = new Object();  //也可以let obj1 = {};实例化对象
obj1.__proto__.gett=function(){console.log('root')} //添加方法
obj1.gett() //root #由于__proto__不是标准属性,建议少用

obj1.prototype.gett=function(){console.log('root')}; //error #普通对象不具有prototype对象,只有new 构造函数才有

prototype 属性使您有能力向对象添加属性和方法。

当代码 new Foo(…) 执行时,会发生以下事情:

1. 一个继承自 Foo.prototype 的新对象被创建。

2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。

3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

实现原生new运算符

  1. 创了一个新对象;
  2. this指向构造函数;
  3. 构造函数有返回,会替换new出来的对象,如果没有就是new出来的对象
  4. 手动封装一个new运算符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var new2 = function (func) {
    //步骤1:创建新的对象
    var o = Object.create(func.prototype);//创建新对象

    //步骤2:将构造函数func()中的this指向新创建的对象o。
    var k = func.call(o);//改变this指向,把结果付给k, #创建对象和原型间关系
    if (k && k instanceof Object) {//判断k的类型是不是对象
    return k;//是,返回k
    } else {
    return o;//不是返回则返回构造函数的执行结果 #基本上执行此行
    }
    }

    new2(func); //func是个函数

    语法:Object.create(proto, [propertiesObject])//方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。

异步加载 JS 的方法有哪些

1.defer:如果你的脚本不会改变文档的内容,可以在script标签中加入defer来加快处理文档的速度,因为浏览器知道它将能够安全的读取剩余的
的部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止
2.async:html5属性仅属于外部脚本,如果在IE中同时存在defer和async,那么defer优先级比较高,脚本将在页面完成时执行创建script标签,插入到DOM中

一般来说,如果脚本之间没有依赖关系,就使用 async 属性,如果脚本之间有依赖关系,就使用 defer 属性。

如果同时使用 async 和 defer 属性,后者不起作用

ES6 异步编程有哪些

Promise , Generator ,Async

解决 ajax 缓存问题有哪些

1.在ajax发送请求前加上anyAjaxObj.setRequestHeader("if-modified-since",0)
2.在ajax发送请求前加上anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
3.在URL上加上随机数:“fresh=”+Math.random()
4.在URL上加上时间戳:“nowtime=”+new Date().getTime()

鼠标移动事件

不论鼠标指针离开被选元素还是任何子元素,都会触发 mouseout 事件
只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件
不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件
只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件

JS 中如何将页面重定向到另一个页面?

跳转页面
使用 location.href:window.location.href =“https://www.onlineinterviewquestions.com/”
使用 location.replace: window.location.replace(“ https://www.onlineinterviewquestions.com/;");
使用 location.assign(“http://www.baidu.com");

获取页面地址
document.URL
windows.location.href

  1. 从输出结果上,document.URL 和 windows.location.href 没有区别。
  2. 非要说区别的话,你只可以读取 document.URL 的值,不能修改它。windows.location.href 的值你即可以读取也可以修改。

刷新本页
window.location.Reload()和 window.location.href=window.location.href; //都是刷新当前页面。
window.location.href 和 self.location.reload(); //也是刷新本页的意思;

遍历循环有哪些

forEach   //用于遍历数组,不改变原数组,但无返回值以及break。总是会将所有成员遍历完。
map       //用于遍历数组,不改变原数组,返回一个新数组,每个数组元素被调用一次func
filter    //用于遍历数组,不改变原数组,返回一个新数组,返回每次判断为true的下标对应的值
for in    //用于遍历对象,不改变原数组,默认返回的是key值,,
for循环   //用于遍历数组以及对象
while     //用于遍历数组以及对象
do while  //用于遍历数组以及对象,至少会执行一次操作

for of    //用于遍历数组以及iterator对象,返回iterator对象,ES6提供
some      //用于遍历数组,不会改变原始数组,一直运行直到回调函数返回 true,返回true时相当于在for循环里break,会提前终止循环
every     //用于遍历数组,不会改变原始数组,一直运行直到回调函数返回 false,返回false时相当于在for循环里break,会提前终止循环

reduce     //用于遍历数组,不会改变原始数组,返回计算结果,属于高阶函数
reduceRight //和 reduce() 功能是一样的,不同的是 reduceRight() 从数组的末尾向前将数组中的数组项做累加

其他:(find,findIndex第一个true就终止并返回数组值和下标),some和every返回值都是布尔值。

map的return语句做判断只返回布尔值。

filter的returen语句做判断为true时则返回的是数组的值,filter必须有return语句,即便return "hello";  最终也是返回item的值,而不是hello。

注意: for of循环可以使用的范围包括数组、SetMap结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。有些数据结构是在现有数据结构的基础上,计算生成的。

TIP: 若返回大量数据,返回 iterator 对象比返回数组性能上更优。

数组 ages.filter(checkAdult) 和 ages.find(checkAdult); 前者是过滤 checkAdult 函数的全部数据,一般情况下 return 中都是判断比较运算符(>=、==、<=、!=),后者只过滤第一条成功的,注:均不会改变原始数组,findIndex 找出第一个满足条件返回 true 的元素下标

数组去重的简洁写法

1
2
3
4
5
6
7
//ES6
[...new Set([4, 3, 2, 4, 3, 6])][
//ES5
(4, 3, 2, 4, 3, 6)
].filter(function(value, key, arr) {
return key === arr.indexOf(value);
});

理解 JavaScript 面向对象编程

面向对象的三大特点:继承、封装、多态

1、JS中通过prototype实现原型继承

2、JS对象可以通过对象冒充,实现多重继承

3、Object类是所有Js类的基类

4、通过function对对象进行封装

5、通过使用arguments实现参数重载

6、ES6语法糖可以直接定义类class,继承对象extends

写一个 function,清除字符串前后的空格。(兼容所有浏览器)

1
2
3
4
5
6
7
8
9
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/, "");
};
}

var str = "\t\n test string ".trim();

alert(str == "test string"); //true

Javascript 中 callee 和 caller 的作用?

caller 是返回一个对函数的引用,该函数调用了当前函数;

callee 是返回正在被执行的 function 函数,也就是所指定的 function 对象的正文。

在 Javascript 中什么是伪数组?如何将伪数组转化为标准数组?

答案:

伪数组(类数组):无法直接调用数组方法或期望 length 属性有什么特殊的行为,但仍可以对真正数组遍历方法来遍历它们。典型的是函数的 argument 参数,还有像调用 getElementsByTagName,document.childNodes 之类的,它们都返回 NodeList 对象都属于伪数组。可以使用 Array.prototype.slice.call(fakeArray)将数组转化为真正的 Array 对象。

怎样理解阻塞非阻塞与同步异步的区别?

同步和异步,阻塞和非阻塞,是两组不同的概念,两个之间没有必然的联系。

  • 阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程是否需要等待
  • 同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会堵塞;异步只需要 I/O 操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写,如 setTimeout。

同步与异步

同步和异步关注的是消息通信机制。

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。

而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如 Node.js

阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用:是指调用结果返回之前,当前线程会被挂起。一直处于等待消息通知,不能够执行其他业务,调用线程只有在得到结果之后才会返回。如 alert,confirm,prompt,JavaScript 在处理回调时也存在阻塞现象

非阻塞调用:指在不能立刻得到结果之前,该调用不会阻塞当前线程,而会立刻返回。

阻塞非阻塞: 请求不能立即得到应答,需要等待,那就是阻塞;否则可以理解为非阻塞。

undefined 与 null 的区别

null 和 undefined 基本是同义的,只有一些细微的差别。

1
2
undefined == null; // true
undefined === null; // false,null是对象

null表示”没有对象”,即该处不应该有值。典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。

(2) 作为对象原型链的终点。

1
2
Object.getPrototypeOf(Object.prototype);
// null

undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:

(1)变量被声明了,但没有赋值时,就等于 undefined。

(2) 调用函数时,应该提供的参数没有提供,该参数等于 undefined。

(3)对象没有赋值的属性,该属性的值为 undefined。

(4)函数没有返回值时,默认返回 undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
var i;
i; // undefined

function f(x) {
console.log(x);
}
f(); // undefined

var o = new Object();
o.p; // undefined

var x = f();
x; // undefined

个人觉得还是根据语义来区分吧,null 更多的表示引用语义而 undefined 更多的表示值语义,虽然在数值上接近。