一、var 、let、const
1. 块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,let和const为 JavaScript 新增块级作用域。
2. 重复声明
let和const不允许在相同作用域内,重复声明同一个变量。var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的变量。
3. 变量提升
var命令会变量提升,即变量可以在声明之前使用,值为undefined。
let和const命令声明的变量一定要在声明后使用,否则报错ReferenceError。
function声明的函数也会进行提升,优先级高于变量提升,不会被变量声明所覆盖,但是会被变量赋值后覆盖。
console.log(a) // function a(params) {}
var a =1
function a(params) {}
console.log(a) //1
4. 暂时性死区
在代码块内,使用let和const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
5. 全局变量
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
var命令和function命令声明的全局变量,依旧是顶层对象的属性;
let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
var a = 1;
// Node环境可以写成global.a
// 或者通用写法,this.a
// 或者顶层对象写法 globalThis.a
window.a // 1
this.a // 1
globalThis.a // 1
let b = 1;
window.b // undefined
this.a // undefined
globalThis.a // undefined
ES2020 在语言标准的层面,引入globalThis作为顶层对象。任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
6. 指针指向
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
7. 初始值设置
对于const来说,只声明不赋值,就会报错.
const foo;
// SyntaxError: Missing initializer in const declaration
二、作用域
1. 全局作用域和函数作用域
(1)全局作用域
1.最外层函数和变量拥有全局作用域;
2.所有未定义直接赋值的变量自动声明为全局作用域;
3所有window对象的属性拥有全局作用域。
(2)函数作用域
1.子函数可以访问父函数中的变量;
2.父函数无法访问子函数中的变量。
2. 块级作用域
ES6中新增了let和const命令,可以声明块级作用域。
作用域链
作用域链的作用:保证执行环境有权访问所有变量和函数的有序访问,通过作用域链,可以访问到外层环境变量和函数。
三、闭包
闭包:指有权访问另一个函数作用域中变量的函数。
function f1() {
var n=999;
function f2() {
console.log(n);
return n
}
return f2;
}
var result=f1();
var num= result(); // 999
闭包作用:
1.保存:可以在外部调用闭包函数,从而在外部访问到函数内部的变量,但会造成内存泄漏; 2.保护:闭包内部变量不会被外部随意修改,从而实现封装,模块化。
四、this
this总是指向函数的直接调用者(而非间接调用者)。
1. 全局环境
全局环境中,this会返回全局对象。
2. 函数环境
函数环境中的this,如果函数不是作为方法调用,而是单纯作为函数调用,this会返回全局对象。
3. 方法环境
方法环境中的this,指向该方法所在的对象。
4. 构造函数环境
构造函数中的this,指向返回的实例对象。
5. 箭头函数环境
箭头函数没有自己的this,箭头函数的this,只取决于外层代码块的this。
五、call、apply、bind
1. call
调用一个对象的一个方法,以另一个对象替换当前对象。
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name);
}
};
var obj2 = {
name: 'obj2'
};
obj1.foo1.call(obj2); // obj2
2. apply
调用一个对象的一个方法,以另一个对象替换当前对象。
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name);
}
};
var obj2 = {
name: 'obj2'
};
obj1.foo1.apply(obj2); // obj2
3. bind
创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其参数则来自于 bind() 之后传入的参数。
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name);
}
};
var obj2 = {
name: 'obj2'
};
var foo1 = obj1.foo1.bind(obj2);
foo1(); // obj2
4. 区别
相同点:
- 都可以改变 this 的指向;
- 第一个参数都是this要指向的对象。
不同点:
- call 和 apply 会调用函数, bind 不会;
- call 和 apply 传递参数的方式不一样,call 传递参数比较直接,apply 需要以数组的形式进行传递;
- bind 除了改变 this 指向之外,它返回的也是一个函数。
5. call、apply、bind实现
Function.prototype.myCall = function (ctx, ...args) {
// console.log(this); // [Function: method] this就是method函数
// ctx.__fn = this // this赋值给__fn函数,
// const result =ctx.__fn(...args) // 剩余参数传递给method函数
// delete ctx.__fn // 删除方法
// return result
// 判断调用对象是否为函数
if (typeof this !== 'function') {
throw error(`TypeError:methods is not a function!`)
}
// 若传递的为null和undefined 指向globalThis,非对象转换为对象
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx)
// __fn有可能重复所以使用symbol创建属性
const key = Symbol()
// 设置可枚举属性让其不会出现在ctx中进行遍历
Reflect.defineProperty(ctx, key, {
value: this,
enumerable: false,
writable: true,
configurable: true
})
const result = ctx[key](...args)
// Reflect.deleteProperty(ctx, key) // 此方法可以不用
return result
}
Function.prototype.myApply = function (ctx, args) {
if (typeof this !== 'function') {
throw error(`TypeError:methods is not a function!`)
}
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx)
const key = Symbol()
Reflect.defineProperty(ctx, key, {
value: this,
enumerable: false,
writable: true,
configurable: true
})
const result = ctx[key](...args)
Reflect.deleteProperty(ctx, key)
return result
}
Function.prototype.myBind = function (ctx, ...args) {
if (typeof this !== 'function') {
throw error(`TypeError:methods is not a function!`)
}
let fn = this
return function (...args2) {
return fn.call(ctx, ...args, ...args2)
}
}
function method(a, b) {
console.log(this, a, b);
return a + b
}
method.call({}, 2, 3)
method.myCall({}, 2, 3)
(1).myCall({}, 2)
六、箭头函数
箭头函数没有自己的 this,箭头函数的 this 只取决于外层代码块的 this。
var name='11111'
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name);
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name);
}
},
foo4: function () {
return () => {
console.log(this.name);
}
}
};
obj1.foo1(); // obj1
obj1.foo2(); // 11111,指向window
obj1.foo3()(); // 11111,this指向window
obj1.foo4()(); // obj1