前言
前排声明,这真的是写 this,没有什么太多新的东西,就是一个自己对 this 绑定规则的总结,也许后期水平提高会从更深的角度去解释 JavaScript 中的 this 绑定规则。本文从分别从绑定全局对象和绑定具体对象的角度总结了一下 this 的绑定规则。
绑定全局对象
this 绑定全局对象,分为两种情况:
- 直接在全局作用域下使用 this
- 被调用的函数不满足绑定具体对象的绑定规则
直接在全局作用域下使用 this
这种没什么好说的,全局作用域下调用 this 绑定的是全局对象,例如浏览器中绑定的是 window 对象。
console.log(this)复制代码
直接拿上面这段代码在浏览器控制台运行下得到的就是全局对象。
被调用的函数不满足绑定具体对象的绑定规则
直接使用函数名调用函数
function test () { console.log(this); // window}test();var test1 = function() { console.log(this); // window}test1();复制代码
定义某对象的属性为函数,该属性值被赋值被另一变量时。
var aObj = { propertyFn: function() { console.log(this.testName); }, testName: "aObj"};aObj.propertyFn(); // "aObj"var aFn = aObj.propertyFn;aFn(); // undefined复制代码
绑定具体对象
绑定具体对象的情况较多,可总结为以下几种
- 函数作为对象属性被获取并调用
- 使用 new 关键字调用函数
- 使用 call,apply,bind 调用函数
- 函数被注册为事件监听器和事件处理器
- 箭头函数中的 this
函数作为对象属性被获取并调用
函数作为对象属性被获取并调用时,函数中的 this 绑定的是获取该属性的对象。考虑如下代码
function test() { return this.testName;}var testObj = { testName: "testObj", getTestName: test, getTestNameFn: function() { return this.testName; }}console.log(testObj.getTestName()); // "testObj" 函数虽然是在全局作用域下定义的, // 但是被赋值给了testObj的getTestName属性,且是被作为对象的属性调用的console.log(testObj.getTestNameFn()); // "testObj"复制代码
使用 new 关键字调用函数
使用 new 关键字调用函数时,该函数会生成并返回以个新的对象,函数中的 this 绑定的生成的新对象。
function Test(name) { this.name = name; console.log(this);}var s = new Test("s"); // {name: "s"}function Test1(name) { this.name = name; console.log(this); return true;}var s1 = new Test1("s1"); // {name: "s1"}function Test2(name) { this.name = name; console.log(this); return {};}var s2 = new Test2("s2"); // {name: "s2"}复制代码
上述结果表明,使用 new 调用函数的时候一定会生成一个新对象,且 this 绑定的就是这个新对象,只不过当你在函数中 return 了非 Object 类型的值时,这个对象不会被赋值给你定义的接收变量,这时接收的变量被赋的是函数中使用 return 返回的值。
使用call ,apply,dind 调用函数
这里call和apply的作用是类似的,都是函数的实例方法,可为函数指定 this 绑定的对象,两者区别在于 apply 的第二个参数是数组,该数组中的值会以实参形式被传递给调用 apply 的函数,而 call 函数除了第一个参数外的参数均被传递给调用 call 的函数。
function test(param1, param2) { console.log(this.name, param1 + ", " + param2);}var a = { name: "a"};var b = { name: "b"}test.call(a, "aParam1", "bParam2");test.apply(b, ["bParam1", "bParam2"]);test();复制代码
bind函数的作用和以上两者与别很大,其作用是将函数中的 this 绑定对象与指定的对象绑定起来,返回一个函数,每次调用返回的函数时,其 this 都是绑定的指定对象。
function test() { console.log(this.name);}var a = { name: "a"}var bindTest = test.bind(a);bindTest(); // "a"var b = { name: "b", getName: bindTest}b.getName(); // "a"var c = new bindTest(); // undefinedbindTest.call(b); // "a"复制代码
从上面的示例代码可以看出,函数和指定对象被绑定后使用 new 关键字是绑定失效,在以上示例中绑定函数中的 this 绑定的是一个新创建的对象实例,且该对想的构造函数时test函数。由此也可得出,this 绑定场景同时出现的情况下 new 的优先级是高于调用 bind 函数的优先级的。
箭头函数
关于箭头函数中 this 绑定,MDN 中的说法是箭头函数是没有自己的 this 的,其 this 是从其作用域链上层作用域继承而来的。那么怎么理解呢?下面上代码:
let arrowFn = () => { console.log(this === window)};arrowFn(); // truelet a = { name: "a", getSelf: arrowFn};a.getSelf(); // truelet b = { name: "b"}arrowFn.call(b); // true复制代码
以上代码是箭头函数直接在全局作用域下定义的情况,那么其作用域链上层就是全局作用域,而在浏览器中全局作用域 this 绑定的值是 window 。由于 JavaScript 中的作用域是静态作用域,那么箭头函数在全局作用域中定义时便已经可以确定其 this 就是 window 了,而且后面的该箭头函数作为对象属性值被调用,还是使用 call 显示指定 this 其 this 均为改变。而非箭头函数的画风是这样的:
let fn = function() { console.log(this === window);}fn(); // truelet a = { name: "a", getSelf: fn};a.getSelf(); // falselet b = { name: "b"}fn.call(b); // false复制代码
那么是不是一旦箭头函数被定义了,其 this 的绑定就已经被确定了呢?
let createArrowFn = function() { return () => { console.log(this)};}let a = { name: "a", getSelf: createArrowFn};let aArrow = a.getSelf(); aArrow(); // 对象alet b = { name: "b"}var bArrow = createArrowFn.call(b); bArrow(); // {name: "b"}复制代码
上面代码两次 this 打印的结果是不一样的,那么是不是就推翻了箭头函数一旦被定义,其 this 就已经确定了的结论。其实不然,这里箭头函数的是上层作用域是createArrowFn这个函数的作用域,这个函数作用域中的 this 会随着调用场景的不同发生发生变化,所以继承其作用域绑定 this 的箭头函数中的 this 自然也会发生改变了。其实箭头函数中的 this 可以这么理解,相当于将函数的上层作用域的 this 用一个变量保存下来,然后在其子函数中使用它。
let fn = function() { var _this = this; return function() { console.log(_this); }}let a = { name: "a", getSelf: createArrowFn};let aArrow = a.getSelf(); aArrow(); // 对象alet b = { name: "b"}var bArrow = createArrowFn.call(b); bArrow(); // {name: "b"}复制代码
在理解箭头函数中的 this 时只需理解其被定义时所在作用域的 this 绑定的是什么就可以了。
事件监听器和事件处理器中的 this
先说明不管是事件监听器还是事件处理器中 this 绑定的都是当前触发该事件的节点,即绑定了该事件的元素节点。
这里主要是区分下事件监听器和事件处理器,事件处理器其实是指 html 标签的 on... 属性定义的函数,比如 οnclick="function() {}",当然也可以在 JavaScript 中去设置该属性,事件处理器的特点是其只能有一个,因为是 html 标签属性所以可以覆盖。
事件监听器是指使用addEventListener函数注册的事件回调函数,可同时注册多个。
结论
在讨论 JavaScript 中 this 的绑定值时,其实就是几种情况:
- new 函数时this是新创建的对象
- call, apply, bind 指定的对象
- 调用该函数的对象
- 箭头函数的 this 取决于其定义时所在作用域的 this 绑定值
- 事件监听器和事件处理器的中的 this 绑定的是绑定函数节点
- 上面的情况都不是时,严格模式下的 undefined ,非严格模式下的全局变量
以上总结仅仅为粗浅的不同场景下 this 的绑定值的总结,没有从更深的层次(比如ES标准中的定义)去讨论,主要个人
时间水平有限,所以大佬请忽略。如有错误欢迎各位指正,不胜感激。