美文网首页Vue3.0+TS
彻底搞懂this指向

彻底搞懂this指向

作者: 望穿秋水小作坊 | 来源:发表于2021-12-08 09:27 被阅读0次

一、初识this

1、this的定义是什么?它的复杂之处体现在哪里?

  • 【this的定义】this是JavaScript中的一个关键字,this会在执行上下文中绑定一个对象。
  • 【复杂之处】this是根据什么条件绑定的对象呢?在不同的执行条件下会绑定不同的对象,这也是让人捉摸不定的地方。

2、对比其他面向对象语言中的this,JavaScript中的this有什么不同?

  • 在常见的面向对象的编程语言中,比如Java、C++、Swift、Dart等等一些列语言中,this通常只会出现在类的方法中
  • 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
  • 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。

3、既然this这么复杂,我们不用this会有什么影响吗?

  • 不使用this的代码
var obj = {
  name: "why",
  running: function() {
    console.log(obj.name + " running");
  },
  eating: function() {
    console.log(obj.name + " eating");
  },
  studying: function() {
    console.log(obj.name + " studying");
  }
}
  • 这么做有一个很大的弊端,如果我将obj名称换成了info,那么方法中的obj都需要换成info
  • 我们都会使用this来进行优化:
var obj = {
  name: "why",
  running: function() {
    console.log(this.name + " running");
  },
  eating: function() {
    console.log(this.name + " eating");
  },
  studying: function() {
    console.log(this.name + " studying");
  }
}
  • this让我们进行一些API设计时,代码更加的简洁和易于复用。

4、在全局作用域下,浏览器中的this指向什么?

  • 所以,在全局作用域下,我们可以认为this就是指向的window
  • 但是,开发中很少直接在全局作用域下去使用this,通常都是在函数中使用
console.log(this); // window

var name = "why";
console.log(this.name); // why
console.log(window.name); // why

5、思考下面代码的打印?

// 定义一个函数
function foo() {
  console.log(this);
}

// 1.调用方式一: 直接调用
foo(); 

// 2.调用方式二: 将foo放到一个对象中,再调用
var obj = {
  name: "why",
  foo: foo
}
obj.foo() 

// 3.调用方式三: 通过call/apply调用
foo.call("abc"); 
  • 【方式一】打印 【window】
  • 【方式二】打印 【obj对象】
  • 【方式三】打印 【String {"abc"}对象】

6、上面例子可以给我们什么样的启示呢?

  • ①函数在调用,JavaScript会默认给this绑定一个值;
  • ②this的绑定和定义的位置(编码位置)没有关系;
  • ③this的绑定和调用方式以及调用的位置有关系;
  • ④this是在运行时被绑定的;

二、this绑定规则?

我们现在已经知道this无非就是在函数调用时被绑定的一个对象,我们就需要知道它在不同的场景下的绑定规则即可。

1、默认绑定是什么?

  • 【是什么】独立函数调用,我们可以理解成函数没有被绑定到某个对象上进行调用;

  • 案例一

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

foo();
  • 案例二
// 2.案例二:
function test1() {
  console.log(this); 
  test2();
}

function test2() {
  console.log(this); 
  test3()
}

function test3() {
  console.log(this); 
}
test1();
  • 案例三
function foo(func) {
  func()
}

function bar() {
  console.log(this); 
}

foo(bar);
  • 案例四
function foo(func) {
  func()
}

var obj = {
  name: "why",
  bar: function() {
    console.log(this); 
  }
}

foo(obj.bar);
  • 上面案例一、二、三、四中打印结果都是 【window】,你能理解吗?

2、隐式绑定是什么?

  • 【是什么】就是在函数调用的位置中,是通过某个对象发起的函数调用。

  • 案例一

function foo() {
  console.log(this); // obj对象
}

var obj1 = {
  name: "why",
  foo: foo
}

obj.foo();
  • 案例二
function foo() {
  console.log(this); // obj对象
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  obj1: obj1
}

obj2.obj1.foo();
  • 案例三
function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}

// 讲obj1的foo赋值给bar
var bar = obj1.foo;
bar();
  • 案例一、二打印【obj1】;案例三打印【window】

3、显式绑定是什么?

  • 【是什么】借助apply、call、bind等函数,对this进行明确的绑定。

  • 案例一

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

foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number对象,存放时123
  • 案例二
function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

var bar = foo.bind(obj);

bar(); // obj对象
bar(); // obj对象
bar(); // obj对象

4、new绑定是什么?

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数时,会执行如下操作:

  • ①创建一个全新的对象;
  • ②这个新对象会被执行Prototype连接;
  • ③这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
  • ④如果函数没有返回其他对象,表达式会返回这个新对象;
// 创建Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "why"}
}

var p = new Person("why");
console.log(p);

5、上述四种方式的优先级顺序是什么?

  • new绑定 > 显示绑定(bind) > 隐式绑定 > 默认绑定

三、箭头函数的this

1、什么是箭头函数?

  • 【箭头函数】是ES6中新增的一个非常好用的函数类型。
  • 核心写法如下:
()=> {
}

2、箭头函数的this遵守上面的四种绑定规则吗?

  • 【箭头函数】不使用this的四种标准规则(也就是不绑定this)
  • 【箭头函数】根据外层作用域来决定this。

3、箭头函数的一些案例

  • 案例一:没有箭头函数之前,我们常见的写法
var obj = {
  data: [],
  getData: function() {
    var _this = this;
    setTimeout(function() {
      // 模拟获取到的数据
      var res = ["abc", "cba", "nba"];
      _this.data.push(...res);
    }, 1000);
  }
}

obj.getData();
  • 案例二:使用箭头函数后
var obj = {
  data: [],
  getData: function() {
    setTimeout(() => {
      // 模拟获取到的数据
      var res = ["abc", "cba", "nba"];
      this.data.push(...res);
    }, 1000);
  }
}

obj.getData();
  • 案例三:如果getData也是一个箭头函数,那么setTimeout中的回调函数中的this指向谁呢?
var obj = {
  data: [],
  getData: () => {
    setTimeout(() => {
      console.log(this); // window
    }, 1000);
  }
}

obj.getData();

四、经典题目演练

1、面试题一
var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)(); 
}
sayName();
  • 代码解析
function sayName() {
  var sss = person.sayName;
  // 独立函数调用,没有和任何对象关联
  sss(); // window
  // 关联
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}

2、面试题二

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); 
person1.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
  • 代码解析
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2

// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window

// 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2

// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1

3、面试题三

var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
  • 代码解析
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2

// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1

// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2

// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1

4、面试题四

var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)

person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
  • 代码解析
// obj.foo1()返回一个函数
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1()() // window
// 最终还是拿到一个返回的函数(虽然多了一步call的绑定)
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

// 拿到foo2()的返回值,是一个箭头函数
// 箭头函数在执行时找上层作用域下的this,就是obj
person1.obj.foo2()() // obj
// foo2()的返回值,依然是箭头函数,但是在执行foo2时绑定了person2
// 箭头函数在执行时找上层作用域下的this,找到的是person2
person1.obj.foo2.call(person2)() // person2
// foo2()的返回值,依然是箭头函数
// 箭头函数通过call调用是不会绑定this,所以找上层作用域下的this是obj
person1.obj.foo2().call(person2) // obj

文章来源:https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA

相关文章

网友评论

    本文标题:彻底搞懂this指向

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