JS中this用法解析

255 阅读6分钟

  博主之前在csdn上面记录过很多次关于js中this的使用方法,但是一直都是断断续续的,直到博主看的一本书,你不知道的javascript才全面理解了this的用法,所以也是特意分享出来,也是自己在读书时做的读书笔记。

  首先可能大部分同学对于this会有一个误解,认为this就是指向自身,在某些情况下,确实是这样的,但是很多情况下却又不是这样,因此如果不清楚this的用法就可能会造成很多想不到的错误,当然我们也不能因为不会用就去避免使用this,this本身被创建出来就是为了提供一种更为优雅的方式,方便复用的。

  在一般情况下,this的绑定规则是由this的调用位置决定的,什么是调用位置,用自己的话来说,就是代码是在什么位置被调用的,而不是在某个位置声明的,为什么说一般情况下,因为还有几种特殊情况,稍后我们会提及到,一般来说this绑定规则有以下四种情况

默认绑定规则,严格模式下绑定到undefined,非严格模式下绑定到全局对象,也就是window 隐式绑定规则,由调用的上下文决定this绑定的对象 显示绑定规则,由call/apply来改变this的绑定 new绑定规则,绑定到由new创建的新对象上   下面我们就一一来说说具体的规则,默认绑定,其实就是我们通常情况下,在函数中使用this,然后直接调用,this就会绑定到默认的全局对象,注意严格模式下,this会绑定到undefined,我们来看一个简单的例子:

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

  是不是非常简单的代码,大家也是肯定能猜到结果了,默认绑定规则下,this会被绑定到全局对象上,也就是window上,还有严格模式下的情况,这里就不细说了。再来看看第二种隐式绑定,所谓隐式绑定,先给大家一个简单的例子

function foo () {
    console.log(this.name);
}
var obj = {
    name: 'uzi',
    foo: foo
}
obj.foo();

  依然非常明显,最后的结果就是‘uzi’,这里调用函数foo有一个上下文的对象,就是这个obj,因此this会被隐式绑定到这个obj对象上,所以这个name属性的值就是‘uzi’,需要注意的地方就是当多次引用这个foo的时候,只有调用foo的上一层的对象才能作为上下文对象被绑定到this上的

function foo (name) {
    console.log(this.name);
}
var obj1 = {
    name: 'uzi',
    foo: foo
}
var obj2 = {
    name: 'xdd',
    obj1: obj1 
}
obj1.obj1.foo();    //'uzi'

  另外还有注意的一点就是隐式绑定是会发生this绑定丢失的情况的,当绑定丢失的时候,this会被绑定到全局作用域或者严格模式下的undefined上

function foo (name) {
    console.log(this.name);
}
var name = 'jkl'
var obj = {
    name: 'uzi',
    foo: foo
}
var bar = obj.foo;    
bar();     //    'jkl'

  这种情况就是发生了绑定丢失,因为此时的bar只是对foo的引用,不包含任何的上下文,因此this指向了全局的作用域。下面说说第三种绑定规则显式绑定,就是用call/apply的方法来改变this的指向,相信了解这两个方法的同学肯定已经是明白了,前端面试中也是会经常问到这个,这里不再详细展开,而要说的另外的补充,我们在使用call/apply的时候,再次调用call/apply的时候,this依然指向第一次绑定的对象无法改变,因此使用他们还是会有一定的局限性,所以书上提到了一种硬绑定的方式,其实就是通过ES5的bind方法来实现的。bind方法会返回一个新的函数,将你指定的参数设置为this的上下文并且调用原来的函数

function foo (name) {
    console.log(this.name, name);
}
var name = 'jkl'
var obj1 = {
    name: 'uzi'
}
var obj2 = {
    name: 'adc'
}
var bar = foo.bind(obj1,'jkl');    
bar();     //    'uzi jkl'
var baz = foo.bind(obj2,'top');
baz();    //    'adc top'

  可以看到硬绑定的bind可以再次应用来改变this,细心的同学应该能想到在react中,constructor构造函数中就使用了bind方法来绑定this的。最后我们提到的就是new绑定,学过java、c#之类的面向对象的语言的同学可能会觉得很熟悉,以为js当中new应该也是和他们一样的,包括博主在内也都是以为是一样的,但是其实这是两种完全不同的机制,js中的new仅仅只是调用函数,而构造函数其实也只是用来被new调用的函数,并不会有实例化一个类的作用,这点还是需要注意的

function foo (name) {
    this.name = name;
}
var bar = new foo('uzi');
console.log(bar.name);

  其实就是这么简单的三句代码,但是我们还是要去研究其中的一些重要的原理,为什么name属性会被绑定到bar上面呢,new的作用也在于此,首先new会创建一个空对象,新对象会执行prototype链,然后将this绑定到这个新对象上,这样我们访问这个新对象的name属性就能得到结果了,注意这里的prototype链比较复杂,就不展开了,后面还涉及了js的行为委托,有兴趣的同学可以找来资料看看。至此一般情况下的this四种绑定情况都说明白了,现在我们就来说说一些例外的情况。

  ES6的箭头函数就是一个例外情况,箭头函数会根据外层的作用域来决定this的指向,一旦绑定就无法修改了,所以一般会用在回调中。另外就是我们有的时候会在call/apply的时候传入一个null,因为我们此时并不关注this的绑定,而是希望预先传入一些参数,此时null的作用就是一个占位符,因为call/apply第一个参数是不能省略的

function foo (a, b) {
    console.log("a:" + a + ",b: " + b);
}
var bar = foo.bind(null, 2);
bar(3);

  这样就得到了我们想要的结果,打印出a,b的值,这种也称作函数柯里化,博主对这个其实也不了解,就不多说了。最后虽然我们知道了this应用的几种方法,但是如果我们同时遇到了这几种情况的话,哪种规则绑定的优先级最高呢,大家也可以自己写一个函数测试一下,最后测试的结果,默认绑定优先级最低,其次隐式绑定,然后硬绑定,最后是new绑定。有些具体的实现细节,博主这里就不细说,需要我们自己去亲自做一下,这样印象也会更加深刻。

  以上是博主读完《你不知道的javascript》自己的一点总结,希望能带给大家一点启发。