一名合格前端人员必须知道的 this 用法和陷阱(JS系列之三)

欢迎大家关注,接下来我会写一个关于 JavaScirpt系列文章,希望我们一起进步。

前言

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。作为一名前端攻城狮对它再熟悉不过了,然而正是因为熟悉它所以很容易忽略它,以至于用它时踩了不少的坑,甚至在面试时还因为它挂了。所以学习和掌握 this 的用法和一些陷阱对于进阶成名一名合格前端攻城狮很有必要。

this 误解

正所谓先破而后立,我们首先解除一下长时间对 this 的误解,再开始 this 学习之旅。

一直以来我们可能以为 this 是指向函数自身或者函数的词法作用域,这在某种情况下是可行的,但是还是不够。this 在未执行时我们谁也不知道它的指向到底是谁,因为只有函数被调用时才会对 this 进行赋值,所以要知道 this 指向谁,首先知道它是在什么位置被调用的。

调用位置

调用位置是函数在代码中调用的位置(而不是声明的位置)。这句话好像看起来像是废话,不过在它面前踩过坑的人觉得这句话说的太精辟了(这就是我想说的)。

调用位置分为以下几种情况:

  • 普通函数调用:在全局环境中调用函数
  • 对象方法调用:通过对象的方法调用
  • 构造器调用:使用 new 运算符实例化调用
  • 显式调用:通过 callapplybind 调用,修正 this 指向

普通函数调用

普通函数调用, this 指向全局对象。在浏览器 JS 引擎中this指向 window, Nodejs 环境 this 指向 global


function f1(){
  return this;
}

 // 在浏览器中,全局对象是 window
f1() === window   // true 

//在Node中,全局对象是 global
f1() === global // true

// 示例代码
var name = 'globalName'

var getName = function () {
  return this.name
}

getName() // globalName

// or

var obj = function () {
  name: 'John',
  getName: function () {
    return this.name
  }
}

var getName = obj.getName
getName() // globalName

值得注意的是在最后两行代码,对象方法赋值给了 getName 变量,调用 getName() 相当于 调用window.getName(),此时 this 是指向 window

陷阱

  • 在严格模式下,this 指向 undefined
  • 在全局环境中,使用 var 声明的变量会挂载在 window 上,但 letconst 声明的变量,不会挂载在 window
function f1(){
  'use strict'
  return this;
}

f1() // 严格模式下,this 指向 undefined

var a = 111
window.a // 111

let b = 222
const c = 333
window.b // undefined let、const 声明变量没有挂载在 window 上
window.c // undefined

对象方法调用

当函数作为对象的方法被调用时, this 指向该对象:

var obj = {
  name: '张三',
  getName: function () {
    return this.name
  }
}

obj.getName() // 张三

对象方法调用

陷阱

使用对象方法调用时,this 有可能会丢失,看下面这段代码

var name = 'globalName'
var obj = {
  name: '张三',
  getName: function () {
    function fn () {
        return this.name
    }
    return fn() // globalName
  }
}

obj.getName() // globalName 

上面代码输出 globalName 而不是 张三·,因为在 getName 函数内部调用 fn, 此时 fn 函数执行上下文this不是指向调用的对象 obj,而是指向 window

构造器调用

除了宿主提供的一些内置函数,大部分 JavaScript 函数可以当作构造器使用。构造器表面和普通函数一模一样,不同的地方在于被调用的方式。
使用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的 this 就指向这个对象

var MyName = function () {
    this.name = 'jeffery'
}
var obj = new MyName()
console.log(obj.name) // jeffery

使用 new 运算符创建 MyName 构造器,此时this 指向 obj

陷阱

使用 new 调用构造器时,还要注意一个问题,如果构造器显式返回一个 object 类型的对象,那么此次运行结果最终是返回这个对象,而不是我们之前期待的 this:

var MyName = function () {
    this.name = 'jeffery'
    return { // 显示返回一个对象
        name: 'this is myName'
    }
}
var obj = new MyName()
// 输出 this is myName,而不是上面的 jeffery
console.log(obj.name) // this is myName

如果构造器不显式返回任何数据,或者返回一个非对象类型的数据,就不会存在上面这个问题

var MyName = function () {
    this.name = 'jeffery'
    return 'this is myName'
}
var obj = new MyName()
console.log(obj.name) // jeffery

call、apply、bind 显式调用修正 this 指向

call、apply 修正 this 指向

callapply 调用函数和其他函数调用相比,它会改变传入函数的 this, 指向第一个传入的参数。callapply 两者实现功能相同, 不同的地方在于接收参数形式不一样,前者接收的是参数个数,后者接收的是一个数组

var obj1 =  {
    name: 'obj1 name',
    getName: function () {
        return this.name
    }
}

var obj2 = {
    name: 'obj2 name'
}
// 对象方法调用,this 指向 obj1
obj1.getName() // obj1 name

// 使用 call 显示调用,改变了原来 this 指向,指向了 obj2
obj1.getName.call(obj2) // obj2 name

陷阱

callapply 第一个参数除了可以是对象引用类型,也可以是基本类型:

  • nullundefinedthis 指向 window;不过,在严格模式下,this 还是指向 undefined

  • numberstringbooleanthis 会指向其内置构造函数 NumberStringBoolean

var name = 'globalName'
var obj =  {
    name: 'obj name',
    getName: function () {
        // 'use strict'  // 严格模式下,this 指向 undefined, null 会报错
        return this.name
    },
    getThis: function () {
        return this
    }
}

obj.getName.call(null) // globalName
obj.getName.apply(undefined) // globalName

// number boolean string
obj.getThis.call(111) // Number {111}
obj.getThis.call(true) // Boolean {true}
obj.getThis.call('str') // String {"str"}

bind 修正 this 指向

bindcallapply 不同的地方在于改变了this指向同时会返回一个新的函数

function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty

bind 绑定改变 this 只能生效一次,如果链式发生多次绑定以第一次为准

两道经典面试题

俗话说:“实践是验证真理的唯一标准”。很多时候我们以为学会了也只是自己以为学会了,是骡子还是马牵出来溜溜就知道了。所以,检验自己的学习成果莫过于实践。下面附上两道面试题让大家动脑实践一下

求解答为什么x.x调用结果会是undefined

function fn(xx){
    this.x = xx;
    return this;
}
var x = fn(5);
var y = fn(6);
console.log(x.x);
console.log(y.x);

下面的代码输出什么

let length = 10;
function fn() {
    console.log(this.length);
}

var obj = {
    length: 5,
    method: function(fn) {
        fn();   
        arguments[0]();     
        
    }
}

obj.method(fn, 1);

不知道答案的小伙伴可以戳这里:前端面试题(八)关于this指向的问题

引用链接

推荐阅读

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

推荐阅读更多精彩内容