JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)

时间:2023-02-23 12:31:59

this是什么?

this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

调用位置与调用栈:

调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

下面我们来看看到底什么是调用栈和调用位置:

function baz() {
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的调用位置
} function bar() {
// 当前调用栈是baz -> bar
// 因此,当前调用位置在baz 中
console.log( "bar" );
foo(); // <-- foo 的调用位置
} function foo() {
// 当前调用栈是baz -> bar -> foo
// 因此,当前调用位置在bar 中
console.log( "foo" );
} baz(); // <-- baz 的调用位置

this绑定规则

1. 默认绑定:
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2

上述代码中,函数调用时应用了this 的默认绑定,因此this 指向全局对象(非严格模式下)。

那么我们怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看看foo() 是如何调用的。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此this 会绑定到undefined:

function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined

这里有一个微妙但是非常重要的细节,虽然this 的绑定规则完全取决于调用位置,但是只有foo() 运行在非strict mode 下时,默认绑定才能绑定到全局对象;严格模式下与foo()的调用位置无关:

function foo() {
console.log( this.a );
}
var a = 2; (function(){
"use strict";
foo(); // 2
})();
2. 隐式绑定:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
}; obj.foo(); // 2

当foo() 被调用时,它的落脚点指向obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this 绑定到这个上下文对象。因为调用foo() 时this 被绑定到obj,因此this.a 和obj.a 是一样的。

对象属性引用链中只有最顶层或者说最后一层会影响调用位置

#######隐式丢失

一个最常见的this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this 绑定到全局对象或者undefined 上,取决于是否是严格模式。

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

虽然bar 是obj.foo 的一个引用,但是实际上,它引用的是foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

3. 显式绑定:

就像我们刚才看到的那样,在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this 间接(隐式)绑定到这个对象上。那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?

JavaScript 中的“所有”函数都有一些有用的特性(这和它们的[[ 原型]] 有关——之后我们会详细介绍原型),可以用来解决这个问题。具体点说,可以使用函数的call(..) 和apply(..) 方法。严格来说,JavaScript 的宿主环境有时会提供一些非常特殊的函数,它们并没有这两个方法。但是这样的函数非常罕见,JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用call(..) 和apply(..) 方法。

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

通过foo.call(..),我们可以在调用foo 时强制把它的this 绑定到obj 上。

显式绑定仍然无法解决我们之前提出的丢失绑定问题,通过以下方法可以解决:

硬绑定

	function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的bar 不可能再修改它的this
bar.call( window ); // 2
4. new绑定:

使用new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。

  2. 这个新对象会被执行[[ 原型]] 连接。

  3. 这个新对象会绑定到函数调用的this。

  4. 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。

     function foo(a) {
    this.a = a;
    }
    var bar = new foo(2);
    console.log( bar.a ); // 2

优先级: new绑定>显式绑定>隐式绑定

判断this:

  1. 函数是否在new 中调用(new 绑定)?如果是的话this 绑定的是新创建的对象。

    var bar = new foo()

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

    var bar = foo.call(obj2)

  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。

    var bar = obj1.foo()

  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

    var bar = foo()

绑定例外

1. 被忽略的this

把null 或者undefined 作为this 的绑定对象传入call、apply 或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
更安全的ths

一种“更安全”的做法是传入一个特殊的对象,把this 绑定到这个对象不会对你的程序产生任何副作用。。就像网络(以及军队)一样,我们可以创建一个“DMZ”(demilitarized zone,非军事区)对象——它就是一个空的非委托的对象。

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的DMZ 空对象
var ø = Object.create( null ); //此处的∅不会创建Object.prototype 这个委托,所以它比{}“更空”
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3

2. 间接引用

另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。

function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2

3. 软绑定(此处不讨论)

this词法(箭头函数的this)

function foo() {
// 返回一个箭头函数
return (a) => {
//this 继承自foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是3 !

foo() 内部创建的箭头函数会捕获调用时foo() 的this。由于foo() 的this 绑定到obj1,bar(引用箭头函数)的this 也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)

JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)的更多相关文章

  1. JavaScript 中的事件类型5(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  2. JavaScript 中的事件类型4(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  3. JavaScript 中的事件类型3(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  4. JavaScript 中的事件类型2(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件: UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  5. JavaScript 中的事件类型1(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  6. 【转载】MDX Step by Step 读书笔记&lpar;三&rpar; - Understanding Tuples &lpar;理解元组&rpar;

    1. 在 Analysis Service 分析服务中,Cube (多维数据集) 是以一个多维数据空间来呈现的.在Cube 中,每一个纬度的属性层次结构都形成了一个轴.沿着这个轴,在属性层次结构上的每 ...

  7. JavaScript中的对象与原型—你不知道的JavaScript上卷读书笔记(四)

    一.对象 对象可以通过两种形式定义:声明(文字)形式和构造形式.即: var myObj = { key: value // ... }; 或: var myObj = new Object(); m ...

  8. JS闭包—你不知道的JavaScript上卷读书笔记(二)

    关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...

  9. JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)

    前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...

随机推荐

  1. &lbrack;Machine Learning &amp&semi; Algorithm&rsqb; 随机森林(Random Forest)

    1 什么是随机森林? 作为新兴起的.高度灵活的一种机器学习算法,随机森林(Random Forest,简称RF)拥有广泛的应用前景,从市场营销到医疗保健保险,既可以用来做市场营销模拟的建模,统计客户来 ...

  2. Method threw &&num;39&semi;org&period;hibernate&period;exception&period;SQLGrammarException&&num;39&semi; exception&period; Cannot evaluate com&period;hotel&period;Object&lowbar;&dollar;&dollar;&lowbar;jvst485&lowbar;15&period;toString&lpar;&rpar;

    数据库字段和类Object属性不匹配,Method threw 'org.hibernate.exception.SQLGrammarException' exception. Cannot eval ...

  3. 如何添加或删除ubuntu用户和组【转】

    转自:http://blog.csdn.net/sin90lzc/article/details/7525670 在创建用户时,需要为新建用户指定一用户组,如果不指定其用户所属的工作组,自动会生成一个 ...

  4. toB的产品经理和toc产品经理区别

    腾讯产品经理现身说法 曾经在UC做过2年to c的app,现在在腾讯做to b的产品. 做to c产品的时候,我很瞧不起做to b产品的同学,认为他们不过是做支撑的. 后来,我参与了一个to b平台级 ...

  5. 学习java之泛型类和泛型方法

    上一篇博文中我自己试着用了下泛型类,昨天看java编程思想一书,发现里面有这么一段话: 使用参数化方法而不是用参数化类的方便之处在于:你不必为需要应用的每种不同类型都使用一个参数去实例化这个类,并且你 ...

  6. Codeforces 444C DZY Loves Colors&lpar;线段树&rpar;

    题目大意:Codeforces 444C DZY Loves Colors 题目大意:两种操作,1是改动区间上l到r上面德值为x,2是询问l到r区间总的改动值. 解题思路:线段树模板题. #inclu ...

  7. &lbrack;js高手之路&rsqb;Vue2&period;0基于vue-cli&plus;webpack同级组件之间的通信教程

    我们接着上文继续,本文我们讲解兄弟组件的通信,项目结构还是跟上文一样. 在src/assets目录下建立文件EventHandler.js,该文件的作用在于给同级组件之间传递事件 EventHandl ...

  8. vue开发小结&lpar;下&rpar;

    前言 继前几天总结了vue开发小结(上)后,发现还有很多的点没有能列举出来,于是还是打算新建一个下篇,再补充一些vue开发中需要注意的细节,确实还是都是细节的问题,我只是在这里强调下,希望对大家有帮助 ...

  9. java多线程读一个变量需要加锁吗?

    如果只是读操作,没有写操作,则可以不用加锁,此种情形下,建议变量加上final关键字: 如果有写操作,但是变量的写操作跟当前的值无关联,且与其他的变量也无关联,则可考虑变量加上volatile关键字, ...

  10. 我的Java学习推荐书目

    一直有这么个想法,列一下我个人认为在学习和使用Java过程中可以推荐一读的书籍,给初学者或者想深入的朋友一些建议,帮助成长.推荐的的都是我自己读过,也会推荐一些朋友读过并且口碑不错的书籍. 一.基础类 ...