讲清楚之 javascript中的this
讲清楚之 javascript中的this
这一节来探讨this。 在 javascript 中 this 也是一个神的存在,相对于 java 等语言在编译阶段确定,而在 javascript 中, this 是动态绑定,也就是在运行期绑定的。这导致了 javascript 中 this 的灵活性,而且对识别对象不同的调用场景下 this 指向带来了一些困扰。
在全局环境中this
指向window
,即this === window
。this 的灵活性主要体现在函数环境中,容易判定出错的也是函数环境下的 this 指向.
this 是函数内部的可见属性之一(另一个是 arguments), 在函数内部我们可以直接使用this
访问指定对象
的属性。那么指定对象
是如何确定的?
下面就围绕 this 的指向对象
来梳理
this 是怎么确定的?
首先 this 是在函数被调用时确定的, 也就是在进入函数后,函数内表达式、语句执行前的变量对象
创建阶段确定的。
而它指向什么取决于函数在哪里被调用(在什么对象上被调用).
一般情形需要关注函数在哪里被调用、被怎么调用.
下面主要分析函数在不同调用场景下this
的指向.
当函数作为方法被对象拥有并调用时 this 指向该对象,否则 this 为 undefind
把标题展开描述就是: 当函数调用时是被某一个对象所拥有,函数内的
this
将绑定到该对象上。如果函数是独立调用的,则函数内部的 this 在严格模式下为 undefind, 在非严格模式下 this 会指向 window(node.js中指向global)。
根据上面的原则,我们首要判断的是函数被谁
所拥有,举几个栗子更好理解:
栗子1:
let a = 1
function foo () {
console.log(this.a)
}
foo() // 1
foo() 是在全局环境下独立调用的,此时函数 foo 被全局对象拥有(this 指向 window),所以this.a
获取的是 window 全局对象下面的 a.
栗子2:
var a = 1
var foo = function (){
console.log(this.a)
}
var too = {
a: 2,
b: foo
}
var bar = too.b
foo() // 1
too.b() // 2
bar() // 1
函数执行时确定 this 指向的大致逻辑:
foo()
:
- 在全局对象 window 下调用,所以输出1。
too.b()
:
- 对象 too 的属性 b 指向函数 foo,此时函数 foo 是 对象 too 内部的一个方法;
too.b()
执行时,b 是被对象 too调用的,此时内部的 this 指向 对象 too;- 所以
this.a
获取的是too.a
,输出2;
bar()
:
- 对象 too 的方法被赋值给 bar, 即此时 bar 标识符同 foo 标识符一样都指向同一个栈地址所代表的函数。
- 执行
bar()
此时在全局对下 window 下调用,所以输出1。
栗子3:
var a = 1
var foo = function () {
console.log(this.a)
}
var too = function (fn) {
var a = 2
fn()
}
too(foo) // 1
too(foo)
:这里函数 foo 作为参数被传递到函数 too的内部执行, fn()
执行时并没有被其他对象显示拥有,所以我们隐式的判定fn()
是在全局对象 window 下面执行的,所以输出 1 。
这个栗子很容易搞错(我自己感觉每过一段时间再看还是会错o(︶︿︶)o),第一印象输出的应该是2,那是应为把 this 与作用域链弄混淆了。始终要记住作用域链是在源代码编码阶段就确定了,而 this 是在函数运行阶段才确定,属于
执行上下文
的概念,是在运行阶段依据this
所在函数被谁调用来确定的。
我们再来把上面的栗子稍微修改一下
栗子4:
var a = 1
var foo = function () {
console.log(this.a) // 输出 1
console.log(a) // 区别在这里, 输出 1
}
var too = function (fn) {
var a = 2
fn()
}
too(foo) // 1, 1
表达式 | - | - | 值 |
---|---|---|---|
console.log(this.a) |
基于上下文 this | 表达式所属 foo 函数在 too 函数内调用时 this 指向 window | 1 |
console.log(a) |
基于作用域链 | 全局上下文中的变量 a 在 foo 函数作用域链上 | 1 |
不知道你理解了没有,这个栗子也体现了上下文
与作用域
在进行变量/属性查找时的区别
栗子5:
let c = 1
let foo = {
c: 2
}
let too = function () {
console.log(this.c)
}
foo.a = {
b: too,
c: 3
}
foo.a.b() // 3
this
的绑定只受最靠近的成员引用的影响。foo.a.b()
函数b
作为对象foo.a
的方法被调用,所以此时的this
绑定到foo.a
。b
与对象foo
的包含成员没有多大关系,最靠近的对象才决定this
的绑定。
最后console.log(this.c)
取到的是foo.a
里c
的值 3 .
栗子5:
let a = 1
let foo = {
a: 2,
msg: function () {
console.log(`hi, ${this.a}`)
}
}
let too = Object.create(foo)
too.msg() // hi, 2
上面用对象foo
作为原型创建新对象too
, 所以对象 too 继承对象 foo 的所有属性、方法。too.msg()
执行时,msg 函数被 too 调用,此时this
就指向对象too
, 所以console.log(
hi, ${this.a})
访问的是从对象foo
继承来的 a.
所以对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么对象实例的this
指向的是调用这个方法的对象,通过this
可以访问到原形链上的方法。
通过上面的几个栗子验证了我们的总结:
当函数作为对象方法调用时 this 指向该对象,作为函数独立调用时 this 指向全局对象 window (严格模式下 this 为 undefind )。
大部分时候依据上面的原则来判断 this 的指向是没有问题,但是 this 还有以下几种特殊场景需要注意。
当函数作为构造函数调用,此时函数内部的 this 指向函数自身
函数也是对象
栗子:
function Foo (a) {
this.a = a // 实例化后 this 指向 too
}
let too = new Foo(1)
我们知道函数 this 在运行期确定,而构造函数实例化时在内部其实是为我们创建了一个新的对象,通过一系列的操作后将 this 指向了这个新对象。
new
操作符执行时的逻辑推导如下:
- 创建一个新的空对象;
- 将构造函数的 this 指向这个新对象;
- 将构造函数的原形添加到新对象的原形链里,将属性、方法挂载在新对象上;
- 返回这个新对象
返回的新对象就是我们实例化的对象。即, new 操作符调用构造函数时,this 是指向内部创建的新对象,最后会将新创建的对象返回给实例变量。
所以当函数作为构造函数调用,则函数内部的 this 绑定到该函数上。在通过构造函数实例化对象时,对象内部的 this 也同样指向该实例对象。
当函数使用 cal、apply 方法调用执行,此时 this 指向 call、apply 方法传入的对象
在 javascript 里函数也是对象。 所有函数都继承于 Function 构造函数,而 call、apply 是 Function.prototype 原形的方法,所以函数都从原形的原形里继承了 call、apply 方法。
call、apply 用于向函数注入 this 对象和变量(call 与 apply 的区别在于传递的参数不一样,其他没有区别)。
let a = 1
let too = {
a: 2
}
function foo () {
console.log(this.a)
}
foo.call(too) // 2
函数 foo 里面的 this 此时指向call
传递进来的对象 too,所以this.a
打印的是 2.
如果传递给 this
的值不是一个对象,JavaScript 会尝试使用内部 ToObject
操作将其转换为对象。因此,如果传递的值是一个原始值比如 7
或 'foo'
,那么就会使用相关构造函数将它转换为对象,所以原始值 7
会被转换为对象: new Number(7)
,而字符串 'foo'
转化成 new String('foo')
。
function bar() {
console.log(this, Object.prototype.toString.call(this));
}
//原始值 7 被隐式转换为对象
bar.call(7); // Number {[[PrimitiveValue]]: 7}__proto__: Number[[PrimitiveValue]]: 7 "[object Number]"
ECMAScript 5 引入了 Function.prototype.bind
。当函数调用f.bind(someObject)
会创建一个与f
具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
对象someObject
。
栗子:
let a = 1
let too1 = {
a: 2
}
function foo () {
console.log(this.a)
}
let bar = foo.bind(too1)
let bar2 = {
a: 4,
b: bar
}
bar() // 2
bar.call({a: 3}) // 2
bar2.b() // 2
当foo
通过bind
创建一个新函数时,新函数的this
强制绑定到了传入的对象too1
, 后续执行中bar
即使是作为对象方法调用还是使用 call、apply 都无法替换使用 bind 绑定的 this。
当函数是以箭头函数方式创建的,此时的 this 指向箭头函数执行时的宿主函数的上下文
栗子:
function foo () {
let that = this
let too = () => {
console.log(this === that) // true
}
too()
}
foo()
too 为箭头函数,内部的 this 被指向为他创建时的上下文,即 foo 的 this 。反过来说就是箭头函数没有自己的上下文,他共享的是封闭词法上下文。
注意这里提到的 “this 是动态绑定,在运行期绑定的” 主要是指进入函数后,函数运行前的上下文创建阶段(
预处理
),此时函数内的表达式、语句并没有执行。但这里都统称为函数运行期,详细请关注变量对象
一节的描述(^.^)。
本文转载于:讲清楚之 javascript中的this
讲清楚之 javascript中的this的更多相关文章
- 【翻译】JavaScript中的作用域和声明提前
原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译开始=== 你知道下面的JavaScript脚本执 ...
- JavaScript中的作用域和声明提前
[翻译]JavaScript中的作用域和声明提前 原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译 ...
- JavaScript中正则使用
字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在.比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦, ...
- 讲清楚之 javascript 参数传值
讲清楚之 javascript 参数传值 参数传值是指函数调用时,给函数传递配置或运行参数的行为,包括通过call.apply 进行传值. 在实际开发中,我们总结javascript参数传值分为基本数 ...
- javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈
Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...
- javascript中的this与函数讲解
前言 javascript中没有块级作用域(es6以前),javascript中作用域分为函数作用域和全局作用域.并且,大家可以认为全局作用域其实就是Window函数的函数作用域,我们编写的js代码, ...
- JavaScript 中的数据类型
Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...
- javascript中的操作符详解1
好久没有写点什么了,根据博主的技术,仍然写一点javascript新手入门文章,接下来我们一起来探讨javascript的操作符. 一.前言 javascript中有许多操作符,但是许多初学者并不理解 ...
- 掌握javascript中的最基础数据结构-----数组
这是一篇<数据结构与算法javascript描述>的读书笔记.主要梳理了关于数组的知识.部分内容及源码来自原作. 书中第一章介绍了如何配置javascript运行环境:javascript ...
随机推荐
- docker入门-Dockerfile入门
1.dockerfile 构建基础命令 2. 构建镜像命令 Usage: docker image build [OPTIONS] PATH | URL | -Options:-t, --tag li ...
- Windows下Apache服务多个端口反向代理配置
修改\Apache24\conf\httpd.conf: 1.修改安装包地址: Define SRVROOT "/Apache24" 修改为: Define SRVROOT &qu ...
- JZ-068-打印从 1 到最大的 n 位数
打印从 1 到最大的 n 位数 题目描述 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数.比如输入 3,则打印出 1.2.3 一直到最大的 3 位数即 999. 题目链接: 打印从 1 到 ...
- LeetCode-056-合并区间
合并区间 题目描述:以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] .请你合并所有重叠的区间,并返回一个不重叠的区间数组, ...
- 线上| 10万奖金!Greaterwms/DVAdmin插件开发者现金激励活动
为激励广大开发者创作精神,Greaterwms/DVadmin插件开发者现金激励活动现已上线! 什么是GreaterWMS 完全开源仓储管理软件,遵循Apache License 2.0协议,前后端分 ...
- es-kibana基本使用
- 『德不孤』Pytest框架 — 14、Pytest中的conftest.py文件
目录 1.conftest.py文件介绍 2.conftest.py的注意事项 3.conftest.py的使用 4.不同位置conftest.py文件的优先级 5.conftest.py中Fixtu ...
- 实践2:github管理代码仓库,包含用webpack打包项目
此篇介绍用github仓库管理代码,github是大型的项目代码管理,存储平台:简单说帮助一个团队共享,修改同一个项目,配合着gitHub Desktop非常方便: 可以做到多人删除.修改.增加同一项 ...
- python关于openpyxl的二次开发
from openpyxl import load_workbook class Excel_util: def __init__(self,path): self.path=path # 加载输入路 ...
- SQL学习日记
目录 SQL学习日记 1. 常见的数据库对象 2. DDL 定义语句 3. DML 操作语句 4. DQL 查询语句 5. DCL 控制语句 SQL学习日记 1. 常见的数据库对象 对象名 关键字 描 ...