felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 之 this 绑定/指向 #48

Open felix-cao opened 6 years ago

felix-cao commented 6 years ago

在主流的面向对象的编程语言中(如 Java, C# 等),this 含义是明确且具体的,即指向当前对象,一般在编译期就绑定了。

JavaScript 中的 this 关键词是一个比较容易混乱的概念,在不同的场景下,this 会指向不同的对象,原因是由于 JavaScript 中的 this 在运行期进行的绑定,这是 JavaScriptthis 关键词具备多重含义的本质原因。

人们对 JavaScriptthis 的绑定常常有两个误解,一是指向函数本身,二是指向函数作用域,这两种想法都是错误的。this 并不指向函数本身,也不指向函数作用域

JavaScript 中的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。

既然 this 是由函数的运行环境决定的,那么我们从函数式编程范式来解读一下,我们知道在JavaScript 中 “函数是一等公民”,函数不仅有自己的特性还具有值的一切特性。详情请移步《JavaScript 为什么说函数是一等公民》

因此 this 有以下4中情况:

下面分别来聊一聊

一、普通函数

JavaScript 中,最常见的函数调用类型就是普通函数调用,也就是“光秃秃”的调用 this 默认指向的是全局对象 window。这条作为默认绑定规则. 详情请移步 《JavaScript 之 this 的默认绑定规则》

function up8Wang() {
  console.log('this: ', this);
  console.log('this.local: ', this.local);
}
var local = "In global";
up8Wang(); // this绑定了window, 输出“In global”

默认绑定里的严格模式

在普通函数调用方式之前加上 'use strict', 则 this 不能绑定到全局对象, 而是指向 undefined

'use strict'
function up8Wang() {
  console.log('this: ', this);
  console.log('this.local: ', this.local);
}
var local = "In global";
up8Wang(); // Uncaught TypeError: Cannot read property 'local' of undefined 

可以看出,在严格模式下,普通函数调用中的 this 实际上指向的是 undefined

二、对象的方法

如果函数有所谓的“落脚点”,即有上下文对象时,隐式绑定规则会把函数中的 this 绑定到这个上下文对象。通俗点讲函数作为对象的一个属性

function getAge() {
  console.log('this.age: ', this.age);
}

var person = {
  age: 18,
  getAge: getAge
}
person.getAge(); // this.age: 18

上面的代码利用对象字面量创建的对象 persongetAge 函数的落脚点, 专业一点的说法就是上下文对象,因此 this 隐式指向 person 对象。这种隐式绑定容易导致隐式丢失,详情请移步《JavaScript 之 this 隐式丢失》

三、构造器及 Object.create

2.1、 new 操作符

通过 new 操作符调用构造器创建一个新对象时, this 绑定这个新对象,关于构造器,请移步阅读 《JavaScript 构造器》 一文,在这篇文章中有段代码,我们拿过来继续分析分析

function Person(name, age) {
  console.log('this: ', this);
  this.name = name;
  this.age = age;
  this.getName = function () {
    console.log(this.name);
  }
}

// 当做构造函数使用时
var person1 = new Person('Felix', 100); // this--> person1
person1.getName(); 

// 当做普通函数使用时
var person2 = Person('Felix Cao', 111); // this->window
getName(); // 或window.getName();
console.log('person2: ' + person2);

上面定义的 Person 函数,既可以作普通函数调用,又可以作构造器调用,

2.2、Object.create

Object.create() 方法是 ECMAScript 5 中新增的方法,这个方法用于创建一个新对象。被创建的对象会继承另一个对象的原型,在创建新对象时还可以指定一些属性

var person = {
  name:'Felix',
  getName: function(){
     console.log(this.name)
  }
}
var person_ch = Object.create(person);
person_ch.name='Felix Cao'
console.log(person_ch.getName()); // 指向新创建的对象 person_ch

四、显示绑定

JavaScript 为每个函数提供三种显示绑定的方法: apply, call, bind, 而bind则返回一个新函数。关于三者的详情,请移步 《JavaScript 函数的 call/apply/bind 方法》

箭头函数中的this绑定

  箭头函数是ES6里一个重要特性,其this规则跟以上四条都不一样,ES6的this遵循着JS的词法作用域, 而不是在运行期进行绑定,即在定义时就确定了指向其当前环境的对象,而不是运行是所在的对象,本篇也不做详细介绍,回头拉出来一篇做专门介绍。