this的正确用法

作者: 钢笔先生 | 来源:发表于2018-06-27 16:59 被阅读0次

何为Call-Site

表示的是:函数在哪里被调用的。

一切都是规则

四条军规。

军规一:默认绑定

其他规则不匹配时就用这个规则啦。

第一件值得注意的事情是:全局环境下声明的变量,就是全局对象的属性了。

function foo() {
    console.log( this.a );
}
var a = 2; // 自动变成全局对象的属性

foo(); // 2

这里的this绑定就指向了全局对象,并拿到了全局对象的属性a.

但是,如果开启了strict模式,则全局变量就不适用于默认绑定了,this会变成未定义。

总结:总体来说,this绑定规则完全是根据Call Site的,全局对象只在非严格模式下才能用于默认绑定。严格模式下,会判定foo函数的调用为无关。

另外注意一点是,你的程序要么是严格模式,要么是非严格模式,不可以一边严格一边不严格。有时候引入的第三方库和你的代码里的模式会有不同。

隐式绑定

上下文对象。

拥有或者包含其他对象的对象。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

这里的obj就是上下文对象。

foo函数用作obj对象的reference property

这里的this就指向obj对象。

function foo() {
    console.log( this.a );
}
var obj2 = {
    a: 42,
    foo: foo
};
var obj1 = {
  a: 2,
  obj2: obj2
};
obj1.obj2.foo(); // 42

this认的是最近的这个。

隐式丢失

这是很常见的一类错误。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var bar = bar.foo; // 函数别名,函数的reference
var a = "oops, global";
bar(); // "oops, global"

这里如果是我自己想的话,会觉得输出为2.

之所以又跑到全局的原因是:barrefer到的是foo本身,不带有obj这个上下文对象。

还有更隐蔽更让人摸不到头脑的呢!

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    fn(); // fn只会被认为是另一个foo函数的reference
}

var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global";
doFoo(obj.foo); // "oops, global"

这个是真的难以察觉的,因为会认为fn()Call Site,但实际上fn是函数foo的别名。

即使是内建函数调用也是一样的效果:

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    fn(); // fn只会被认为是另一个foo函数的reference
}

var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global";
setTimeout(obj.foo, 100); // "oops, global"

结果是一样的。

如何避免这些问题呢?

显式绑定

在JS中的所有函数都有一些特性可用,通过原型链

而且,函数有call()apply()方法可用。

看代码就明白了:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
foo.call( obj ); // 2

通过foo.call(obj)可以强行把this绑定到obj上。

来来来,我喊到的谁谁就是当前的this啦!

硬绑定

functionn foo() {
    console.log( this.a );
}

var obj = {
    a: 2
};
var bar = function() {
    foo.call( obj ); // 强制性把`this`绑定给obj
};
bar(); //2
setTimeout( bar, 100 ); //2
bar.call(window); // 2

bar函数内部强制绑定。

最典型的硬绑定的方式是下面这种,可以传递任意参数的:

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    a: 2
};
var bar = function() {
    return foo.apply( obj, arguments ); 
}
var b = bar(3); // 2 3
console.log( b ); // 5

这种写法不是特别能理解,bar函数明明不接受参数,只是在return返回的是foo.apply( obj, arguments)。但是调用bar函数时传递了参数!?

为了表示这种模式,可以用下面的更加可以复用的写法:

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
// 将函数fn和obj绑定在一起
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 3
console.log( b ); // 5

整体输出效果和上面的案例是一样的。只不过是用bind这种写法会更加可复用。

既然硬绑定这么普遍,所以这在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 ); // 直接就bind了foo函数内的this给obj了
var b = bar( 3 ); // 

语言层面实现好,真的大大减轻工作量的!一行就硬绑定,那么我也理解为什么会有函数绑定的用法了~一切都源于`this`这个任性的关键词啊

API内叫作contexts

很多JS内置函数和主机环境提供一个可选关键词,称作context,设计用来避免使用bind却能保证回调函数正确使用了this关键词。

下面的例子一下没看明白,看了两次才看懂:

function foo(el) {
    console.log( el, this.id);
}
var obj = {
    id: "awesome"
};
// obj就是这样的可选参数,用于接收foo内的this哦~
[1,2,3].forEach( foo, obj );// 1 awesome 2 awesome 3 awesome
[1,2,3].forEach( foo ); // 1 undefined 2 undefined 3 undefined

如果在全局加一个:var id = "global",再调用[1,2,3].forEach( foo ); 结果就是:

1 "global" 2 "global" 3 "global"

通过new绑定

最后一条军规了~

仍然是从我们可能犯错的地方出发思考。

重新定义js中的构造器

function foo(a) {
    this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2

这么干净的就宣布了thisfoo的了~

所以来认真思考下为什么~~

JS中,用new并不表示实例化了一个类,只不过恰巧和有类的语言同名了而已,没有其他特殊因素。

JS中的函数,包含内建函数,能够通过在函数名前加new,这会使得这类函数调用有个新的名字:constructor call. 大概翻译为构造器调用,而并非构造器。

这个区别很细微但很重要:实际没有一个构造器函数,只是一种构造器调用,具体构造啥呢?

当函数前面有new时,下面几条会自动执行:

  1. 一个全新的对象被建立
  2. 新建的对象是原型连接的
  3. 新建的对象被设定为绑定了this,意味着在该函数内使用this就是表示自身
  4. Unless the function returns its own alternate object, the new- invoked function call will automatically return the newly con‐structed object 这个还不知道如何理解呢

万物皆有序

现在四条军规已经揭晓,我们所需要做的就是找到调用点,并确定使用了哪个规则。

但是如果多条规则同时满足呢?这时候就需要个顺序了。

默认绑定顺序是最靠后的,这个毋庸置疑。

先看显式绑定和隐式绑定的顺序。

function foo() {
    console.log( this.a );
}
var obj1 = {
    a: 2,
    foo: foo
};
var obj2 = {
    a: 3, 
    foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2

可见,显式绑定顺序在隐式绑定之前。

现在问题就只剩下一个了,new绑定和显式绑定谁先谁后?

还是通过代码来看:

function foo(something) {
    this.a = something;
}
var obj1 = {
    foo: foo
};
var obj2 = {};
// 隐式绑定
obj1.foo(2); // 为this.a赋值2,但是你猜this现在指向谁呢?
console.log(obj1.a); // 2,为obj1添加了一个新属性

obj1.foo.call(obj2, 3); // 显式调用,并传递数据
console.log(obj2.a); // 3,this指向obj2

var bar = new obj1.foo(4); // new, this指向函数自身
console.log(obj1.a); // 2 
console.log(bar.a); // 4

new绑定顺序在隐式绑定之前。

但是newcall/apply不能一起用。

如何测出new绑定和显式绑定的顺序呢?

function foo(something) {
    this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1); // 强行赋予this给obj1
bar(2); // 
console.log(obj1.a); // 2

var baz = new bar(3); 
console.log(obj1.a); // 2, new修改了this的指向
console.log(baz.a); // 3

new绑定的顺序在显式绑定之前。

再看一个:

function foo(p1,p2) {
    this.val = p1 + p2;
}
var bar = foo.bind(null, "p1");
var baz = new bar("p2");

baz.val; // "p1p2"

通过var bar = foo.bind(null, "p1")this绑定到了空对象上,但是通过new覆盖了this的绑定,结果是"p1p2",那么,这个p1是如何保留的呢?

关于这部分内容,仍然有待进一步思考理解才能完善。

目前只是一个大概的认知。

还有个softBind,暂时不深究。

箭头函数的this规则

在ES6中,箭头函数用的不是上面的四个规则。

function foo() {
    return (a) => {
        console.log( this.a ); 
    }
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var bar = foo.call(obj1);
bar.call(obj2);// 2

barfoo.call(obj1)的返回结果,返回的是箭头函数。

foo函数内创建的箭头函数,会在调用时捕获foo中的this

The lexical binding of an arrow-function cannot be overridden (even with new!).

function foo() {
    setTimeout(() => {
        console.log( this.a );
    });
}
var obj = {
    a: 2
};
foo.call( obj ); // 2

要注意箭头函数和前面四条规则的不同,程序中可以混用,但是在单个函数内可不要两种都出现。

ES6提出的箭头函数是在词法作用域进行的this绑定。就是说,箭头函数会从闭包函数继承this绑定。

相关文章

网友评论

    本文标题:this的正确用法

    本文链接:https://www.haomeiwen.com/subject/nkhiyftx.html