资讯专栏INFORMATION COLUMN

《You Don't Know JS》阅读理解——this

tianren124 / 2080人阅读

摘要:运行规则根据的运作原理,我们可以看到,的值和调用栈通过哪些函数的调用运行到调用当前函数的过程以及如何被调用有关。

1. this的诞生

假设我们有一个speak函数,通过this的运行机制,当使用不同的方法调用它时,我们可以灵活的输出不同的name。

var me = {name: "me"};

function speak() {
  console.log(this.name);
}

speak.call(me) //me

但是如果没有this, 这时我们需要显示的传递上下文给该函数。这时必须硬性的指定上下文,代码的复杂度增加,灵活性也欠缺。

function speak(context) {
  console.log(context.name);
}
2. this的运行机制 2.1 运行原理

When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function"s execution.

当函数被调用时, 函数会创建一个activation object(执行上下文), 这个对象包括了函数在哪里被调用(调用栈),函数的调用方式,传入的参数,以及this值。

因此,我们可以看到,this值是在函数调用时赋值的,而不是在声明的时候。是动态的。

2.2 运行规则

根据this的运作原理,我们可以看到,this的值和调用栈(通过哪些函数的调用运行到调用当前函数的过程)以及如何被调用有关。

2.2.1 Default Binding(默认绑定)

当函数是被独立调用时,this值在非严格模式下为全局对象, 严格模式下为undefined.

var a = 1;
function foo() {
  var a = 2;
  console.log(this.a);
}

function bar() {
  debuuger;
  foo();
}

bar();

打开chrome devtool可以看到,在调用foo时,函数的调用栈为bar -> foo,调用方式是独立调用,且是在非严格模式下,此时this值指向window,输出1。

2.2.2 Implicit Binding(隐式绑定)
var = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo
}

obj.foo(); //2

此时,调用foo时,函数前加上了对obj这个对象的引用,输出obj.a

因此,如果有上下文对象引用了函数,隐式绑定规则会指定this值为该引用对象。

但是我们再看看下面这种情况。要注意的是,bar的值是对函数foo的引用,因此此时foo的调用并没有上下文对象的引用,因此应用的是default binding, 输出1。要注意这种赋值的情况。

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

var bar = obj.foo;

bar(); //1
2.2.3 Explicit Binding(显式绑定)

上面两种情况,要么this值为全局对象(非严格模式),要么通过对象方法调用,this指向调用的对象。
那我想不通过对象调用,而是独立调用时又能指定this值为某个对象呢?这时,call,apply就诞生了。它的第一个参数是this值,帮助我们明确指定函数调用时this的值。

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

foo.call(obj); //2

通过call, apply,我们可以在调用时明确指定this值。还有一种情况是,有时候我们希望this值绑定在我们给定的对象上,而函数只需要接受一些参数。特别是在第三方库中,它会提供一种方法,接收方法需要的参数,但是不希望你意外的修改了方法的this值,这时它可能会采用bind这种硬性绑定的方法明确的指出this值。

在ES5中提供了Function.prototype.bind,它的应用场景就是帮助你predicable的绑定this值。常用的应用场景为包裹函数、事件绑定函数、setTimeout中绑定this

//包裹函数,用来接受参数
function multiple(num) {
  console.log(this.pen, num);
  return this.pen * num;
}

var priceMapping = {
  pen: 10
}

function calTotalPrices() {
  return multiple.apply(priceMapping, arguments);
}

var total = calTotalPrices(3);
console.log(total); //30
//事件绑定
var states = {
  clickCount: 0
}
function clickHandler() {
  this.clickCount++;
  console.log(this.clickCount);
}
button.addEventListener("click", clickHandler.bind(states));

注意:当使用显示绑定时,如果第一个参数是null, undefined,则应用默认绑定规则。为避免传入null, undefined时错误的改变了全局值,最好创建一个空对象代替null, undefined

var ø = Object.create(null);

foo.call(ø);
2.2.4 new Binding(new绑定)

明白new的运作原理:

创建一个新对象;

对象链接到[[prototype]]上;

this绑定到这个新对象上;

有显式的return,返回return,否则返回这个新对象。

2.2.5 优先级

new > 显示绑定(call,apply,bind) > 隐式绑定(方法调用) > 默认绑定(独立函数调用)

function foo(sth) {
  this.b = sth;
  console.log("a:", this.a, "b:", this.b);
}

var a = "window";
var obj1 = {
  a: "obj1",
  foo: foo,
}

var obj2 = {
  a: "obj2",
  foo: foo,
}

obj1.foo("obj1"); //a: obj1 b: obj1
obj1.foo.call(obj2, "obj2"); //a: obj2 b: obj2; 显示 > 隐式
var bar = foo.bind(obj1);
new bar("new"); //new > 显示
4. 箭头函数

箭头函数中的this并不适用于以上四种规则。因为这里的this不是使用的传统this机制,而是使用的词法作用域,根据外层的作用域来决定this。应用机制不一样,该this也不能通过显示绑定来修改。

5. 总结

下一次再看到this的时候,我们问自己两个问题:

where to call: 函数的调用位置是?

how to call: 函数的调用方法是?应用的规则是?

应用规则4条(按优先级排序):

new (新创建的对象)

显式绑定 (绑定到指定对象,call, apply, bind:可预测的this);

隐式绑定 (调用的上下文对象,注意间接引用的错误);

默认绑定 (全局对象或undefined注意函数体是否为严格模式);

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

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

相关文章

  • 读书笔记(you don't know js): this的理解(没写完...)

    摘要:基本概念首先,函数不能存储的值,指向哪里,取决于调用它的对象。如果没有这个对象,那默认就是调用非严格模式下。也就是说是在运行的时候定义的,不是在绑定的时候定义的。 基本概念 首先,函数不能存储this的值,this指向哪里,取决于调用它的对象。如果没有这个对象,那默认就是window调用(非严格模式下)。也就是说this是在运行的时候定义的,不是在绑定的时候定义的。 funct...

    freewolf 评论0 收藏0
  • You Don't Know JS阅读理解——作用域

    摘要:在我们的程序中有很多变量标识符,我们现在或者将来将使用它。当我们使用时,如果并没有找到这个变量,在非严格模式下,程序会默认帮我们在全局创建一个变量。词法作用域也就是说,变量的作用域就是他声明的时候的作用域。 作用域 定义 首先我们来想想作用域是用来干什么的。在我们的程序中有很多变量(标识符identifier),我们现在或者将来将使用它。那么多变量,我咋知道我有没有声明或者定义过他呢,...

    codeKK 评论0 收藏0
  • You don't know cross-origin

    摘要:为什么会存在跨域问题同源策略由于出于安全考虑,浏览器规定不能操作其他域下的页面,不能接受其他域下的请求不只是,引用非同域下的字体文件,还有引用非同域下的图片,也被同源策略所约束只要协议域名端口有一者不同,就被视为非同域。 showImg(https://segmentfault.com/img/remote/1460000017093859?w=1115&h=366); Why 为什么...

    hersion 评论0 收藏0
  • [翻译]You Don't Know JS: this & Object Prot

    摘要:引用是从匿名函数内部引用自身的唯一方法,不过,最好的方法是避免使用匿名函数,至少在那些需要引用自身的时候,使用命名函数或者表达式。 [翻译]Chapter1 this or that 第一次翻译,翻译的不好,已经再尽全力s去翻译了,如果哪里看不明点,请出门左转下边原文地址 英文原文点击这里 javascript中最令人困惑的东西就是this关键字,它在每个函数作用域中都会自动定义的一个...

    mingzhong 评论0 收藏0
  • You Don't Know Js 阅读笔记

    摘要:回调传递函数是将函数当做值并作为参数传递给函数。这个例子中就是因为事件绑定机制中的传入了回调函数,产生了闭包,引用着所在的作用域,所以此处的数据无法从内存中释放。 javascript作用域 一门语言需要一套设计良好的规则来存储变量,并且之后可以方便的找到这些变量,这逃规则被称为作用域。 这也意味着当我们访问一个变量的时候,决定这个变量能否访问到的依据就是这个作用域。 一、词法作用域 ...

    wanglu1209 评论0 收藏0

发表评论

0条评论

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