this是 JavaScript 中的关键字,与传统面向对象的语言相比, JavaScript 中的this更加灵活,涉及到它出现的位置和代表的含义。在讲述之前,我们先来看一下不使用 this 的情况。
this 的基本使用
-
现在有一个需求,需要在对象的方法中输出一些信息,如下
var obj = { name: "zhangsan", study: function () { console.log(obj.name + " study"); }, };上述代码的缺点:如果将 obj 名称改成 info,那么所有方法中的
obj.name都需要换成info.name -
在实际开发中,我们会通过
this关键字进行优化,如下var obj = { name: "zhangsan", study: function () { console.log(this.name + " study"); }, };经过上述修改后,方法中通过
this引用,解耦了对变量名的依赖。修改对象变量名时,无需修改方法中引用的名称。
this 的指向
在全局作用域下,this 指向什么?
console.log(this); // window
var name = "zhangsan";
console.log(this.name); // zhangsan
console.log(window.name); // zhangsan
console.log(this === window); // true
- 在浏览器环境中,全局作用域下,
this指向 window
在实际开发中,很少会直接在全局作用域下使用 this,通常都是在函数中使用。函数在被调用时,都会创建一个执行上下文,用于记录
- 函数的调用栈、调用方式、传入参数等信息
this也是其中的一个属性
定义一个函数、通过 3 种方式进行调用,产生了 3 种不同结果
// 定义一个函数
function foo() {
console.log(this);
}
foo(); // window对象
var obj = {
name: "zhangsan",
foo: foo,
};
obj.foo(); // {name: "zhangsan", foo: ƒ}
foo.call("lisi"); // String {"lisi"}
- 函数在调用时,JavaScript 会默认给
this绑定一个值 this的绑定和函数定义的位置无关this的绑定和函数调用方式及调用位置有关this是在运行时绑定的
那么,this的绑定规则是怎样的呢?
this 绑定规则
1、默认绑定
独立的函数调用,可以理解成函数没有被绑定到某个对象上的调用
案例 1:普通函数调用
-
函数直接调用,没有与任何对象关联
-
这种独立的函数调用就会使用默认绑定,通常情况下,函数中的
this指向全局对象windowfunction foo() { console.log(this); } foo(); // window
案例 2:函数调用链
-
所有函数调用都没有绑定到对象上
function foo() { console.log(this); bar(); // window } function bar() { console.log(this); baz(); // window } function baz() { console.log(this); } foo(); // window
案例 3:将函数作为另一个函数的参数进行调用
-
将函数作为参数,传入另一个函数中,在另一函数中直接调用该函数
function foo(fn) { fn(); } function bar() { console.log(this); } foo(bar); // window
案例 4:将对象中的一个方法传入另一个函数中进行调用
-
将对象中的方法传入到另一个函数中,另一个函数直接调用
function foo(fn) { fn(); } var obj = { name: "zhangsan", bar: function () { console.log(this); }, }; foo(obj.bar); // window- 上例中的 this 之所以是 window,是因为在真正调用函数的位置,并没有与对象进行关联,是一个独立的函数调用
2、隐式绑定
比较常见的调用方式,通过某个对象进行调用。也就是它的调用位置中,是通过某个对象发起的函数调用
案例 1:通过对象调用函数
- foo 的调用位置是
obj.foo()方式进行调用的 - foo 调用时的
this就会绑定到 obj 对象上
function foo() {
console.log(this); // obj
console.log(this === obj); // true
}
var obj = {
name: "zhangsan",
foo: foo,
};
obj.foo();
案例 2:多层对象调用函数
- 通过 obj2 引用了 obj1,再通过 obj1 对象调用 foo 函数
- foo 调用时的
this还是绑定了 obj1 对象上
function foo() {
console.log(this); // obj1
console.log(this === obj1); // true
}
var obj1 = {
name: "zhangsan",
foo: foo,
};
var obj2 = {
name: "zhangsan",
obj1: obj1,
};
obj2.obj1.foo();
案例 3:隐式丢失
- foo 被调用的位置是 bar,而 bar 在调用时没有绑定任何对象,是独立函数调用。
function foo() {
console.log(this); // window
console.log(this === window); // true
}
var obj = {
name: "zhangsan",
foo: foo,
};
var bar = obj.foo;
bar();
隐式绑定的前提条件是
- 必须在调用的对象内部有一个对函数的引用
- 如果没有这样的引用,在进行调用时,会报错找不到该函数
- 正是通过这个引用,间接将
this绑定到这个对象上。
如果不希望对象内部包含这个属性,同时又希望在这个对象上强制调用,可以通过 call、apply进行显式绑定
3、显式绑定
JavaScript 所有函数都可以使用 call() 和 apply() 方法
- 二者的区别在于第 2 个参数不同。
call()是参数列表、apply()是数组. - 二者的第 1 个参数,是在函数调用时绑定到的 this 对象
案例 1:通过 call、apply 绑定 this
- 显式绑定后,
this就会明确的指向绑定的对象
function foo() {
console.log(this);
}
var obj = {
name: "zhangsan",
};
foo.call(window); // window
foo.call(obj); // obj {name: "zhangsan"}
foo.call("lisi"); // String {"lisi"}
案例 2:通过 bind 函数
- 通过
bind()绑定this后,调用bind()方法返回的函数,该函数的this始终是传入 bind 函数的参数
function foo() {
console.log(this);
}
var obj = {
name: "zhangsan",
};
var bar = foo.bind(obj);
bar(); // obj {name: "zhangsan"}
bar(); // obj {name: "zhangsan"}
4、new 绑定
JavaScript 中的函数,可以通过 new 关键字当作一个类的构造函数来使用。通过 new 关键字调用函数时,会执行如下操作:
- 创建一个新对象
- 这个新对象会执行
prototype连接 - 新对象会绑定到函数调用的
this上 - 如果函数没有返回其他对象,则会返回这个新对象
function Person(name) {
console.log(this); // Person {}
this.name = name;
}
var p = new Person("zhangsan");
console.log(p); // Person {name: "zhangsan"}
this 绑定优先级
上面介绍了this绑定的四种规则,在开发中我们只需要查找函数的调用应用了哪条规则即可。但如果一个函数的调用位置应用了多条规则,哪条规则的优先级更高呢?
默认规则的优先级最低
默认规则的优先级是最低的,因为存在其它规则时,就会通过其它规则的方式绑定 this
显式绑定优先级高于隐式绑定
隐式绑定和显式绑定同时存在时,最终的this指向显式绑定传入的值,说明显式绑定的优先级高于隐式绑定
function foo() {
console.log(this);
}
var obj1 = {
name: "zhangsan",
foo: foo,
};
var obj2 = {
name: "lisi",
foo: foo,
};
// 隐式绑定
obj1.foo(); // obj1 {name: "zhangsan", foo: ƒ}
obj2.foo(); // obj2 {name: "lisi", foo: ƒ}
// 隐式绑定和显式绑定同时存在
obj1.foo.call(obj2); // obj2 {name: "lisi", foo: ƒ}
new 绑定高于隐式绑定
new 绑定和隐式绑定同时存在时,最终的this指向 new 创建出来的对象,所以 new 绑定的优先级高于隐式绑定
function foo() {
console.log(this);
}
var obj = {
name: "zhangsan",
foo: foo,
};
new obj.foo(); // foo {}
new 绑定高于 bind 绑定
new绑定不能和call()、apply()同时使用,所以不存在谁的优先级更高
function foo() {
console.log(this);
}
var obj = {
name: "zhangsan",
foo: foo,
};
var obj2 = new obj.foo.call(obj); // Uncaught TypeError: obj.foo.call is not a constructor
new绑定可以和bind一起使用,但最终this指向新创建出的对象,说明new绑定的优先级高于bind绑定
function foo() {
console.log(this);
}
var obj = {
name: "zhangsan",
foo: foo,
};
var bar = foo.bind(obj);
new bar(); // foo {}
绑定优先级
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
this 绑定规则特殊应用
忽略显示绑定
如果在显式绑定中,传入了null或undefined,那么这个显式绑定就会被忽略,使用默认规则。
function foo() {
console.log(this);
}
var obj = {
name: "zhangsan",
};
foo.call(obj); // obj
foo.call(null); // window
foo.call(undefined); // window
var bar = foo.bind(null);
bar(); // window
var baz = foo.bind(undefined);
baz(); // window
间接函数引用
创建一个函数的间接引用,这种情况使用默认规则。
function foo() {
console.log(this);
}
var obj1 = {
name: "zhangsan",
foo: foo,
};
var obj2 = {
name: "lisi",
};
obj1.foo(); //obj
(obj2.foo = obj1.foo)(); // window
ES6 中的箭头函数
在 ES6 中的箭头函数,不使用this的四种绑定规则,而是根据外层作用域的this来决定。
1、模拟一个网络请求
-
使用
setTimeout模拟网络请求,请求到数据后如何可以存放到 data 中呢? -
需要拿到 obj 对象,设置 data
-
直接拿到的 this 是 window,我们需要在外层定义:
var _this = this -
在
setTimeout的回调函数中使用_this 就代表了 obj 对象var obj = { data: [], getData: function () { var _this = this; setTimeout(function () { // 模拟获取到的数据 var res = ["abc", "cba", "nba"]; _this.data.push(...res); }, 1000); }, }; obj.getData();
上例的代码在 ES6 之前是我们最常用的方式,从 ES6 开始,我们会使用箭头函数:
-
为什么在
setTimeout的回调函数中可以直接使用this呢? -
因为箭头函数并不绑定
this对象,那么this引用就会从上层作用域中找到对应的thisvar obj = { data: [], getData: function () { setTimeout(() => { // 模拟获取到的数据 var res = ["abc", "cba", "nba"]; this.data.push(...res); }, 1000); }, }; obj.getData();
上例中如果 getData 也是一个箭头函数,那么setTimeout中的回调函数中的this指向谁呢?
- 答案是
window; - 依然是不断的从上层作用域找,那么找到了全局作用域;
- 在全局作用域内,
this代表的就是window
var obj = {
data: [],
getData: () => {
setTimeout(() => {
console.log(this); // window
}, 1000);
},
};
obj.getData();
下一篇,梳理JavaScript中的函数式编程
- 如果认为本篇知识点梳理的尚可,欢迎点赞
- 如果有需要补充、指正的地方,也欢迎评论留言哦~