资讯专栏INFORMATION COLUMN

JS中的this详解

Mike617 / 2299人阅读

摘要:实际上并不存在什么构造函数,只存在对于函数的构造调用发生构造函数的调用时,会自动执行下边的操作创建一个全新的对象。说明绑定的优先级高于硬绑定。

原文阅读

  js中的this是很容易让人觉得困惑的地方,这篇文章打算说一下this绑定的几种情况,相信可以解决大部分关于this的疑惑。

this是在运行的时候绑定的,不是在编写的时候绑定的,函数调用的方式不同,就可能使this所绑定的对象不同。

1.几种绑定规则

  函数调用的位置对this的指向有着很大的影响,但却不是完全取决于它。下面是几种this的绑定规则:

1.1.默认绑定

  默认规则的意思就是在一般情况下,如果没有别的规则出现,就将this绑定到全局对象上,我们看如下代码:

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

  这段代码中,this是被默认绑定到了全局对象上,所以this.a得到的是2。我们如何判断这里应用了默认绑定呢?foo在调用的时候直接使用不带任何修饰的函数引用,只能使用默认绑定。有人会误认为结果是3this常有的几种错误理解之一就是认为this指向当前函数的词法作用域,this与词法作用域以及作用域对象是完全不同的东西,作用域对象是在引擎内部的,js代码是无法访问的。还有本文我们不讨论严格模式下的情况,严格模式这里的this会绑定到undefined

1.2.隐式绑定

  如果在调用位置有上下文对象,说简单点就是这个函数调用时是用一个对象.出来的。就像下边这样,它就遵循隐式绑定:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
}
var a = "opps, gloabl";                //全局对象的属性
obj.foo();                    //2
var bar = obj.foo;
bar();                        //"opps, gloabl"

  第9行代码,就是函数在调用的时候,是用前边的对象加上.操作符调用出来的,此时就用到了隐式绑定规则,隐式绑定规则会将函数调用中的this绑定到这个上下文对象,此时的this.aobj.a是一样的。
  而隐式绑定会出现一个问题,就是隐式丢失,上边的第10行代码,是新建一个foo函数的引用,即bar,在最后一行调用的时候,这个函数不具有上下文对象,此时采用默认绑定规则,得到的结果自然是opps, gloabl;
  绑定丢失也会发生在函数作为参数传递的情况下,即传入回调函数时,因为参数传递就是一种隐式赋值,看如下代码:

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    fn();            //在此处调用,参数传递是隐式赋值,丢失this绑定
}
var obj = {
    a: 2,
    foo: foo
};
var a = "opps, global";
doFoo( obj.foo );        //看似是隐式绑定,输出opps, global

  javascript环境中内置的函数,在具有接受一个函数作为参数的功能的时候,也会发生像上边这种状况。例如setTimeout函数的实现就类似于下边的伪代码:

function setTimeout(fn, delay) {
    //等待delay毫秒
    fn();//在此处调用
}

  所以回调函数丢失this绑定是非常常见的,后边我们再看如何通过固定this来修复这个问题。

1.3.显式绑定

  在此之前,相信你已经用过很多次applycall函数了,使用这两个函数可以直接为你要执行的函数指定this,所以这种方式称为显式绑定。

function foo() {        //显式绑定this,这种方式依然无法解决绑定丢失的问题
    console.log( this.a );
}
var obj = {
    a: 2
};
foo.call( obj );        //2

  通过像上边这样调用,我们可以将foothis强制绑定到obj上。如果给call传入的是一个基本类型数据,这个基本类型数据将会被转换成对应的基本包装类型。不过这种方式依然无法解决上边的丢失绑定问题。

1.3.1.硬绑定

  为了解决这个问题,我们可以写像下边这样的代码:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
var bar = function() {         //创建一个包裹函数,以确保obj的绑定
    foo.call( obj );
};
bar();                       //2
setTimeout( bar, 100 );           //2
bar.call( window );           //2

  上边这样的函数确实解决了绑定丢失的问题,每次调用bar就可以确保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);
console.log(b);

  因为这种模式很常用,所以ES5内置了这个方法,就是bindbind(...)返回一个硬编码的新函数,将你指定的对象绑定到调用它的函数的this上。

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    a: 2
};
var bar = foo.bind( obj );    //bind返回一个绑定到obj上的新函数
var b = bar(3);
console.log(b);
var a = "window"s a";
foo("!");                       //对原来的函数不产生影响
1.3.2.API调用参数指定this

  许多第三方库里的函数,以及许多语言内置的函数,都提供了一个可选的参数用来指定函数执行的this

function foo(el) {
    console.log( el, this.id );
}
var obj = {
    id: "awesome"
};
[1, 2, 3].forEach( foo, obj );   //forEach的第二个参数就是用来设置this
1.4.new绑定

js中的所谓的构造函数,其实和一般的普通函数没有什么区别,并不具有特殊性,它们只是被new操作符调用的普通函数而已。实际上并不存在什么构造函数,只存在对于函数的构造调用

发生构造函数的调用时,会自动执行下边的操作:

创建一个全新的对象。

这个对象会被执行[[Prototype]]连接。

这个新对象会绑定到函数调用的this

执行这个函数里的代码。

如果函数没有返回其他对象,则自动返回这个新对象。

这个在执行new操作的时候对this的绑定就叫做new绑定。

function fun() {
  this.a = 1;
  this.b = 2;
}
var instance = new fun();
console.log(instance.a);
1.5.箭头函数的this

  ES6中的箭头函数是无法使用以上几种规则的,它是根据外层的作用域来决定this,即取决于外层的函数作用域或全局作用域,而且箭头函数的绑定无法修改,即使是new绑定也不可以。

function foo() {
    return (a) => {
        console.log( this.a );
    }
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var bar = foo.apply(obj1);
bar.apply(obj2);        //2
2.绑定规则的优先级

  前边我们已经说了this的几种绑定规则,当函数调用的位置可以使用多条绑定规则的时候,我们就需要确定这几种规则的优先级。

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 = {};
var bar = foo.bind( obj1 );
bar(2);
console.log( obj1.a );    //2
var baz = new bar(3);
console.log( obj1.a );    //2
console.log( baz.a );    //3

  仔细看这段代码,barfoo绑定到obj1上返回的一个函数,对这个函数进行new操作,并传入新的a值,发现改变的是新对象baz的属性,和obj1已经脱离关系。说明new绑定的优先级高于硬绑定。

  综上所述,我们在遇到this时,如果不是箭头函数,就可以以这种顺序判断它的指向:

如果函数在new中调用,绑定到新建的对象。

函数通过callapply或者硬绑定调用,this绑定到指定的对象上。

函数在某个上下文对象中调用,绑定到这个上下文对象上。

采用默认绑定规则。

  以上就是这篇博客的所有内容了,如果有什么理解的不对的地方欢迎讨论,这里是我的博客以及github,欢迎来访。

参考书籍:《你不知道的JavaScript(上卷)》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88324.html

相关文章

  • JavaScript深入浅出

    摘要:理解的函数基础要搞好深入浅出原型使用原型模型,虽然这经常被当作缺点提及,但是只要善于运用,其实基于原型的继承模型比传统的类继承还要强大。中文指南基本操作指南二继续熟悉的几对方法,包括,,。商业转载请联系作者获得授权,非商业转载请注明出处。 怎样使用 this 因为本人属于伪前端,因此文中只看懂了 8 成左右,希望能够给大家带来帮助....(据说是阿里的前端妹子写的) this 的值到底...

    blair 评论0 收藏0
  • 详解js和jquery里的this关键字

    摘要:出于这个原因,该函数返回的,所以在这里指的是,所以返回的是第一个说明关键字通常在对象的构造函数中使用,用来引用对象。重写无法重写,因为它是一个关键字。结论,表示当前的上下文对象是一个对象,可以调用对象所拥有的属性,方法。 在《javaScript语言精粹》这本书中,把 this 出现的场景分为四类,简单的说就是: 有对象就指向调用对象 没调用对象就指向全局对象 用new构造就指向新对...

    LoftySoul 评论0 收藏0
  • js 中的 call / apply 方法详解和引用类型的继承

    摘要:也就是说当使用后,当前执行上下文中的对象已被替换为,后续执行将以所持有的状态属性继续执行。借用的方法替换的实例去调用相应的方法。实现引用类型的继承其实没有类这一概念,我们平时使用的等严格来说被称作引用类型。 call 方法:object.method.call(targetObj[, argv1, argv2, .....]) apply 方法:object.method.apply(...

    cod7ce 评论0 收藏0
  • JavaScript之this对象详解

    摘要:再来看一个小的示例淘宝腾讯淘宝为什么输出的依然是淘宝呢调用的是对象中的方法,方法里面有一个定时器,而定时器的一个参数是这里的指的就是的对象,然后方法里面有调用了,但是定时器中的指的是对象,所以最终调用的是对象中。 1.看前热身 看一段代码 var name = javascript; var obj = { name:js, foo:f...

    Integ 评论0 收藏0
  • js事件详解

    摘要:使用级方法指定的事件处理程序被认为是元素的方法。用于立即停止事件在中的传播,取消进一步的事件捕获或冒泡。捕获事件目标对象冒泡只有在事件处理程序执行期间,对象才会存在,执行完成后,对象就会被销毁。 引用 事件是我认为前端最特别的地方,这是唯一其他语言不一样的地方,我们通过它与页面进行交互。 事件流 事件流描述的是从页面中接收事件的顺序。IE和网景团队提出流相反的事件流概念。IE事件流是事...

    AlienZHOU 评论0 收藏0
  • js事件详解

    摘要:使用级方法指定的事件处理程序被认为是元素的方法。用于立即停止事件在中的传播,取消进一步的事件捕获或冒泡。捕获事件目标对象冒泡只有在事件处理程序执行期间,对象才会存在,执行完成后,对象就会被销毁。 引用 事件是我认为前端最特别的地方,这是唯一其他语言不一样的地方,我们通过它与页面进行交互。 事件流 事件流描述的是从页面中接收事件的顺序。IE和网景团队提出流相反的事件流概念。IE事件流是事...

    xioqua 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<