摘要:本文主要总结了JS中容易混淆的知识点。
1 数据类型的判断
1.1 typeof
使用typeof
关键字,得到字符串类型的返回结果:
1 | // 基本类型 |
凡是通过对象的方式创建的变量,其使用typeof
得到的都是object
字符串。
另外,基本数据类型中的null
使用typeof
判断也会得到object
字符串。
1.2 instanceof
使用instanceof
关键字判断右侧显示调用的原型是否在左侧隐式调用的原型链上:
1 | // 基本类型,左侧非对象,没有原型链,直接返回false |
在创建对象时,会将显示调用赋值给隐式调用。在修改了显示调用的引用地址后,显示调用和隐式调用得到的原型对象将不再相同。
2 函数
2.1 创建方式
创建函数:
1 | function func() { |
创建匿名函数并赋值给变量:
1 | var func = function() { |
创建函数对象:
1 | var func = new Function("console.log(this);"); |
创建构造函数:
1 | function Func() { |
创建箭头函数:
1 | var func = () => {console.log(this);}; |
2.2 调用方式
创建函数并调用:
1 | // 创建函数 |
2.3 回调函数
回调函数在定义以后不需要主动调用,当某个事件触发时才会调用。
常见的回调函数:
- 定时函数,包括使用
setTimeout()
方法和setInterval()
方法传入的函数 - DOM事件触发的函数,比如
onclick()
方法绑定的函数 - AJAX请求回调函数
- 生命周期回调函数
3 this关键字
任何函数本质上都是通过某个对象来调用的,所有函数内部都有一个this变量,它的值是调用函数的当前对象。
明确this的值:
1 | function func() { |
4 执行上下文和执行上下文栈
4.1 执行上下文
代码按生命周期分为两种:
- 全局代码:在全局任意位置有效
- 局部代码:仅在某段代码内有效,比如代码块、函数等,局部代码内可以使用全局代码
执行上下文分为全局执行上下文和函数执行上下文两种。
全局执行上下文:
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理:
- 将全局变量的声明提升,赋值为undefined,添加为window的属性
- 将全局函数添加为window的方法
- 将this对象赋值为window
- 开始执行全局代码
函数执行上下文:
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
- 对局部数据进行预处理:
- 将传入的实参赋值到函数定义的实参,添加为执行上下文的属性
- 对函数的arguments内部属性赋值实参列表,添加为执行上下文的属性
- 将局部变量的声明提升,赋值为undefined,添加为执行上下文的属性
- 将函数内声明的函数添加为执行上下文的方法
- 将this对象赋值为调用函数的对象
- 开始执行函数体代码
在计算代码执行产生的上下文时,函数每次被调用都会生成一个函数执行上下文,在代码执行时会生成一个全局执行上下文。
4.2 执行上下文栈
在创建执行上下文之前,JavaScript引擎会创建一个栈来存储管理所有的执行上下文对象:
- 在确定全局执行上下文后,将其压入到栈中
- 在创建函数执行上下文后,将其压入到栈中
- 在当前函数执行完后,将栈顶的对象移除
- 在所有代码执行完后,栈中只剩下全局执行上下文
5 作用域和作用域链
5.1 作用域
作用域是某段代码所在的区域,使用目的是为了隔离变量,不同的作用域之间,同名变量不会有冲突,比如覆盖。
按照代码的声明周期可以将作用域分为三种:
- 全局作用域:全局代码区域
- 函数作用域:在函数中的代码区域
- 块作用域:在代码块中的区域,ES6之后才有这个概念
相对于上下文来说,作用域是静止的,在编写代码时就确定了,作用域的确定时间要早于上下文的创建时间。
在统计作用域的数量时,每个函数作为一个函数作用域,整个代码作为一个全局作用域。在ES6以后,每个代码块是一个块作用域。
5.2 作用域链
作用域链是在发生函数嵌套时产生的,使用目的是为了查找变量,特别是当发生了变量冲突时确定变量的值。
在发生函数嵌套时,外层函数作为上级,内层函数作为下级,作用域链的查找方向是从下往上的。
6 闭包
在发生函数嵌套时,并且内部函数引用了外部函数的变量或函数,就会在内部函数产生闭包:
1 | function outer() { |
闭包是在创建函数执行上下文时产生的对象,作为内部函数的属性,封装了在内部函数中使用的外部函数的变量或函数。
使用闭包的流程:
- 执行内部函数定义时产生闭包:
- 在执行内部函数时,会将闭包对象作为函数执行上下文的变量:
闭包的作用:
- 延长局部变量的生命周期,局部变量在外部函数执行完后, 仍然存活在内存中
- 允许在外部函数之外,通过内部函数修改外部函数的局部变量
闭包的生命周期:
- 在执行内部函数定义时产生,即在创建内部函数执行上下文时产生
- 在内部函数被视为垃圾对象时死亡,即不存在指向内部函数的变量引用
闭包会产生内存泄漏的可能,外部函数执行后,局部变量未能被马上释放:
1 | function outer() { |
解决办法:
- 尽量少用闭包
- 在使用闭包后及时手动将内部函数的引用清空,释放局部变量:
js 1
2
3
4
5
6
7
8
9
10
11function outer() {
var num = 0;
function inner() {
console.log(++num);
}
return inner;
}
var inner = outer();
inner();// 1
inner = null;
inner();// 报错
7 对象
7.1 创建
创建对象的几种方式:
- 直接创建:
js 1
2
3
4
5
6
7
8var obj = {
name: '张三',
setName: function(name) {
this.name = name;
}
};
obj.setName('李四');
console.log(obj);// {name: '李四', setName: ƒ} - 通过Object构造函数创建:
js 1
2
3
4
5
6
7var obj = new Object();
obj.name = '张三';
obj.setName = function(name) {
this.name = name;
}
obj.setName('李四');
console.log(obj);// {name: '李四', setName: ƒ} - 通过工厂模式创建:
js 1
2
3
4
5
6
7
8
9
10
11
12function createPerson(name) {
var person = {
name: name,
setName: function(name) {
this.name = name;
}
};
return person;
}
var person = createPerson('张三');
person.setName('李四');
console.log(person);// {name: '李四', setName: ƒ} - 通过自定义构造函数创建,推荐:
js 1
2
3
4
5
6
7
8
9function Person(name) {
this.name = name;
this.__proto__.setName = function(name) {
this.name = name;
};
}
var person = new Person('张三');
person.setName('李四');
console.log(person);// Person {name: '李四4'}
7.2 继承
当需要在一个类中使用另一个类的属性和方法时,将没有相关属性和方法的类称为子类,将具有相关属性和方法的类称为父类,子类的属性和方法继承自父类。
使用原型链继承:
1 | // 定义父类 |
使用父类的构造方法继承:
1 | // 定义父类 |
组合继承,同时使用原型链和构造函数:
1 | // 定义父类 |
8 线程机制
进程:
- 进程是程序的一次执行,是程序在计算机中运行的实体。
- 进程是系统进行资源分配的最小单位,每个进程都有各自独立的内存空间,各个进程互不干扰。
线程:
- 线程是进程的子任务,是进程实际的运作单位。
- 线程是CPU调度的最小单位,线程有独立的栈空间和局部变量,但没有独立的内存空间,一个进程内的多个线程共享进程的存储空间。
JavaScript采用的是单线程机制,作为浏览器脚本语言,主要用途是与用户互动以及操作DOM,使用单线程不需要考虑复杂的同步问题。
使用多线程的系统可以更好的利用CPU资源,提高CPU的运行效率,但也会因为需要频繁创建销毁线程导致占用了一部分性能。
线程池:
- 线程池可以复用线程,减少线程创建和销毁的次数,减少了性能浪费,提高程序的运行效率。
- 线程池可以控制线程的最大并发数,避免过高的并发量导致系统卡死。
9 事件机制
定时器的调用问题:
1 | var time = Date.now(); |
第一个定时函数不是在1秒后执行的,第二个定时函数不是在2秒后执行的,并且两个定时函数是同时执行的。
这是因为JavaScript是单线程的,定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行。
JavaScript引擎执行基本流程:
- 执行初始化代码,包括对变量赋值、执行普通方法、执行代码、设置定时器、绑定监听事件、发送请求
- 执行回调函数,包括定时器回调、事件回调、请求回调
事件管理模块:
- 定时模块,处理定时任务绑定的回调函数
- DOM模块,处理DOM元素绑定的回调函数
- AJAX模块,处理请求返回触发的回调函数
事件管理模型运转流程:
- 在执行初始化代码时,将事件回调函数交给对应的模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调列队中
- 只有当初始化代码执行完后,才会遍历读取回调队列中的回调函数执行
条