this是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。
this既不指向函数自身,也不指向函数的词法作用域,具体指向什么,取决于你是怎么调用函数。
function foo() { console.log(this.a);}var a = 2;foo(); // 2
严格模式:
function foo() { "use strict" console.log(this.a);}var a = 2;foo(); // undefiend
function foo() { console.log(this.a);}var obj = { a: 2, foo: foo}obj.foo(); // 2
注意事项
function foo() { console.log(this.a);}var obj1 = { a: 42, foo: foo}var obj2 = { a: 3, obj1: obj1}obj2.obj1.foo() // 42
function foo() { console.log(this.a);}function doFoo(fn) { // fn 其实引用的是foo fn();}var obj = { a: 2, foo: foo}var a = "oops, global";doFoo(obj.foo) // 输出"oops, global"
使用call, apply方法调该函数时,this指向call,apply方法所传入的对象,这种方式成为显示绑定
js中几乎所有的函数都有call()和apply()方法,这两个方法的第一个参数是一个对象,他们会把这个对象绑定到this.
function foo() { console.log(this.a);}var obj = { a: 2,}foo.call(obj); // 2
显示绑定仍然存在绑定丢失的问题:例如
function foo() { console.log(this.a);}function doFoo(fn) { fn();}var obj = { a: 2,}var a = "oops, global";doFoo.call(obj, foo); // 输出oops, global
解决办法:
function doFoo(fn) { fn.call(obj);}
硬绑定一种常见的应用常见是创建一个可重复使用的辅助函数
function foo(something) { console.log(this.a, something); return this.a + something;}// 简单的辅助绑定函数function bind(fn, obj) { return function() { return fn.apply(obj, arguments); }}var obj = {a:2};var bar = bind(foo, obj);var b = bar(3); // 输出2 3console.log(b); // 输出5
ES5中提供了内置的硬绑定的方法: Function.prototype.bind, 用法如下:
function foo(something) { console.log(this.a, something); return this.a + something;}var obj = { a:2;}var bar = foo.bind(obj);var b = bar(3); // 2 3console.log(b); // 5
bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this的上下文并调用原始函数。
在javascript中,所有的函数都可以用new来调用,这种函数调用被称为构造函数调用。使用new来调用函数时,会自动执行下面操作:
1. 创建一个全新的对象;2. 这个新对象会被执行[[原型]链接;3. 这个新对象会绑定到函数调用的this4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
示例:
function foo(a) { this.a = a; // 在这里,this绑定到bar对象}var bar = new foo(2);console.log(bar.a); // 2
new绑定优先级 > 显示绑定 > 隐式绑定 > 默认绑定
function foo(something) { this.a = something;}var obj1 = {foo: foo};var obj2 = {};obj1.foo.call(obj2, 3); // foo函数的调用同时出现了隐式绑定和显示绑定,则显示绑定优先,this指向 obj2console.log(obj2.a); // obj2.a = 3var bar = new obj1.foo(4); // 同时出现了隐式绑定和new绑定, this指向new绑定创建的对象bar, 而不是obj1console.log(bar.a); // 输出4
function foo(something) { this.a = something;}var obj1 = {};var bar = foo.bind( obj1 );bar( 2 );console.log( obj1.a ); // 2var baz = new bar(3);console.log( obj1.a ); // 2console.log( baz.a ); // 3
bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a
修改为 3。相反, new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this
function foo() { console.log(this.a);}var a = 2;foo.call(null); // 输出2
function foo() { console.log(this.a);}var a = 2;var o = { a: 3, foo: foo };var p = { a: 4 };o.foo(); // 3(p.foo = o.foo)(); // 2
赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是p.foo() 或者 o.foo()
前面提到的四种绑定规则对箭头函数不适用,箭头函数中的this是根据外层作用域来决定的。
function foo() { return (a) => { console.log(this.a); }}var obj1 = {a:2};var obj2 = {a:3};var bar = foo.call(obj1); // 箭头函数的外层作用域中的this指向 obj1, 所以箭头函数中this也指向obj1,箭头函数的绑定无法被修改bar.call(obj2); // 输出2,
判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置,找到后就可以顺序应用这四条规则判断this的绑定对象。
另外还需要注意上面提到的几种特殊情况。