📒【this】JavaScript中的this指向探索

在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期绑定。而 在 JavaScript 中,this 是动态绑定,或称为运行期绑定的 ,由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式

Q1:为什么要用this呢?

function identify(){
  return this.name.toUpperCase();
}
function speak(){
  var greeting = "Hello,I'm " + identify.call(this);
  console.log(greeting);
}
var me = { name: 'BubbleM', speak: speak };
me.speak(); // Hello,I'm BUBBLEM

上面这段代码可以在不同的上下文对象(me和you)中重复使用函数identify和speak,不用针对每个对象编写不同版本的函数。
如果不使用this,那就需要给identify和speak显式传入一个上下文对象:

function identify(context){
  return context.name.toUpperCase();
}
function speak(context){
  var greeting = "Hello,I'm " + identify(context);
  console.log(greeting);
}
var me = { name: 'BubbleM', speak: speak };
me.speak(me); // Hello,I'm BUBBLEM

A1:this提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将API设计得更加简洁并易于复用。

this指向谁?

多数情况下,this 指向调用它所在方法的那个对象。当调用方法没有明确对象时,this 就指向全局对象。在浏览器中,指向 window;在 Node 中,指向 Global。(严格模式下,指向 undefined)
⚠️ this 的指向是在调用时决定的,而不是在书写时决定的。这点和闭包恰恰相反。

// 声明位置
var me = {
  name: 'xiuyan',
  hello: function() {
    console.log(`你好,我是${this.name}`)
  }
}
var you = {
  name: 'xiaoming',
  hello: function() {
    var targetFunc = me.hello
    targetFunc()
  }
}
var name = 'BigBear'

// 调用位置
you.hello()

调用位置输出的结果是 BigBear—— 竟然不是 xiaoming?的确,我们打眼看过去,直觉上肯定会认为是 you 这个对象在调用 hello 方法、进而调用 targetFunc,所以此时 this 肯定指向 you 对象啊!为啥会输出一个 window 上的 name 呢?
“this 指向调用它所在方法的那个对象”
回头看 targetFunc 这个方法,之所以第一直觉会认为它的 this 应该指向 you 这个对象,其实还是因为把 “声明位置”“调用位置” 混淆了。我们看到虽然 targetFunc 是在 you 对象的 hello 方法里声明的,但是在调用它的时候,我们是不是没有给 targetFunc 指明任何一个对象作为它前缀? 所以 you 对象的 this 并不会神奇地自动传入 targetFunc 里,js 引擎仍然会认为 targetFunc 是一个挂载在 window 上的方法,进而把 this 指向 window 对象。
一定要记住“不管方法被书写在哪个位置,它的 this 只会跟着它的调用方走”这个核心原则。

特殊情境下的this指向

在三种特殊情境下,this 会 100% 指向 window:

  • 立即执行函数(IIFE),即定义后立刻调用的匿名函数;
var name = 'BigBear'
var me = {
  name: 'xiuyan',
  // 声明位置
  sayHello: function() {
    console.log(`你好,我是${this.name}`)
  },
  hello: function() {
    (function(cb) {
      // 调用位置
      cb()
    })(this.sayHello)
  }
}

me.hello() // BigBear

立即执行函数作为一个匿名函数,在被调用的时候,我们往往就是直接调用,而不会(也无法)通过属性访问器( 即 xx.xxx) 这样的形式来给它指定一个所在对象,所以它的 this 是非常确定的,就是默认的全局对象 window。

  • setTimeout 中传入的函数;
  • setInterval 中传入的函数;
var name = 'BigBear'

var me = {
  name: 'xiuyan',
  hello: function() {
    setTimeout(function() {
      console.log(`你好,我是${this.name}`)
    })
  }
}

me.hello() // 你好,我是BigBear

我们所看到的延时效果(setTimeout)和定时效果(setInterval),都是在全局作用域下实现的。无论是 setTimeout 还是 setInterval 里传入的函数,都会首先被交付到全局对象手上。因此,函数中 this 的值,会被自动指向 window。

严格模式下的this指向

  1. 普通函数中的 this 在严格模式下的表现:
    所谓 “普通函数” ,这里我们是相对于箭头函数来说的。在非严格模式下,直接调用普通函数时,函数中的 this 默认指向全局变量(window 或 global)
function showThis() {
  console.log(this)
}
showThis() // 输出 Window 对象

在严格模式下,this 将保持它被指定的那个对象的值,所以,如果没有指定对象,this 就是 undefined

'use strict'
function showThis() {
  console.log(this)
}
showThis() // undefined
  1. 全局代码中的 this 在严格模式下的表现:
'use strict'
console.log(this) // 直接在全局代码里尝试去拿 this  Window
'use strict'
var name = 'BigBear'
var me = {
  name: 'xiuyan',
  hello: function() {
    // 全局作用域下实现的延时函数
    setTimeout(function() {
      console.log(`你好,我是${this.name}`)
    })
  }
}
me.hello() // 你好,我是BigBear

像这样 处于全局代码中的 this, 不管它是否处于严格模式下,它的 this 都指向 Window(这点要特别注意,容易误以为这里也是 undefined )。

构造函数中的this

当我们使用构造函数去 new 一个实例的时候:

function Person(name) {
  this.name = name
  console.log(this)
}
var person = new Person('xiuyan')

构造函数里面的 this 会绑定到我们 new 出来的这个对象person上。

Q2:使用new来调用函数,或者发生构造函数调用时发生了什么?

  1. 创建一个全新的对象
  2. 这个新对象会执行[[Prototype]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

箭头函数中this的指向

箭头函数和闭包很相似,都是认“死理”—— 认“词法作用域”的家伙。所以说 箭头函数中的 this,和你如何调用它无关,由你书写它的位置决定(和普通函数的 this 规则恰恰相反~)

var name = 'BigBear'
var mefun = function(){
  this.name = 'xiuyan';
  return () => {
    console.log(this.name)
  };
}
var me = {
  name: 'xiuyan',
  // 声明位置 此时作用域是全局作用域
  hello: () => {
    console.log(this.name)
  }
}
// 调用位置
me.hello() // BigBear
mefun()(); // xiuyan

如何改变this的指向?

  1. 通过改变书写代码的方式做到(比如箭头函数)
    当我们将普通函数改写为箭头函数时,箭头函数的 this 会在书写阶段(即声明位置)就绑定到它父作用域的 this 上。无论后续我们如何调用它,都无法再为它指定目标对象 —— 因为 箭头函数的 this 指向是静态的,“一次便是一生”。
  2. 显式地调用一些方法来帮忙,如callapplybind
  • call、apply 和 bind 之间的区别比较大:
    callapply在改变 this 指向的同时,直接执行目标函数;
    bind则只负责改造 this,不作任何执行操作,返回一个绑定上下文的函数。
  • call 和 apply 之间的区别,则体现在对入参的要求上:
    call只需要将目标函数的入参逐个传入即可;
    apply则希望入参以数组形式被传入。

模拟实现call方法 实现核心思想

  • 改变 this 的指向,将 this 绑到第一个入参指定的的对象上去;
  • 根据输入的参数,执行函数。
Function.prototype.myCall = function(context, ...args){
  context.func = this;
  context.func(...args);
  delete context.func;
}
Function.prototype.myApply = function(context, arr){
  context.func = this;
  context.func(...arr);
  delete context.func;
}
Function.prototype.myBind = function(context, ...args){
  let self = this;
  return function(){
    self.call(context, ...args)
  }
}
var name = 'global';
var me = {
 name: 'hah'
}
function showName(){
  console.log(this.name);
}
function showFullName(firstNama, lastName){
  console.log(`${this.name} ${firstNama}_${lastName}`);
}
showName(); // global
showName.myCall(me); // hah
showFullName.myCall(me, 'Bubble', 'M'); // hah Bubble_M
showFullName.apply(me, ['Bubble', 'M']); // hah Bubble_M
showFullName.myApply(me, ['Bubble','M']); // hah Bubble_M
showFullName.bind(me, 'Bubble', 'M')(); // hah Bubble_M
showFullName.myBind(me, 'Bubble', 'M')(); // hah Bubble_M

扩充思考:
Q:如果我们第一个参数传了 null 怎么办?是不是可以默认给它指到 window 去?A:context = context || global;
Q:函数如果是有返回值的话怎么办?是不是新开一个 result 变量存储一下这个值,最后 return 出来就可以了?

Function.prototype.myCall = function(context, ...args){
  context = context || window;
  context.func = this;
  let result = context.func(...args);
  delete context.func;
  return result;
}

其他

Reference 类型与 this 的指向密切关联
ES3模拟实现apply和call

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348