this 关键字是 Javascript 中很特别的一个关键字,被自动定义在所有函数的作用域中。this提供了一种更优雅的方式隐式“传递”一个对象的引用。今天就来说说 this 的指向问题。

this 是在运行时,也就是说函数被调用时进行绑定,而不是在编写时进行绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数在哪里被调用。

一、调用位置

所谓 调用位置 就是 函数被调用的位置。找到这个位置并不是很简单的事情,因为有的时候,真正的调用位置会被隐藏起来。

这里要做的就是分析调用栈,即为了到达执行位置所调用的所有函数,我们关心的调用位置就在当前正在执行的函数的前一个调用中。

举个例子:

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

那么知道调用位置之后,我们来看看 它是如何决定 this 的绑定对象的。

二、绑定规则

1、默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则的默认规则。

例子:

 function test(){
console.log(this.a);
}
var a = 2;
test(); //

声明在全局作用域中的变量(比如var a=2)就是全局对象的一个同名属性,即浏览器环境下的 window.a=2;

接下来,我们可以看到调用 test() 时,this.a 被解析成了全局变量 a,为什么呢?因为在这个例子中,test()是直接使用不带任何修饰的函数引用进行调用的,应用了默认规则,this 指向了全局对象。

这里要注意一个问题,如果使用了严格模式(strict mode),则不能将全局对象用于默认绑定,因此 this 会绑定到 undefined。

例子:

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

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

例子:

 function test(){
console.log(this.a)
}
var a =2;
(function(){
"use strict";
foo(); //
})()

比较一下上面的两个例子,发现不同 strict mode 中 this 应用默认绑定的差别。

2、隐式绑定

另一条需要考虑的规则是调用位置是否有上下文,或者说是否被某个对象拥有或者包含。

例子:

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

当 test() 被调用时,它的前面加上了对 obj 的引用。当函数引用上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。在本例中, test() 调用时 this 被绑定到 obj,因此 this.a 即 obj.a。

在隐式绑定中需要注意两点:

a、对象属性引用链中只有上一层或者说最后一层在调用中起作用。

例子:

 function test(){
console.log(this.a)
} var obj = {
a:2,
test:test
}; var obj_1 = {
a:4,
obj:obj
} obj_1.obj.test(); //

b、隐式丢失。隐式绑定的函数丢失绑定对象,即应用默认绑定,从而把 this 绑定到全局对象上或者是 undefined(如果是严格模式)。

例子:

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

这里的 foo 实际上 引用的是 test 函数本身,当在执行 var foo = obj.test; 时,就把 obj.test 对 test 的引用地址复制给了 foo,也就是说 foo 和 obj.test 指向同一个 引用地址。所以 调用 foo 其实是一个不带任何修饰的函数调用,应用了默认绑定。关于复制的引用这里要是还是不是很明白 可以看一下,http://www.cnblogs.com/lijiayi/p/jsdeeepcopy.html 中的第一部分基本类型 和 引用类型。

3、显示绑定

这里主要就是指通过 call() 、apply() 的函数调用,实现的绑定方式。

它们的第一个参数是一个对象,是给 this 准备的,接着在调用函数时将其绑定到 this。因为你可以直接指定 this 的绑定对象,所以就叫显示绑定。

例子:

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

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

这里多说一点:如果你传入了一个原始值(字符串类型,布尔值型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String() 、new Boolean() 或者 new Number() )。这通常被称为 “装箱”。

在显示绑定中需要注意一点:

显示绑定中 call apply 或者 bind 中传入 null 或者 undefined ,这些值在调用时会被忽略,实际应用的是默认绑定。

例子:

 function test(){
console.log(this.a)
}
var a = 2;
test.call(null); //

你或许会想,我怎么会传个 null ?

一种非常常见的做法是 使用 apply 来 “展开” 数组,并当作参数传入一个函数。

例子:

 function test(a,b){
console.log("a:"+ a + ",b:" + b)
}
foo.apply(null,[2,3]); // a:2,b:3

另一种做法 用bind 可以对参数进行柯里化(预先设置一些参数)

例子:

 function test(a,b){
console.log("a:"+ a + ",b:" + b)
} var bar = test.bind(null,2);
bar(3) // a:2,b:3

以上两种方法都需要传入一个参数当作 this 的绑定对象,但如果函数并不关系 this 的话,你仍然需要一个占用值,这时 null 可能是一个不错的选择。

4、new 绑定

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

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

b、这个新对象会被执行 [[prototype]] 连接。

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

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

例子:

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

使用 new 来调用 test() 时 ,我们会构造一个新对象并把它绑定到 test() 调用中的 this 上。

三、优先级

现在你已经知道 this 绑定的四条规则,那么当某个调用位置可以应用多个规则怎么办?

在这里我们说一下,上面四条规则的优先级:

new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

总结:当我们在碰到 this 的时候先找到调用位置,然后按照以下顺序应用调用规则:

1、函数是否在 new 中调用,即应用的 new 绑定?如果是的话 this 绑定的是新创建的对象。

var bar = new test();

2、函数是否通过 call 和 apply 调用,即应用 显示绑定?如果是的话 this 绑定的是指定的对象。

test.call(obj)

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

obj.test()

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

test()

四、es6 中的 this

在 es 6 中 使用了一种无法应用上面这些规则的特殊函数类型:箭头函数。

箭头函数不是使用 function 关键字定义的,而是使用被称为 “胖箭头” 的操作符 => 定义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。
例子:

 function test(){
return (a)=>{
console.log(this.a)
}
} var obj1 = {
a:2
} var obj2 = {
a:4
} var bar = test.call(obj1);
bar.call(obj2); // 2,不是4!

test 内部创建的箭头函数会捕获调用时 test() 的 this。由于 test 的 this 绑定到了 obj 1,bar(引用箭头函数) 的 this 也会绑定到 obj 1 ,箭头函数的绑定无法被修改(new也不行)。箭头函数会继承外层函数调用的 this 绑定。

引用并感谢:

你不知道的JavaScript(上卷) (炒鸡推荐大家看)

【 js 基础 】【读书笔记】关于this的更多相关文章

  1. JS基础知识笔记

    2020-04-15 JS基础知识笔记 // new Boolean()传入的值与if判断一样 var test=new Boolean(); console.log(test); // false ...

  2. 【js】【读书笔记】廖雪峰的js教程读书笔记

    最近在看廖雪峰的js教程,重温了下js基础,记下一些笔记,好记性不如烂笔头嘛 编写代码尽量使用严格模式 use strict JavaScript引擎是一个事件驱动的执行引擎,代码总是以单线程执行 执 ...

  3. handlebars.js基础学习笔记

    最近在帮学校做个课程网站,就有人推荐用jquery+ajax+handlebars做网站前端,刚接触发现挺高大上的,于是就把一些基础学习笔记记录下来啦. 1.引用文件: jquery.js文件下载:h ...

  4. 两万字Vue.js基础学习笔记

    Vue.js学习笔记 目录 Vue.js学习笔记 ES6语法 1.不一样的变量声明:const和let 2.模板字符串 3.箭头函数(Arrow Functions) 4. 函数的参数默认值 5.Sp ...

  5. js高程读书笔记(1-3章)

    一.js简介 js是一种专为与网页交互而设计的脚本语言,由以下三个不同的部分组成: 1.ECMAScript,由ECMA-262(它规定了语言的这些组成部分:语法,类型,语句,关键字,保留字,操作符, ...

  6. JS高程读书笔记-第一、二章-内附在线思维导图和quizlet卡片

    之前在kindle上买了高程,今天又到了纸质的<JavaScript语言精粹>,<高性能JavaScript>,<JavaScipt设计模式>,开始读书之旅啦. 我 ...

  7. javascript-智能社-JS基础A笔记

    JavaScript基础A JavaScript组成 ECMA : 全称ECMAScript,解释器.计算机语言的翻译 DOM:全称Document Object Model,赋予了JS操作HTML的 ...

  8. Javascript进阶篇——(JS基础语法)笔记整理

    根据慕课网学习整理到一起的笔记,把东西整理到一起看起来比较方便 什么是变量字面意思:变量是可变的量:编程角度:变量是用于存储某种/某些数值的存储器.我们可以把变量看做一个盒子,盒子用来存放物品,物品可 ...

  9. 两万字Vue.js基础学习笔记(二)

    Vue.js学习笔记(二) 4.模块化开发 ES6模块化的导入和导出 我们使用export指令导出了模块对外提供的接口,下面我们就可以通过import命令来加载对应的这个模块了 首先,我们需要在HTM ...

  10. node.js 基础学习笔记3 -express

    1.工作原理 当通过app.js建立的服务器时,会看到一个简单的页面.返回页面时,浏览器会向服务器发送请求.app会解析请求的路径,调用相应的逻辑,调用对应的视图模板,传递对象数值,最终生成HTML页 ...

随机推荐

  1. Class和普通js构造函数的区别

    Class 在语法上更加贴合面向对象的写法 Class 实现继承更加易读.易理解 更易于写 java 等后端语言的使用 本质还是语法糖,使用 prototype Class语法 typeof Math ...

  2. Office 2010激活 NO KMS products detected问题

    今天用office2010激活工具Office 2010 Toolkit激活安装的office2010时悲剧的遇到了这个问题,如下图: (这张图是从网上找的,不过和我遇到的问题是一样的). 然后上网搜 ...

  3. 使用bash echo 输出回车转义

    输出回车 [root@~]# echo -e 'hello\n'hello 回车去掉 [root@~]# echo -n hello hello[root@~]#

  4. C#枚举中使用Flags特性

    .NET中的枚举我们一般有两种用法,一是表示唯一的元素序列:还有就是用来表示多种复合的状态.这个时候一般需要为枚举加上[Flags]特性标记为位域,这样我们就可以用"或"运算符组合 ...

  5. 08-03 java 继承

    继承格式,优缺点,概述: /* 继承概述: 把多个类中相同的内容给提取出来定义到一个类中. 如何实现继承呢? Java提供了关键字:extends 格式: class 子类名 extends 父类名 ...

  6. Python动态变量名定义与调用

    动态变量名赋值 在使用tkinter时需要动态生成变量,如动态生成var1...var10变量 使用exec动态赋值 exec在python3中是内置函数,它支持python代码的动态执行. 示例: ...

  7. SpringBoot用@ConfigurationProperties获取配置文件值

    SpringBoot的配置文件有yml和properties两种,看一些文章说yml以数据为中心,比较好.个人觉得properties更好用,所以这里以properties格式为例来说. 我们都知道@ ...

  8. Postman入门

  9. translate和position的比较

    有很多css属性可以影响元素定位,比如float,margin,padding,position,translate().表面上来看,position:relatative和transform:tra ...

  10. CentOS7 下安装 iSCSI Target(tgt) ,使用 Ceph rbd

    目录 一.iSCSI 介绍 1. iSCSI 定义 2. 几种常见的 iSCSI Target 3. 优缺点比较 二.安装步骤 1. 关闭防火墙 2. 关闭selinux 3. 通过 yum 安装 t ...