资讯专栏INFORMATION COLUMN

关于 this 你想知道的一切都在这里

Lemon_95 / 3173人阅读

摘要:如下在第一个例子中,被点击元素是通过,这个形式参数来代替的。它的作用和形式参数类似,其本质上是一个对象的引用,它的特殊性在于不需要手动传值,所以使用起来会更加简单和方便。

无论在 javascript 的日常使用中还是前端面试过程中,this 的出镜率都极高。这无疑说明了,this 的重要性。但是 this 非常灵活,导致很多人觉得 this 的行为难以理解。本文从为什么要有 this 作为切入点,总结了 this 的六大规则,希望能帮助你解答困惑。

简介

this 实际上相当于一个参数,这个参数可能是开发中手动传入的,也可能是 JS 或者第三方传入的。
这个参数,通常指向的是函数执行时的“拥有者”。this 的机制,可以让函数设计的更加简洁,并且复用性更好。

this 是在函数执行时进行绑定的,绑定规则一共六条,分别是:

new 绑定:使用 new 关键字创建对象时,this 会绑定到创建的对象上。

显式绑定:使用 callapplybind 方法显式绑定时, this 为其第一个参数。

隐式绑定:当函数挂在对象上执行时,系统会隐式地将 this 绑定到该对象上。

默认绑定:当函数独立执行时,严格模式 this 的默认绑定值为 undefined,否则为全局对象。

箭头函数绑定:使用箭头函数时,this的绑定值等于其外层的普通函数(或者全局对象本身)的this

系统或第三方绑定:当函数作为参数,传入系统或者第三方提供的接口时,传入函数中的 this 是由系统或者第三方绑定的。

this 的作用

this 的机制提供了一个优雅的方式,隐式地传递一个对象,这可以让函数设计的更加简洁,并且复用性更好。

考虑下面一个例子,有两个按钮,点击后将其背景改为红色。

function changeBackgroundColor(ele) {
  ele.style.backgroundColor = "red";
}

btn1.addEventListener("click",function () {
  changeBackgroundColor(btn1);
});
btn2.addEventListener("click",function () {
  changeBackgroundColor(btn2);
});

在这里,我们显式地将被点击的元素传递给了 changeBackgroundColor 函数。但实际上,这里可以利用 this 隐式传递上下文的特点,直接在函数获取当前被点击的元素。如下:

function changeBackgroundColor() {
    this.style.backgroundColor = "red";
}

btn1.addEventListener("click",changeBackgroundColor);
btn2.addEventListener("click",changeBackgroundColor);

在第一个例子中,被点击元素是通过 ele ,这个形式参数来代替的。而在第二个例子中,是通过一个特殊的关键字 this 来代替。this 它的作用和形式参数类似,其本质上是一个对象的引用,它的特殊性在于不需要手动传值,所以使用起来会更加简单和方便。

六大规则

在实际使用中, this 究竟指向哪个对象是最令人困惑的。本文归类了六类情景,总结六条 this 的绑定规则。

new 绑定

使用 new 创建对象的时候,类中的 this 指的是什么?

class Person {
  constructor(name){
    this.name = name;
  }

  getThis(){
    return this
  }
}


const xiaoMing = new Person("小明");

console.log(xiaoMing.getThis() === xiaoMing); // true
console.log(xiaoMing.getThis() === Person); // false
console.log(xiaoMing.name === "小明"); // true

在上面例子中,使用了 ES6 的语法创建了 Person 类。在使用 new 关键字创建对象的过程中,this 会由系统自动绑定到创建的对象上,也就是 xiaoMing

规则一:在使用 new 关键字创建对象时,this 会绑定到创建的对象上。

显式绑定

情景二,使用 callapplybind 方法,显式绑定 this 参数。

call 为例,call 方法的第一个传入的参数,是 this 引用的对象。

function foo() {
  console.log( this === obj ); // true
  console.log( this.a === 2 ); // true
}

const obj = {
  a: 2
};

foo.call( obj );

在显式传递的情况下,this 指向的对象很明显,就是 callapplybind 方法的第一个参数。

规则二:使用 callapplybind 方法显式绑定时, this 为其第一个参数。

隐式绑定

隐式绑定和显式绑定不同的地方在于,显式绑定由开发者来指定 this;而隐式绑定时,函数或方法都会有一个“拥有者”,这个“拥有者”指的是直接调用的函数或方法对象。

例一

先看一个最简单的例子。

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

const obj = {
  foo: function () {
    console.log( this === obj );
  },
  bar: bar
};

obj.foo(); // true
obj.bar(); // true

函数 foo 是直接挂在对象 obj 里面的,函数 bar 是在外面定义的,然后挂在对象 obj 上的。无论函数是在何处定义,但最后函数调用时,它的“拥有者”是 obj。所以 this 指向的是函数调用时的“拥有者” obj

例二

为了更加深入的理解,再考虑函数重新赋值到新的对象上的情况,来看看下面的例子。

function bar() {
  console.log( this === obj1 ); // false
  console.log( this === obj2 ); // true
}

const obj1 = {
  foo: function () {
    console.log( this === obj1 ); // false
    console.log( this === obj2 ); // true
  },
  bar: bar
};

const obj2 = {
  foo: obj1.foo,
  bar: obj1.bar
};

obj2.foo();
obj2.bar();

在该例子中,将 obj1 中的 foobar 方法赋值给了 obj2。函数调用时,“拥有者”是 obj2,而不是 obj1。所以 this 指向的是 obj2

例三

对象可以多层嵌套,在这种情况下执行函数,函数的“拥有者”是谁呢?

const obj1 = {
  obj2: {
    foo: function foo() {
      console.log( this === obj1 );      // false
      console.log( this === obj1.obj2 ); // true
    }
  }
};

obj1.obj2.foo()

foo 方法/函数中的直接调用者是 obj2,而不是 obj1,所以函数的“拥有者”指向的是离它最近的直接调用者。

例四

如果一个方法/函数,在它的直接对象上调用执行,又同时执行了 call 方法,那么它是属于隐式绑定还是显式绑定呢?

const obj1 = {
  a: 1,
  foo: function () {
    console.log(this === obj1); // false
    console.log(this === obj2); // true
    console.log(this.a === 2);  // true
  }
};

const obj2 = {
  a: 2
};

obj1.foo.call(obj2); // true

由上,可以看出,如果显式绑定存在,它就不可能属于隐式绑定。

规则三:如果函数是挂在对象上执行的,这个时候系统会隐式的将 this 绑定为函数执行时的“拥有者”。

默认绑定

前一小段,讨论了函数作为对象的方法执行时的情况。本小段,要讨论的是,函数独立执行的情况。

在函数直接调用的情况下,this 绑定的行为,称之为默认绑定。

例一

为了简单起见,先讨论在浏览器的非严格模式的下绑定行为。

function foo() {
  console.log( this === window); // true
}

foo();

在上面的例子中,系统将 window 默认地绑定到函数的 this 上。

例二

在这里,先介绍一种我们可能会在代码中见到的显式绑定 null 的写法。

function foo() {
  console.log( this == window ); // true
}

foo.apply(null);

将例一默认绑定的情况,改为了显式绑定 null 的情况。

在实际开发中,我们可能会用到 apply 方法,并在第一个参数传入 null 值,第二个参数传入数组的方式来传递数组类型的参数。这是一种传统的写法,当然现在可以用 ES6 的写法来代替,但是这不在本文的讨论范围内。

在本例最需要关注的是,this 竟然指向的 window 而不是 null。个人测试的结果是,在函数独立调用时,或者显式调用,传入的值为 nullundefined 的情况下,会将 window 默认绑定到 this 上。

在函数多次调用,形成了一个调用栈的情况下,默认绑定的规则也是成立的。

例三

接着,探讨下严格模式下,this 的默认绑定的值。

"use strict";

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

foo();               // true
foo.call(undefined); // true
foo.call(null);      // false

在严格模式下,this 的默认绑定的值为 undefined

规则四:在函数独立执行的情况下,严格模式 this 的默认绑定值为 undefined,否则默认绑定的值为 window

箭头函数绑定

箭头函数实际上,只是一个语法糖,实际上箭头函数中的 this 实际上是其外层函数(或者 window/global 本身)中的 this

// ES6
function foo() {
  setTimeout(() => {
    console.log(this === obj); // true
  }, 100);
}

const obj = {
  a : 1
}

foo.call(obj);

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log(_this === obj); // true
  }, 100);
}

var obj = {
  a : 1
}

foo.call(obj);

规则五:使用箭头函数时,this 的绑定值和其外层的普通函数(或者 window/global 本身) this 绑定值相同。

系统或第三方绑定

在 JavaScript 中,函数是第一公民,可以将函数以值的方式,传入任何系统或者第三方提供的函数中。现在讨论,最后一种情况。当将函数作为值,传入系统函数或者第三方函数中时,this 究竟是如何绑定的。

我们在文章一开始提到的,两个按钮例子,系统自动将 this 绑定为点击的按钮。

function changeBackgroundColor() {
    console.log(this === btn1); // true
}

btn1.addEventListener("click",changeBackgroundColor);

接着测试系统提供的 setTimeout 接口在浏览器和 node 中绑定行为。

// 浏览器
setTimeout(function () {
  console.log(this === window); // true
},0)

// node
setTimeout(function () {
  console.log(this === global); // false
  console.log(this); // Timeout
},0)

很神奇的是,setTimeout 在 node 和浏览器中的绑定行为不一致。如果我们将 node 的中的 this 打印出来,会发现它绑定是一个 Timeout 对象。

如果是第三发提供的接口,情况会更加复杂。因为在其内部,会将什么值绑定到传入的函数的 this 上,事先是不知道的,除非查看文档或者源码。

系统或者第三方,在其内部,可能会使用前面的五种规则一种或多种规则,对传入函数的 this 进行绑定。所以,规则六,实际上一条在由前五条规则上衍生出来的规则。

规则六:调用系统或者第三方提供的接口时,传入函数中的 this 是由系统或者第三方绑定的。

参考文章:

You-Dont-Know-JS

The this keyword

MDN this

后期补充

查完规范后,用伪代码再总结一下。

规范地址:

Construct:http://www.ecma-international...
Function Objects:http://www.ecma-international...
Function Calls:http://www.ecma-international...
ArrowFunction:http://www.ecma-international...

伪代码

if (`newObj = new Object()`) {
  this = newObj
} else if (`bind/call/apply(thisArgument,...)`) {
  if (`use strict`) {
    this = thisArgument
  } else {
    if (thisArgument == null || thisArgument == undefined) {
      this = window || global
    } else {
      this = ToObject(thisArgument)
    }
  }
} else if (`Function Call`) {
  if (`obj.foo()`) {
    // base value . Reference = base value + reference name + strict reference
    // 例外: super.render(obj).  this = childObj ?
    this = obj 
  } else if (`foo()`) {
    // 例外: with statement. this = with object
   
    this = `use strict` ? undefined : window || global
  }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81254.html

相关文章

  • [译] 你想知道关于 JavaScript 作用域一切

    摘要:原文链接原文作者你想知道的关于作用域的一切译中有许多章节是关于的但是对于初学者来说甚至是一些有经验的开发者这些有关作用域的章节既不直接也不容易理解这篇文章的目的就是为了帮助那些想更深一步学习了解作用域的开发者尤其是当他们听到一些关于作用域的 原文链接: Everything you wanted to know about JavaScript scope原文作者: Todd Mott...

    Flands 评论0 收藏0
  • 箭头函数你想知道都在这里

    摘要:没有箭头函数没有自己的对象,这不一定是件坏事,因为箭头函数可以访问外围函数的对象那如果我们就是要访问箭头函数的参数呢你可以通过命名参数或者参数的形式访问参数不能通过关键字调用函数有两个内部方法和。 1、基本语法回顾 我们先来回顾下箭头函数的基本语法。ES6 增加了箭头函数: var f = v => v; // 等同于 var f = function (v) { return ...

    xiaoqibTn 评论0 收藏0
  • 关于正则表达式,你想知道一切

    摘要:实例化一个对象实例化时,构造器接受的第一个参数就是正则表达式的内容,为类型。那么当我们在创建一个正则表达式的过程中,要加上以上的的话,定义如下使用字面量时直接把跟在正则表达式后面可以给多个实例化一个时作为构造器的第二个参数,类型。 这里只讲在JavaScript中的正则表达式 1: 如何创建一个正则表达式 在JS中有 2 种方式创建一个正则表达式: 1: 通过正则表达式字面量 co...

    wayneli 评论0 收藏0
  • 关于 setTimeout 与 setInterval,你需要知道一切

    摘要:这里是结论,将是更惊艳的那一个。浏览器隔一段时间像服务器发送一个请求,询问这里有没有需要更新的消息。在响应回来时,才会继续发出第二个请求。但是,显然的,这对我们要做的事来说并不算是什么问题。 我们都知道的是setTimout是用来延迟一个简单的动作的,然而,setInterval的目的是用来重复执行某个动作的。 然后,以上只是一半的事实。因为如果一个函数需要在一个间隔时间内重复的执行,...

    rottengeek 评论0 收藏0
  • 掌握 HTTP 缓存——从请求到响应过程一切(下)

    摘要:上篇文章掌握缓存从请求到响应过程的一切上我们讨论了关于利用头来解决缓存问题,这篇文章我们将介绍缓存和之间的关系。文件将会被缓存起来,这时如果你想让你的新文件起作用,那么用最新的版本号命名它就可以。缓存不同供应商清除缓存的方式不一样。 作者:Ulrich Kautz 编译:胡子大哈 翻译原文:http://huziketang.com/blog/posts/detail?postId=...

    Gilbertat 评论0 收藏0

发表评论

0条评论

Lemon_95

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<