讲清楚之 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.ab与对象foo的包含成员没有多大关系,最靠近的对象才决定this的绑定。

最后console.log(this.c)取到的是foo.ac的值 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操作符执行时的逻辑推导如下:

  1. 创建一个新的空对象;
  2. 将构造函数的 this 指向这个新对象;
  3. 将构造函数的原形添加到新对象的原形链里,将属性、方法挂载在新对象上;
  4. 返回这个新对象

返回的新对象就是我们实例化的对象。即, 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的更多相关文章

  1. 【翻译】JavaScript中的作用域和声明提前

    原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译开始=== 你知道下面的JavaScript脚本执 ...

  2. JavaScript中的作用域和声明提前

    [翻译]JavaScript中的作用域和声明提前 原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译 ...

  3. JavaScript中正则使用

    字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在.比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦, ...

  4. 讲清楚之 javascript 参数传值

    讲清楚之 javascript 参数传值 参数传值是指函数调用时,给函数传递配置或运行参数的行为,包括通过call.apply 进行传值. 在实际开发中,我们总结javascript参数传值分为基本数 ...

  5. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

  6. javascript中的this与函数讲解

    前言 javascript中没有块级作用域(es6以前),javascript中作用域分为函数作用域和全局作用域.并且,大家可以认为全局作用域其实就是Window函数的函数作用域,我们编写的js代码, ...

  7. JavaScript 中的数据类型

    Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...

  8. javascript中的操作符详解1

    好久没有写点什么了,根据博主的技术,仍然写一点javascript新手入门文章,接下来我们一起来探讨javascript的操作符. 一.前言 javascript中有许多操作符,但是许多初学者并不理解 ...

  9. 掌握javascript中的最基础数据结构-----数组

    这是一篇<数据结构与算法javascript描述>的读书笔记.主要梳理了关于数组的知识.部分内容及源码来自原作. 书中第一章介绍了如何配置javascript运行环境:javascript ...

随机推荐

  1. k8s全方位监控 -prometheus实现短信告警接口编写(python)

    1.prometheus短信告警接口实现(python)源码如下: import subprocess from flask import Flask from flask import reques ...

  2. QQ音乐官方定制精简版v1.3.6 纯净无广告

    介绍 近期腾讯推出了QQ音乐简洁版.顾名思义,QQ音乐简洁版就是官方精简后的版本,没有内置任何广告.完全专注于听歌,不存在直播.K歌.短视频等花里胡哨的内容.如有违规,请删删.. 结尾附pc端 QQ音 ...

  3. LabVIEW,控件快捷菜单,温度转换

    目前正在自学LabVIEW,深感困难重重,我将偶尔发表一些自己的收获,自认为算是干货了, 搜到这篇随笔的朋友们或多或少遇到了些许困难,希望这能帮助到你们. 内容:练习使用LabVIEW中的控件快捷菜单 ...

  4. java 中判断输入是否合法 if (变量名.hasNextInt())

    //案例: Scanner sc = new Scanner(System.in); System.out.println("你选择了新修改商品功能!"); System.out. ...

  5. CF698C题解

    为什么 \(n,k \leq 20\)? 我还以为是什么 \(n,k \leq 10^6\) 的厉害题/qd 看到这个队列操作很迷惑,但是仔细看看要操作 \(10^{100}\) 遍,所以我们可以直接 ...

  6. Java案例——字符串中的数据排序

    需求:有一个字符串"9 1 2 7 4 6 3 8 5 0",请编写程序实现从小到大数据排序 分析:最重要的部分是如何将字符串中的数据取出来 1.定义一个字符串为"9 1 ...

  7. 阿里云开源镜像站支持IPv6访问

    阿里云开源镜像站在国内企业镜像站中率先支持IPv6访问! 点击立即试用https://developer.aliyun.com/mirror/ 同时基于阿里云OpenSearch的搜索能力,开源镜像站 ...

  8. nginx配置只允许某个IP或某些IP进行访问

    server{ listen 80; listen 443 ssl; server_name ehall.jerry.plus; ssl_certificate "****.crt" ...

  9. Nginx 静态文件服务

    Nginx 静态文件服务 我们先来看看最简单的本地静态文件服务配置示例: server { listen 80; server_name www.test.com; charset utf-8; ro ...

  10. Java中对文件的处理01-递归删除

    package com.ricoh.rapp.ezcx.admintoolweb.util; import java.io.BufferedInputStream; import java.io.Bu ...