1 this的五种绑定方式

1.1 默认绑定

默认绑定是指当函数调用时,没有为其指定对象上下文,此时会将该函数的this绑定到全局对象(window对象)。自ES5有了严格模式之后,默认绑定方式又分为非严格模式的默认绑定和严格模式的默认绑定。

1.1.1 非严格模式

在非严格模式下,函数的默认绑定只能绑定到全局对象window,见下面例子:

var myName = 'syzdev' // 相当于 window.myName = 'syzdev'
function sayName() {// 这里的 this === windowconsole.log(this === window); // trueconsole.log(this.myName); // syzdev
}
sayName()

在这个例子中,首先声明了一个全局变量myName

var声明的变量为全局变量,并且会将该变量添加为全局对象window的属性,需要注意的是,例子中的var不可修改为ES6中的letconst,不然结果大有不同,有兴趣的读者可以查阅资料“varletconst的区别”。

再定义了一个sayName()函数,在函数中输出this.myName,最后直接调用sayName()函数,再调用时,由于没有指定其对象上下文,所以触发了默认绑定,即将函数中的this绑定到全局对象window上,因此在函数内this === window,最后输出的this.myNamesyzdev

1.1.2 严格模式

关于严格模式,这里不做介绍,若不了解的读者可以阅读MDN-严格模式。

在严格模式下,有一项规定为“禁止this关键字指向全局对象window”,此时this会绑定到undefined,见下面例子:

// 开启严格模式
'use strict'
var myName = 'syzdev'
function sayName() {// 这里的 this !== windowconsole.log(this === window); // falseconsole.log(this); // undefinedconsole.log(this.myName); // 报错:Uncaught TypeError: Cannot read properties of undefined (reading 'myName')
}
sayName()

在这个例子中,在代码顶部使用'use strict'声明了严格模式,由于在严格模式下禁止this绑定到全局对象window,所以函数中的this !== window,输出thisundefined,因此输出this.myName时会直接抛出错误“Uncaught TypeError: Cannot read properties of undefined (reading ‘name’)”。

由于严格模式可以声明为全局或个别函数内,上面的例子中就是声明为全局严格模式,若声明为函数内严格模式,伪代码如下:

function sayName() {'use strict'...
}

需要注意的是,严格模式并不会影响到调用函数内的默认绑定,见下面例子:

var myName = 'syzdev'
function sayName() {// 这里的 this === windowconsole.log(this === window); // trueconsole.log(this.myName); // syzdev
}function strictSayName() {// 函数内的严格模式'use strict'sayName()
}
strictSayName()

在这个例子中,定义函数strictSayName()并在函数内部声明严格模式,再调用sayName()函数,此时sayName()函数内部依然能够将this绑定到全局对象window上。

1.2 隐式绑定

隐式绑定是日常开发中最为常见的绑定方式,即调用函数时为函数指明其对象上下文,此时函数中的this就为对象本身,见下面例子:

var personObj = {myName: 'syzdev',sayName: function() {console.log(this === personObj) // trueconsole.log(this.myName); // syzdev}
}
personObj.sayName()

在这个例子中,定义一个personObj对象,包含myName属性和sayName()方法,通过对象personObj.sayName()调用函数,此时就会触发this隐式绑定,函数中的this就是personObj本身。

但隐式绑定还可能会出现绑定丢失的情况,此时隐式绑定就会变成了默认绑定,见下面例子:

var personObj = {myName: '123',sayName: function() {console.log(this === window); // trueconsole.log(this.myName); // undefined}
}
var anotherSayName = personObj.sayName
anotherSayName()

在这个例子中,把personObj.sayName()函数赋值给了变量anotherSayName,再直接使用anotherSayName()调用,此时虽然调用的还是sayName()函数,但是却丢失了对象上下文,此时隐式绑定变成了默认绑定,anotherSayName()函数内的this绑定到全局对象window上,由于window对象上没有myName属性,所以输出undefined

1.3 显式绑定

显示绑定顾名思义,通过调用call/apply/bind方法,强制将函数中的this绑定到指定的对象,三者区别如下:

  • call()接受的是多个对象作为参数。
  • apply()接受的是多个对象组成的数组作为参数。
  • bind()返回的是一个函数,除此之外使用方法与call()一样。

使用call/apply/bind实现显示绑定的方法如下:

  1. 不含参数的使用方法:
function foo() {console.log(this.myName);
}var myName = 'window myName'
var personObj = {myName: 'personObj myName'
}foo() // window myName
foo.call(personObj) // personObj myName
foo.apply(personObj) // personObj myName
foo.bind(personObj)() // personObj myName

在这个例子中,直接调用sayName()函数会触发默认绑定,函数内的this绑定到全局对象window,所以输出window myName。由于该例子中函数不需要传参,所以在使用call/apply/bind方法时,不需要为其传递除this对象外的其他参数,其效果都是一致的,都是将sayNname()函数内的this绑定到personObj对象,最后输出的myNamepersonObj对象中的personObj myName

  1. 含参数的使用方法:
function foo(name, age) {console.log(name, age)
}foo('syzdev', 18) // 直接调用函数传递参数
foo.call(this, 'syzdev', 18) // call执行函数,第一个参数指定this对象,后续参数依次为函数的传参
foo.apply(this, ['syzdev', 18]) // apply执行函数,第一个参数指定this对象,第二个参数为一个数组,数组中为函数的传参
foo.bind(this, 'syzdev', 18)() // bind执行函数,返回的是一个函数,除此之外用法与call相同
// 输出结果
// syzdev 18
// syzdev 18
// syzdev 18
// syzdev 18

代码详解见注释。

1.4 new绑定

在ES6以前,生成一个实例对象的方法是使用构造函数,构造函数只是一个普通的函数,当使用new操作符调用构造函数时,JavaScript解释器便会在底层创建一个新对象,构造函数内的this绑定的就是这个新对象,见下面例子:

function Person(name, age) {this.name = namethis.age = age
}
var person = new Person('syzdev', 18)

在这个例子中,使用new操作符执行一个构造函数,创建一个person对象,在构造函数内会创建一个新的对象,将函数内的this绑定到这个新对象上,如果函数内部没有返回其他对象,则构造函数会默认返回这个新对象,流程如下图所示:

1.5 箭头函数绑定

箭头函数是ES6中的一个重要特性,在箭头函数中没有自己的this,其this是根据外层的作用域来决定的,箭头函数内的this指的是定义时所在的对象,是在函数定义时已经决定了,而不是像普通函数一样在调用时绑定this对象,见下面例子:

var foo = () => {console.log(this.myName);
}var myName = 'window myName'
var personObj = {myName: 'personObj myName'
}foo() // window myName
foo.call(personObj) // window myName
foo.apply(personObj) // window myName
foo.bind(personObj)() // window myName

在这个例子中,函数foo定义为箭头函数,由于箭头函数没有自己的this,其this值在定义时已经决定,所以无法被call/apply/bind方法所改变,在该例子中其this值为全局对象window,所以四种方法调用foo()函数的结果都为window myName

2 this绑定的优先级

所谓this绑定的优先级,指的是当函数执行时若同时指定了多个this对象时的绑定顺序。

  1. 隐式绑定 > 默认绑定

在第2章的例子中已经证明了这个结论:

var personObj = {myName: 'syzdev',sayName: function() {console.log(this === personObj) // trueconsole.log(this.myName); // syzdev}
}
// 相当于 window.personObj.sayName()
personObj.sayName()

在这个例子中,personObj.sayName()为隐式绑定,而personObj.sayName()相当于window.personObj.sayName()可见window对象的默认绑定失效了,最终执行的this依旧是personObj,所以隐式绑定 > 默认绑定。

  1. 显示绑定 > 隐式绑定

修改第3章的例子如下:

function foo() {console.log(this.myName);
}var myName = 'window myName'
var personObj = {myName: 'personObj myName',foo: foo // 修改部分
}personObj.foo.call(window) // window myName

在上面例子中的personObj.foo为隐式绑定,再通过callfoo()函数中的this显示绑定到全局对象window上,最终输出的结果还是window myName,可见显示绑定 > 隐式绑定。

  1. new绑定 > 显示绑定
function foo(name) {this.name = name
}// 创建对象obj
var obj = {}
// 将obj绑定为函数foo中的this
var bindFoo = foo.bind(obj)
bindFoo('syzdev')
console.log(obj.name) // syzdevvar bindObj = new bindFoo('bind syzdev')
console.log(bindObj.name) // bind syzdev

在这个例子中,使用bind方法将obj绑定到foo()函数中并返回一个新的函数bindFoo,在使用new操作符创建一个新的对象bindObj时,函数bindFoo()this指向发生了改变,原本函数bindFoo()中的this指向的是obj,在使用new操作符后指向了一个新的对象并返回赋值给了bindObj

2.2 总结

在讨论this绑定优先级时之所以不讨论箭头函数,是因为箭头函数的this在其函数定义时就已经确定,不存在优先级一说。其他四种绑定方式的优先级如下,由高到低:

  1. new绑定
  2. 显示绑定
  3. 隐式绑定
  4. 默认绑定

JavaScript中this的五种绑定方式详解相关推荐

  1. PCBA加工中常见的两种焊接方式详解

    PCBA加工中常见的两种焊接方式详解 PCBA加工,两种常见的焊接方式就是回流焊和波峰焊,与手动焊接技术相比,自动焊接技术具有减少人为因素的影响.提高效率.降低成本.提高质量等优势,在PCBA加工中, ...

  2. JS(javascript)中this的几种用法实例详解

    本文主要介绍了javascript(以下简称js)中 this 用法,结合具体实例详细分析一下js中 this 的含义及使用方法,需要的朋友可以参考下. this 是 JavaScript 语言的一个 ...

  3. C++的三种继承方式详解

    文章目录 @[toc] C++的三种继承方式详解以及区别 前言 一.public继承 二.protected继承 三.private继承 四.三者区别 五.总结 后话 C++的三种继承方式详解以及区别 ...

  4. @Resource,@Autowired,@Inject3种注入方式详解

    转载自 @Resource,@Autowired,@Inject3种注入方式详解 概况 @Resource,@Autowired,@Inject 这3种都是用来注入bean的,它们属于不同的程序中. ...

  5. android 实现毫秒定时器,Android实现定时器的五种方法实例详解

    一.Timer Timer是Android直接启动定时器的类,TimerTask是一个子线程,方便处理一些比较复杂耗时的功能逻辑,经常与handler结合使用. 跟handler自身实现的定时器相比, ...

  6. javascript中的Base64.UTF8编码与解码详解

    javascript中的Base64.UTF8编码与解码详解 本文给大家介绍的是javascript中的Base64.UTF8编码与解码的函数源码分享以及使用范例,十分实用,推荐给小伙伴们,希望大家能 ...

  7. c# 委托实例的几种执行方式详解

    声明委托: 首先,绑定委托方法有如下几种方式: 第一种,直接对符合委托结构的方法进行多绑定: 第二种,绑定匿名方法: 第三种,用等号指定单方法: 注意,第三种绑定方式会将之前所绑定的方法全部解绑,执行 ...

  8. Rabbitmq中常用的五种连接方式

    目录 前提准备 方式一:  Hello World 服务端(provider)代码 客户端(customer)代码 方式二: work(以下方式都是通过工具类来创建connection对象) 有两种方 ...

  9. JavaScript中函数的四种调用方式(若有错误之处请斧正)

    函数的几种调用方式 一.一般形式函数的直接调用 fun(); 二.作为对象的方法调用 var obj = {name:"123",sayMyage:function(age){al ...

最新文章

  1. Servlet实现的三种方法
  2. BZOJ1393 [Ceoi2008]knights
  3. java rtmp m3u8_vue常用插件之视频播放(rtmp m3u8)
  4. 【实战】用机器学习来提升你的用户增长:(三、预测客户的终生价值)
  5. s5p4418 Android 4.4.2 驱动层 HAL层 服务层 应用层 开发流程记录(三 APP应用)
  6. PLSQL_案例优化系列_学会应用工具进行SQL整体优化(案例11)
  7. 随机模拟_随机模拟可帮助您掌握统计概念
  8. bluestacks手机模拟器安装qq或微信时,鼠标左键点聊天编辑框后会自动输入 c 字母
  9. 每日三道前端面试题--vue 第二弹
  10. Linux 每日一练 :cat猫 的 反义词是啥 ? 是这个tac 哇哇哇!!
  11. vc2013 调用大漠插件例子
  12. 项目管理可参考华为这10张表格
  13. 页面跳转 并将改页面从历史栈中删除
  14. 微信公众平台Js API实现微信分享
  15. iPhone上塔罗牌测试软件,塔罗牌占卜:测Ta对你是用情至深还是一时兴起?准到没朋友!...
  16. 个人关于学习的一些总结
  17. endnote添加引文格式
  18. [rust-003] rust by example学习过程点点滴滴杂记
  19. ologit 平行线检验 brant踩坑历程not all independent variables can be retained in binary logits brant test cann
  20. jQuery-Ajax的使用

热门文章

  1. iOS Ruby出现问题,导致无法安装Pod
  2. 解决SecureCRT连接GNS3时SecureCRT标签窗口同名的问题
  3. 寻找实力高手长期合作
  4. 为什么ORM性能比iBATIS好?
  5. CSU2188: Substring
  6. 深入理解MyBatis的原理(三):配置文件(上)
  7. python 字符串的魔法 day11(2)
  8. ES6--基础语法(一)
  9. [Github]watch和star的区别
  10. JSON WEB TOKEN