null 和 undefined
console.log( typeof unll ) // Object
console.log( typeof undefined ) // undefined
数据类型不同
console.log( null == undefined ) // true
console.log( null === undefined ) // false
因为数据类型不同,所以 null 与 undefined 相等而不全等
console.log( Number( null ) ) // 0
console.log( Number( undefined ) ) // NaN
转换为数字不同
var a
console.log( a ) // undefined
var b = null
console.log( b ) // null
null 代表空,表示一个空指针
undefined 是定义了没有赋值
throw
throw new Error("这是一个自定义错误") // 抛出一个错误
try...catch结构
try {
throw new Error('出错了!');
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
catch 代码块会捕获 try 代码块抛出的错误,catch 接收的参数就是 try 抛出的值
catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。
finally代码块
try...catch 结构允许在最后添加一个 finally 代码块,表示不管是否出现错误,都必需在最后运行的语句。
function cleansUp() {
try {
throw new Error('出错了……');
console.log('此行不会执行');
} finally {
console.log('完成清理工作');
}
}
cleansUp()
// 完成清理工作
// Uncaught Error: 出错了……
// at cleansUp (<anonymous>:3:11)
// at <anonymous>:10:1
变量提升
JS 会自动将变量声明“提升”到代码块的头部
if (!x) {
var x = {};
}
// 等同于
var x;
if (!x) {
x = {};
}
面向对象 OOP
面向对象实际上就是一种编程思维
面向对象三大特征
封装、继承、多态
let person = new Person()
当执行到 new 关键字时,会立即在 Person 构造函数内创建一个新对象,这个新对象就是 new 出来的实例,也就是 person ,构造函数中的 this 指向就是这个新创建的对象
构造函数执行流程
1.立即创建一个新的对象
2.将新建的对象设置为 this ,在构造函数中可以使用 this 来引用新建的对象
3.逐行执行构造函数中的代码
4.将新建的对象作为返回值返回
原型 prototype
我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype
这个属性对应着的一个对象就是所谓的原型对象
当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性
该属性指向构造函数的原型对象,可以通过 __proto__ 来访问该属性
obj.hasOwnProperty("属性名")
检查对象自身是否含有该属性,有返回true
原型对象也是对象,所以它也有原型
当我们使用一个对象的属性或方法时,会现在该对象自身寻找是否有该属性或方法,若有,则直接使用,若没有,则去该对象的原型中找,有则使用,没有则去该对象原型的原型中去找,逐层往上找,找到找到 Object 对象的原型,Object 的原型没有原型,如果在 Object 中都没有该属性或方法,则返回 undefined ;这就是所谓的原型链!!!
this
this 永远指向最后调用它的那个对象
this 不是在编译时绑定,而是在运行时绑定,所以它指向谁取决于谁调用它
this 的绑定方式:具名函数绑定 / 箭头函数绑定
具名函数绑定:
1.默认绑定:函数中的 this 默认指向全局,即 window
注意:使用严格模式时,this 不能绑定全局作用域,会抛出错误
2.隐式绑定:谁调用了函数,函数中的 this 就指向谁,即指向调用者
3.显示绑定:call、apply、bind
function fn1(){
console.log(this.num);
}
var obj = {
num:1
}
fn1.call(obj);
fn1.apply(obj);
fn1.bind(obj);
// 把 fn1 中的 this 与 obj 对象绑定
// 此时 fn1 中的 this 指向 obj
// 函数输出 this.num 即为 obj.num
}
// apply 和 call 的区别
// call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组
// bind 会返回一个新函数
// 执行
// call / apply 改变了函数的 this 上下文后马上执行该函数
// bind 则是返回改变了上下文后的函数,不执行该函数
// 返回值
// call / apply 返回 fun 的执行结果
// bind 返回 fun 的拷贝,并指定了 fun 的 this 指向,保存了 fun 的参数。
new命令
new 命令的作用,就是执行构造函数,返回一个实例对象
若调用构造函数而没有使用 new
var Vehicle = function (){
// use strict
this.price = 1000;
};
var v = Vehicle();
v // undefined
price // 1000
变量 v 变成了 undefined , price 属性变成了全局变量
为了避免这种情况,可以在构造函数内使用严格模式use strict,这样一旦忘了使用 new 命令,直接调用构造函数就会报错。
另一种方式则是在构造函数内部判断是否使用 new 命令,如果发现没有则直接返回一个一个实例对象
function Fubar(foo, bar) {
if (!(this instanceof Fubar)) {
return new Fubar(foo, bar);
}
this._foo = foo;
this._bar = bar;
}
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
// 上面代码中的构造函数,不管加不加`new`命令,都会得到同样的结果。
instanceof => 判断其左边对象是否为其右边类的实例,返回的是boolean类型的数
new 命令的原理
使用 new 命令时,它后面的函数依次执行下面的步骤。
1.创建一个空对象,作为将要返回的对象实例。
2.将这个空对象的原型,指向构造函数的 prototype 属性。
3.将这个空对象赋值给函数内部的 this 关键字。
4.开始执行构造函数内部的代码。
也就是说,构造函数内部, this 指的是一个新生成的空对象,所有针对 this 的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即 this 对象),将其“构造”为需要的样子。
如果构造函数内部有 return 语句,而且 return 后面跟着一个对象, new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象。
var Vehicle = function () {
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000
// false
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
如果对普通函数(内部没有 this 关键字的函数)使用 new 命令,则会返回一个空对象。
new 命令简化的内部流程,可以用下面的代码表示。
function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}
// 实例
var actor = _new(Person, '张三', 28);
作用域和闭包
作用域
作用域指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限
全局作用域
程序最外层的作用域,一直存在
函数作用域
函数作用域只有函数被定义时才会创建,包含在父级函数作用域/全局作用域内
闭包
能够访问其他函数内部变量的函数称为闭包
或者说是一个不可以被销毁的内存、对象
闭包会造成内存泄漏,因为它不会被销毁
Promise
Promise 是同步的
Promise 就是在一个同步的环境当中进行异步的操作
Promise 支持链式调用,可以解决回调地狱问题
Promise 的状态
pending(进行中)
fulfilled(已成功)
rejected(已失败)
Promise 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态。状态只会改变一次
Promise 构建出来的实例存在以下方法
then() 是实例状态发生改变时的回调,第一个参数是 resolved 状态的回调函数;第二个参数是 rejected 状态的回调函数。then 方法返回的是一个新的 Promise 实例,也就是 Promise 能链式书写的原因
catch() 方法是 .then(null, rejection) 或 .then(undefined, rejection) 的别名,用于指定发生错误时的回调。Peomise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获位置
finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
util.promisify
将回调函数形式的转成 Promise
Promise.all()
用法:const p = Promise.all( [p1, p2, p3] );
Promise.all 是将多个 Promise 实例包装成一个新的 Promise 实例
p 的状态由 p1, p2, p3 决定;只有 p1, p2, p3 的状态都为 fulfilled ,p 的状态才会变成 fulfilled ,只要 p1, p2, p3 中有一个的状态为 rejected,p 的状态就会变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 P 的回调函数
Promise.race()
用法:const p = Promise.race( [p1, p2, p3] );
Promise.race 是将多个 Promise 实例包装成一个新的 Promise 实例
只要 p1,p2,p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数
Promise.allSettled()
用法:const p = Promise.allSettled( [p1, p2, p3] );
该方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是 rejected ),返回的 Promise 对象才会发生状态变更
Promise.any()
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。
Promise.resolve()
Promise.reject()
Promise.tey()