全文约 2000 字,读完大约须要 5 分钟git
今天咱们就来啃下这个你可能惧怕但又不得不去吃的瓜,this
!github
首先, this 在大多数状况下是一个对象,也有多是 undefined 或其余值。数组
什么状况下,this
是 undefined
?函数运行在严格模式下,应用默认绑定规则的时候:markdown
var a = 1;
function foo() {
"use strict";
console.log(this.a);
};
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
复制代码
原理其实很简单,由于规范定义了严格模式下,不能将全局对象 Window
用于默认绑定。而大多数状况下,咱们说的 this
,其实就是一个对象,因此肯定 this
的指向,本质上就是要找到这个对象。app
因此接下来我就来教你们如何 “找对象” 🤣 。函数
找对象最重要的是什么?是否是得先经过各类途径(社交,搭讪,相亲...)去认识对象,途径越多,咱们找到对象的概率就越大,对吧,这里也是同样,因此咱们须要尽量的了解 this
的绑定规则。oop
ECMAScript 5规范 定义的 this
的绑定规则,有 4 种。ui
教科书会告诉咱们,几乎全部的规则都会有一个默认的状况,this
this
绑定也不例外,默认绑定的规则为:es5
非严格模式下,this 指向全局对象,严格模式下,this 会绑定到 undefined。
var a = 1;
function foo() {
console.log(this.a);
};
function bar() {
"use strict";
console.log(this.a);
};
foo(); // 1,非严格模式下,this 指向全局对象 Window,这里至关于 Window.a
bar(); // Uncaught TypeError: Cannot read property 'a' of undefined,严格模式下,this 会绑定到 undefined,尝试从 undefined 读取属性会报错
复制代码
若是函数在调用位置有上下文对象,this 就会隐式地绑定到这个对象上,
提及来有点晦涩,直接看例子:
var a = 1;
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo, // <-- foo 的调用位置
};
obj.foo(); // 2,foo 在调用位置有上下文对象 obj,this 会隐式地绑定到 obj,this.a 至关于 obj.a
复制代码
这个规则可能会让你想起关于 this
常常听到的一句话,this 依赖于调用函数前的对象。
须要注意的是,隐式绑定在某些状况下可能会致使绑定丢失,具体来讲有两种状况,
第一种是使用函数别名调用时:
var a = 1;
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo;
bar(); // 1,赋值并不会改变引用自己,使用函数别名调用时,bar 虽然是 obj.foo 的一个引用,可是实际上引用的仍是 foo 函数自己,因此这里隐式绑定并无生效, this 应用的是默认绑定
复制代码
第二种是函数做为参数传递时:
function foo() {
console.log(this.a);
};
function bar(fn) {
fn(); // <-- 调用位置
};
var a = 1;
var obj = {
a: 2,
foo: foo,
};
bar(obj.foo); // 1, 参数传递也是一种隐式赋值,即便传入的是函数,这里至关于 fn = obj.foo,因此 fn 实际上引用的仍是 foo 函数自己,this 应用默认绑定
复制代码
咱们知道 call
,apply
,bind
等方法能够改变 this
的指向,经过传入参数就能够指定 this
的绑定值,够不够显式 ?这种明目张胆的绑定 this
的规则就叫显式绑定。
call
和 apply
的区别只是接受的参数格式不一样,call
接受一个参数列表,apply
接受一个参数数组,但二者的第一个参数都是相同的,都是 绑定的 this 值:
function foo() {
console.log(this.a);
};
var a = 1;
var obj = { a: 2 };
foo.call(obj); // 2,调用时显式地将 foo 的 this 绑定为 obj 对象,因此这里的 this.a 至关于 obj.a
foo.apply(obj); // 2,同理
复制代码
前文咱们提到隐式绑定可能会致使绑定丢失,显式绑定也不例外,
思考一下,如何才能解决绑定丢失的问题?
答案其实很简单,只须要在调用函数的内部使用显式绑定,强制地将 this
绑定到对象:
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo,
};
function bar(fn) {
fn.call(obj);
};
var a = 1;
bar(obj.foo); // 2,
复制代码
这其实就是 bind 的实现原理,与 call
,apply
不一样,bind
调用后不会执行,而是会返回一个硬绑定的函数,因此经过 bind
能够解决绑定丢失的问题。bind
也是显式绑定,咱们来回顾下 bind
的用法:
function foo() {
console.log(this.a);
};
var obj = { a: 2 };
var a = 1;
var bar = foo.bind(obj);
bar(); // 2,bar 是经过 bind 返回后的一个硬绑定函数,其内部应用了显式绑定
复制代码
此外,还须要注意的是,将 null
,undefined
做为第一个参数传入 call
,apply
,bind
,调用时会被忽略,实际应用的是默认绑定规则,即严格模式下,this
为 undefined
,非严格模式下为全局对象。
先来回顾下 new 的实现原理,
function _new() {
let obj = new Object(); // 1. 建立一个空对象
let Con = [].shift.call(arguments); // 2. 得到构造函数
obj.__proto__ = Con.prototype; // 3. 连接到原型
let result = Con.apply(obj, arguments); // 4. 绑定 this,执行构造函数
return typeof result === 'object' ? result : obj; // 5. 返回 new 出来的对象
}
复制代码
在使用 new
来调用函数时,会建立一个连接到函数原型的对象,并把它绑定到函数调用的 this
,因此应用了 new
绑定规则后,不会被任何方式修改 this
指向:
function foo(a) {
this.a = a;
};
var bar = new foo(2);
bar.a; // 2,new 会返回一个对象,这个对象绑定到构造函数的 this
复制代码
ES6 中新增了一种函数类型,箭头函数,箭头函数中 this
不会应用上述规则,而是根据最外层的词法做用域来肯定 this,简单来讲,箭头函数的 this
就是它外面第一个不是箭头函数的函数的 this:
function foo() {
return () => {
return () => {
console.log(this.a);
};
};
};
foo()(); // undefined,箭头函数调用时,this 取决于最外层的第一个不是箭头函数的函数,这里就是 foo 函数,非严格模式下,默认绑定全局对象 Window,this.a 至关于 Window.a,输出 undefined
复制代码
this
绑定的优先级为:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
根据绑定规则和优先级,咱们能够总结出 this
判断的一些通用模式,
new
调用?call
,apply
,bind
调用?本文首发于个人 博客,才疏学浅,不免有错误,文章有误之处还望不吝指正!
若是有疑问或者发现错误,能够在相应的 issues 进行提问或勘误
若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励
(完)