你不知道的JavaScript----this解析

目录

关于this

为什么要用this

 this的误解

指向自身

误解二:this指向函数的作用域

this全面解析

调用位置

绑定规则

① 默认绑定

② 隐式绑定

③ 显示绑定

④new绑定

优先级


关于this

比如,我们在同一个方法内使用了一个外部变量a和this,此时,不管该方法被谁调用,这个外部变量a指向的永远都是该方法定义时所在代码块外部的那个变量a(这就是词法作用域)不会改变,但是this不一样,它会因为调用该方法的对象的改变而改变!

其实简单来说,正常情况下,this绑定的是当前方法的调用对象,注意不是定义时的词法作用域,它与词法作用域最大的区别就是:随着调用该方法的对象的不同,this指向的对象也会改变!

为什么要用this

如果说词法作用域是方法与定义时的代码块内外交流的通道,那么this就是方法与被调用对象间的唯一关联,通过词法作用域,我们可以读取使用外部变量,通过this我们可以使用调用对象的属性,方法等!

    function identify() {
            return this.name.toUpperCase();
        }

    function speak() {
            var greeting = "Hello, I'm " + identify.call(this);
            console.log(greeting);
        }

    var me = {
         name: "Kyle"
    };
    var you = {
        name: "Reader"
    };

    identify.call(me); // KYLE
    identify.call(you); // READER

    speak.call(me); // Hello, 我是KYLE
    speak.call(you); // Hello, 我是READER

所以this这个磨人的小妖精,魔力就在于能更优雅的给开发者上下文环境

 this的误解

太拘泥于“this”的字面意思就会产生一些误解。有两种常见的对于this的解释,但是它们都是错误的。

指向自身

    function foo(num) {
        console.log(num);
        this.count++;
    }
    foo.count = 0;

    var i;
    for (i = 0; i < 5; i++) {
        foo(i);
    }
    console.log('count=' + foo.count);  // ->>> 0
    console.log('globalCount=' + window.count);     // ->>> NaN

行之后我们发现在foo函数中分别打印了0,1,2,3,4,按我们“预期”的结果,应该要打印出count = 5,结果遗憾的是0。 事实上当执行foo.count = 0时,函数对象确实被添加了一个count属性,但是函数中的this.count跟这个count并不是同一个。其实是创建了一个全局变量count,值为NaN。

当前执行的方法是window对象下的,执行过程this指向window,而count的自加变成了 undefined + 1,而undefined 在做隐式类型转换会变成NaN,所以值为NaN

误解二:this指向函数的作用域

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

此处视图使用this.a去访问foo的a,但是无法将作用域连接起来。此处this还无法引用作用域内部的东西。

this 和词法作用域是两个不同的、没有交叉的概念,有各自的用途。具体而言,词法作用域是在编写代码时就确定的,this ,是在运行时确定的。运行时想使用某个对象,可以修改 this。

this全面解析

调用位置

寻找调用位置就是寻找“函数被调用的位置”,最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。

 function baz() {
        // 当前调用栈是:baz
        // 因此,当前调用位置是全局作用域
        console.log("baz");
        bar(); // <-- bar的调用位置
    }

    function bar() {

        // 当前调用栈是baz -> bar
        // 因此,当前调用位置在baz中
        console.log("bar");
        foo(); // <-- foo的调用位置
    }

    function foo() {
        // 当前调用栈是baz -> bar -> foo
        // 因此,当前调用位置在bar中

        console.log("foo");
    }

    baz(); // <-- baz的调用位置

在运行代码的时候,CPU计数器会记录当前执行函数的地址及函数返回后要跳转的地址。函数调用会使用call指令,在将函数的入口地址设定到程序计数器之前,call指令会把调用函数后要执行的指令地址存储在主存内的栈中。函数处理完毕后,再通过函数的出口来执行return命令。return命令的功能是把保存在栈中的地址设定到程序计数器中。 

绑定规则

我们来看看在函数的执行过程中调用位置如何决定this的绑定对象。 四种绑定规则:

  • 1.默认绑定(函数被声明的时候默认绑定)
  • 2.隐式绑定(对象引用了函数的时候会隐式绑定到对象上)
  • 3.显示绑定(调用call、apply、bind)
  • 4.new操作符绑定(new执行四个动作)

———————————————扫盲专区——————————

bind,apply,call的区别

  • bind,apply,call都会改变this的指向,区别在于:
  • 1. bind会返回一个改变指向后的函数,不会立即执行;
  • 2. apply,call会立即执行该函数;
  • 3. apply第二个参数传的是个数组(也可以是arguments);
  • 4. call是从第二个参数开始依次传入。

new在构造函数中做了哪些事

  • //1.在内存中创建一个新的对象
  • //2.让this指向这个新对象
  • //3.在构造函数里面的代码,给这个新对象添加属性和方法
  • //4.返回这个新对象 所以构造函数里面一般不需要return

① 默认绑定

this默认绑定为window,我们在全局作用域下调用了foo,找不到指向,默认就是window。需要注意的是:如果开启严格模式,this.name打印的将是undefined。但是严格模式与foo调用的位置无关,还是默认绑定到Window

var name = 'a';
function foo() {
    // "use strict";
    var name = "b";
    console.log(this.name); //a
    console.log(this); //Window
}
foo();

this在非严格模式下,默认指向全局;在严格模式下,默认为undefined;在调用得情况下,谁调用就指向谁。

② 隐式绑定

判断调用的位置是否有上下文对象


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

foo函数本质是不属于obj对象的,而是在obj中作为引用属性。但是当通过obj间接调用foo时,this的指向就发生了变化,此时this就绑定到了obj。如果是直接调用,毋庸置疑,this指向的是全局的。

函数不属于某一个对象,只是说函数被调用时,某个对象引用了它。

如果有多层的引用,那么this指向的就是最近的一层,毕竟远水救不

 function foo() {
    console.log(this.name);
  }
  var obj = {
    name: 'b',
    foo: foo
  }
  var obj2 = {
    name: 'c',
    obj: obj
  }
  obj2.obj.foo()  // b

 ❤❤❤❤❤❤❤-------------隐式丢失-------------❤❤❤❤❤❤

 但是也会出现一种情况,间接引用的时候,很可能会被绑定到全局中

var name = "a";
function foo() {
    console.log(this.name);
}
var obj = {
    name: "b",
    foo: foo
};
var bar = obj.foo;
bar(); // a

上面的var bar = obj.foo引用的是foo函数的本身,不带任何修饰,所以调用bar时候,应该采用默认绑定的方式,即this指向全局(非严格模式)或者undefined(严格模式)。还有一种函数传参的方式也是一样的:

var name = "a";
function foo() {
    console.log(this.name);
}
var obj = {
    name: 'b',
    foo: foo
}
function doAction(func) {
    func()
}
doAction(obj.foo)//a

doAction传参时候,我们知道会有一个赋值操作,即 func = obj.foo,所以和上面一样,如果把函数传入内置函数,比如setTimeOut,还是一样的,没啥区别。

    function foo() {
        console.log(this.a);

    }

    var obj = {
        a: 2,
        foo: foo
    };

    var a = "oops, global"; // a是全局对象的属性

    setTimeout(obj.foo, 100); // "oops, global"

函数执行的时候,看它前面有没有点,如果有,点前面是谁,this就是谁,如果没有,就是window,间接引用的时候,很可能会被绑定到全局中

③ 显示绑定

改变this指向,在文章开头的时候就提到过了,apply和call都可以实现

我们可以理解为硬要绑定,强制绑定,如下例子:

var name = "a";
var obj = { name: "b" };
function foo() {
    console.log(this.name);
}
var bar = function() {
    foo.call(obj);
};
bar(); //b
setTimeout(bar, 100); //b
bar.call(window);  //b

bar函数内部为foo强制绑定了this得指向,即obj。不管如何调用bar,到最后都要经过这一步的操作,所以this指向无法得到修改,一直都是指向obj。

❤❤❤❤❤❤应用场景❤❤❤❤❤❤

通过指定this的指向,包裹函数,传入参数进行操作,或者可以使用辅助函数

function foo(sth) {
    return this.age + sth;
}
var obj = { age:2 };
var bar = function() {
    return foo.apply( obj, arguments ); // 第二个参数是所有参数的数组
};
var b = bar( 3 );
console.log( b ); // 5

API调用的,一些第三方库,可以支持上下文的参数,确保回调函数使用我们指定的this


function foo(el) {
    console.log( el, this.name );
}
var obj = { name: "guoguo" };
[1, 2, 3].forEach( foo, obj );
// 1 guoguo 2 guoguo 3 guoguo

数组中每个元素都会被当作foo的一个参数,逐个调用,并且在forEach的第二个参数中指定了this的指向

④new绑定

当我们使用new来调用对象函数时,会复制一份里面所有用this点的属性到新的对象中,因此新的对象就可以访问属性了

function foo(a) {
    var b = a + 1;
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a, bar.b); // 2, undefined

注意点:

当this遇到了return,如果返回的是一个对象,那么this就是指向那个对象,否则还是原来的。返回undefined也不会改变指向。但是有一个特殊情况:null虽然是对象,但是还是指向原来的。

function foo(a){
    this.a = a
    return {} // undefined
    // return function(){} undefined
    // return null 1
    // return undefined 1
}
var func = new foo(1)
console.log(func.a)

把基本数据类型转换为对应的引用类型的操作称为装箱,把引用类型转换为基本的数据类型称为拆箱。

优先级

  • ① new 绑定的,this 绑定新创建的对象。
  • ② call、apply显式绑定或者硬绑定调用的,this绑定指定的对象。
  • ③ 隐式绑定的,this 绑定上下文对象。
  • ④优先级最低,默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。

这是是一些例外:————————

①当把null, undefined作为this的绑定对象传入call,apply,bind,实际应用的是默认的绑定规则

②当遇上间接引用的时候,调用这个函数会应用默认绑定规则

    var a = 2;
    function foo() {
        console.log(this.a);
    }
    var o = { a: 3, foo: foo };
    var p = { a: 4 };
    o.foo(); // 3 隐式绑定
    (p.foo = o.foo)(); // 2 使用全局的

③ 当this遇上了箭头函数 

分析:foo函数内部的箭头函数会保留调用foo时候的this指向,此处则为obj1,而且指向无法被修改,所以就算硬绑定了obj2也没用。

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

箭头函数的this是根据外层函数决定的,如果外层还是还是一个箭头函数,就再往外层,直到全局

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值