关于this你需要知道的

this是什么

在函数调用的时候,会创建一个活动对象(active object,有时也称为执行上下文)。这个记录会包括函数在哪里被调用(调用栈)函数的调用方法传入的参数等信息this就负责记录其中的一个属性,会在函数函数调用的过程中用到。

this解决了什么问题

  1. 记录当前执行上下文信息,如调用栈、函数的调用方式、传入的参数。
  2. 改变this执行可以让不具备某个方法的对象,调用该方法(即:隐式’传递’一个对象的引用);

this取决于什么?

  1. this在函数调用的时候发生绑定,指向完全取决于函数在哪里执行。
  2. 始终坚持一个原理:this 永远指向最后调用它的那个对象this 永远指向最后调用它的那个对象this 永远指向最后调用它的那个对象

来个测试题看看你对this了解情况:
栗子1:

var number = 5;
var obj = {
    number: 3,
    fn1: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
复制代码

误解指向自身

栗子2:

function countor(num){
    console.log('countor',num)
    //记录foo被调用的次数
    this.count ++;
}
countor.count = 0;
var i = 0;
for(i = 0; i < 10 ; i++) {
    if(i > 5) {
        countor(i)
    }
}
// foo:6 
// foo:7
// foo:8 
// foo:9
console.log(countor.count) // 0 
复制代码
  • 为什么countor明明调用了4次但是值还是0?

  • 如果希望countor可以具备记录函数的调用次数有哪些解决方案?

方法一:

function countor(num){
    console.log('countor',num);
    countor.count ++; 
}
countor.count = 0;
for(i = 0 ;i < 10; i++){
    if(i > 5 ){
        countor(i)
    }
}
console.log(countor.count)
复制代码

方法二:

function countor(num){
    console.log('countor',num);
    data.count ++;
}
var data = {
    count: 0
}
var i ;
for(i = 0 ;i < 10; i++){
    if(i > 5 ){
        countor(i)
    }
}
console.log(data.count)
复制代码

虽然解决了问题,但是避开了this

通过直接将 countor.count的执行上下文直接指向当前的函数即 直接调用 countor.count 替代this,但还是避开了this

方案三:

function countor() {
    console.log('countor',num)
    //记录foo被调用的次数
    this.count ++;
}
countor.count = 0;
var i = 0;
for(i = 0; i < 10 ; i++) {
    if(i > 5) {
        //直接改变函数调用时的上文,将this指向 countor本身
        countor.bind(countor,i)
    }
}
复制代码

上面的案例说明了一个误区;

  • this在任何情况下都不指向函数的词法作用域

现在我们来回答为什么countor明明调用了4次但是值还是0?

    1. 从上面的3个解决方案中可以得知,全局函数的执行上下文不是指向函数本身而是window
    1. 可以通过改变函数调用时的this指向来改变函数执行上下文

this的调用位置

image.png

this的指向始终坚持一个原理:this永远指向最后调用它的那个对象this永远指向最后调用它的那个对象this永远指向最后调用它的那个对象重要的事情说三遍。

根据不同的调用位置,确定使用下面4种绑定规则的哪一种:

绑定规则

默认绑定、隐式绑定、显式绑定、new绑定

默认绑定

全局方法默认绑定为window,严格模式,不能将全局对象用于默认绑定,因此this会绑定到undefined

1.1 题目一
function foo() {
    console.log(this.a);
}
var a = 2;
复制代码
1.2 题目二
function foo(){
    'use strict';
    ocnsole.log(this.a);
}
var a =2;
foo(); 
复制代码

如果改为调用的地方为’use strict’呢?

function foo() {
    console.log(this.a);
}
var a = 2;
(function(){
    'use strict';
    foo()
})()
复制代码

结论:默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)

隐式绑定

2.1 题目一
function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
}
obj.foo();
复制代码
2.1 题目二

如果引用2层呢?

function foo() {
    console.log(this.a);
}
var obj2 = {
    a:2,
    foo:foo
}
var obj1 = {
    a:2,
    obj2:obj2
}
obj1.obj2.foo()
复制代码

隐式丢失:常见在回调函数中

为什么隐式绑定的this会丢失?

3.1 题目一
function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
}
var bar = obj.foo;
var a = "global";
bar(); 
复制代码

由于foo.bar引用的是函数本身,bar()调用的位置在全局,所以this.a输出 global

下面这个栗子更微妙、更出乎意料

3.2 题目二
function foo() {
    console.log(this.a);
}
function doFoo(fn){
    fn();
}
var obj = {
    a:'local',
    foo:foo
}
var a = "global";
doFoo(obj.foo)
复制代码
3.3 题目三

现在我们不用window调用doFoo,而是放在对象obj2里,用obj2调用:

function foo(){
    console.log(this.a);
}
function doFoo(fn){
    console.log(this);
    fn();
}
var obj = {a:"local",foo};
var a = "global";
var obj2 = {a:"obj2", doFoo};

ojb2.doFoo(obj.foo);
复制代码
3.4 题目四
var length = 10;
function fn () {
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function (fn) {
        console.log(this.length)
        fn();
        arguments[0]();
    }
};
obj.method(fn, 1);
复制代码

为什么arguments[0]()的值是2呢?

显示绑定

  • 通过call()或者apply()bind()方法直接指定this的绑定对象, 如foo.call(obj)

使用显示绑定有三点需要注意:

  1. 使用 .call()、apply() 的函数会直接执行
  2. 使用 .bind() 会创建一个新的函数,需要手动调用才能执行
  3. .call() 、.apply()用法基本类似,不过call接受若干个参数,apply接受一个数组。
4.1 题目一
function foo() {
    console.log(this.a);
}
var obj = {a: 1};
var a = 2;
foo();
foo.call(obj);
foo.apply(obj);
foo.bind(obj);
复制代码

第四个foo,原因是因为bind创建了一个新函数需要用变量接收并调用,因此此处不会执行。

注意:如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数

function foo() {
    console.log(this.a);
}
var a = "global";
foo.call();
foo.call(null);
foo.call(undefined);
复制代码

知道显示绑定后,我们来看一个它的妙用

4.2 题目二
var obj1 = {
    a:1
}
var obj2 = {
    a:2,
    foo1:function(){
        console.log(this.a);
    }
    foo2:function() {
        console.log(this);
        setTimeout( function (){
            console.log(this);
        },0)
    }
}
var a = "global";
obj2.foo1();
obj2.foo2();
复制代码
如果希望定时器里this指向 obj1 如何改造?
4.3 题目三
setTimeout(function(){
    console.log(this);
}.call(obj1),0)

// 只需要在上例的回调函里面 .bind(obj1)
复制代码

所以有小伙伴就会问了,我下面的这种写法不可以吗?

obj2.foo2.bind(obj1)
复制代码

注意⚠️:这种写法实际上是改变了foo2函数内部的this, 而 setTimeout里的函数thisfoo2函数里面的this是没有关系的,定时器调用里面的this始终都是window

4.4 题目四

OK?,我们不用定时器,把它干掉,换成一个函数:

var obj1 = {
    a:"obj1"
}
var obj2 = {
    a:"obj2",
    foo1:function() {
        console.log(this.a);
    },
    foo2:function() {
        function inner() {
            console.log(this);
            console.log(this.a);
        }
        inner()
    }
}
var a = 3;
obj2.foo1();
obj2.foo2()
复制代码

如果将 inner() 改为显示绑定呢?

inner.call(obj1)
复制代码
4.5 题目五

看看下题会输出什么?

function foo() {
    console.log(this.a);
}
var obj = {
    a:1
}
var a = "global";
foo();
foo.call(obj);
foo().call(obj)

复制代码

注意⚠️:此处会报错Uncaught TypeError: Cannot read property 'call' of undefined,因为 call必须要被函数调用。

那如果函数foo里面返回一个函数呢?

4.6 题目六
function foo() {
    console.log(this.a);
    return function() {
        console.log(this.a)
    }
}
var obj = {
    a:1
}
var a = "global";
foo();
foo.call(obj);
foo().call(obj);
复制代码

如果把上面的call换成bind会如何?

4.7 题目七
function foo() {
    console.log(this.a);
    return function () {
        console.log(this.a)
    }
}
var obj = {
    a:1
}
var a = "global";
foo();
foo.bind(obj1);
foo().bind(obj)
复制代码

注意⚠️:foo.bind(obj) 不会执行,因为返回的新的函数需要变量接收并调用才可以。

4.8 题目八

函数内层的this与函数外层的this有关系?内层this到底指向谁?我们重要的口诀再来一遍:this由最后一个调用它的对象决定

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

foo.call(obj)()
复制代码

如果将上面的函数返回函数放入对象中呢?

4.9 题目九
var obj = {
    a:"obj",
    foo:function() {
        console.log('foo',this.a);
        return function() {
            console.log('inner',this.a)
        }
    }
}
var obj2 = { a: "obj2"};
var a = 2
obj.foo();
obj.foo.call(obj2)();
obj.foo().call(obj2)
复制代码

加个参数玩玩

4.10 题目十
var obj = {
    a:1,
    foo:function(b) {
        b = b || this.a;
        return function(c) {
            console.log(this.a + b + c)
        }
    }
}
var a = 2;
var obj2 = { a: 3};
obj.foo(a).call(obj2,1);
obj.foo.call(obj2)(1)
复制代码

new绑定

new做了什么?

function myNew (Person) {
    const context = Object.create(Person);
    let result = context.call(context);
    if(typeof result !== 'null' && (typeof result === 'object' || typeof result === 'function')){
        return result;
    }else {
        return context;
    }
}
复制代码

总结4句话:

  • new一个新对象,
  • 新对象原型指向构造函数
  • 并将this指向创建的新对象
  • 函数如果没有返回其他对象,则返回新创建的对象,如果构造函数返回的为对象或者函数,则将该对象或函数返回
new绑定的优先级问题
5.1 题目一
function foo() {
    console.log(this.a);
}
var obj1 = {
    a:2,
    foo:foo
}
var obj2 = {
    a:3,
    foo:foo
}
obj1.foo.call(obj2);
obj2.foo.call(obj1);
复制代码

可以看到显示绑定优先级更高

下面看看new绑定隐式绑定优先级

5.2 题目二
function foo(something){
    this.a = something;
}
var obj1 = {
    foo:foo
}
var obj2 = {};

obj1.foo(1);
console.log(obj1.a);

obj1.foo.call(obj2,3);
console.log(obj2.a);

var bar = new obj1.foo(4);
console.log(obj1.a);
console.log(bar.a);
复制代码

可以看出new绑定比隐式绑定优先级更高,现在我们看看 new绑定与显示绑定哪个优先级更高??

5.3 题目三

由于我们无法 通过 new foo.call(obj1)测试,所以我们间接通过硬绑定实现

function foo(something){
    this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);

var baz = new bar(3);
console.log(baz.a);
console.log(bar.a);
复制代码

new bar并没有像我们预计的那样把obj1.a修改为3.
bind的内部实现:会判断是否被new 调用,如果是的话就会使用新创建的this替换硬绑定的this

5.4 题目四
复制代码

总结,判断this:

根据优先级来判断函数在某个调用位置应该用的是哪条规则,可以按照下面的顺序来判断

  • 函数是否在new中调用(new绑定)?如果是this绑定就是新创建的对象。var bar = new foo():

  • 函数是通过call、apply、(显示绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

  • 函数是否在某个上下文中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。var bar = obj1.foo();

  • 如果都不是的话,使用默认绑定,在非严格模式下绑定到undefined,否则绑定到全局对象 var var = foo();

箭头函数

  • 对于上面的问题,this永远指向最后一个绑定它的对象,但是对于箭头函数就不一样了。

  • 箭头函数的this 由外层的作用域决定的,指向定义时的this,而非执行时。

它里面的this由外层的作用域决定的是什么意思呢?

箭头函数中没有this的,必须通过查找作用域链来决定它的值,如果箭头函数被非箭头函数包含,则this就指向最近的一层非箭头函数的this, 否则this为undefined

6.1 题目一
function obj = {
    name:"obj",
    foo1:() => {
        console.log(this.name);
    },
    foo2:function (){
        console.log(this.name);
        return => {
            console.log(this.name);
        }
    }
}
var name = "galbol";
obj.foo1();
obj.foo2()();
复制代码

解题:

  • 对于obj.foo1()函数的调用,它的外层作用域是window,对象obj当然不属于作用域,(作用域只有全局作用域与函数创建的局部作用域),所以输出为 galbol

6.2 题目2

总结一下箭头函数需要注意的点吧:

  • 它里面的this由外层来决定,且指向函数定义时的this而非执行时

  • 字面量创建的对象,作用域是window,如果里面有箭头函数是属性的话,this指向的是window

  • 构造函数创建的对象,作用域可以理解为这个构造函数,且这个构造函数this是指向新创建的对象

  • 箭头函数里面的this是无法通过bind、apply、call来修改的,但是可以通过改变作用域中的this指向来间接修改。

综合栗子

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn();
}
Hi.call(person, person.sayHi); 

复制代码

改变setTimeout的thsi

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this)
      console.log(this.a)
    }.call(obj1), 0)
  }
}
var a = 3
obj2.foo1()
obj2.foo2()

复制代码

参考阅读

《你不知道的js二部分》

40题搞懂this

第一个案例不懂

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享