特点
- this永远指向一个对象。
- this指向 在函数
执行时确定。(执行上下文三大属性之一)
执行上下文概念可在往期文章 作用域与作用域链 | 8月更文挑战 中了解。
规则
摘自《你所不知道的JavaScript》上卷,用来判断this指向。顺序代表着优先级,由高到低。(箭头函数不适用)
- 由new调用,则绑定到新创建的对象。
- 由call、apply、bind调用,绑定到指定的对象。
- 由上下文对象调用,绑定到上下文对象。
- 默认:非严格模式下绑定到undefined,否则绑定到全局对象。
下文将按照当前规则顺序讲述,最后单独讨论箭头函数。
new 调用
用new调用构造函数创建新对象,this指向新对象。
function person(name) {
this.name = name
}
let obj = new person('瑾行')
console.log(obj.name) // 瑾行
Tips:若构造函数有返回值,只返回构造函数返回的对象。
手写new函数
function _new() {
let obj = new Object()
Constructor = [].prototype.shift.call(arguments)
obj.__proto__ == Constructor.prototype
let ret = Constructor.apply(obj, arguments)
return typeof ret === 'object' ? ret : obj
}
call、apply、bind
call、apply、bind都拥有一样的能力去做同一件事(更改函数运行时的执行上下文对象),主要讲讲三者用法以及差异。
call
function.call (thisArg, arg1, arg2, ...)
thisArg: 函数上下文对象,可选参数。若null或undefined自动替换全局对象。
arg1,arg2,...: 参数列表。
function introduce(job) {
console.log(this.name + '是一名' + job)
}
let obj = { name: '瑾行' }
let job = '前端工程师'
introduce.call(obj, job) // 瑾行是一名前端工程师
apply
与call用法大致相同,差异在利用数组包裹参数列表。
function.apply(thisArg, [arg1, arg2, ...])
function introduce(job) {
console.log(this.name + '是一名' + job)
}
let obj = { name: '瑾行' }
let job = '前端工程师'
introduce.apply(obj, [job]) // 瑾行是一名前端工程师
bind
bind 与 call、apply差异在于不是立即调用函数,而是返回改变函数上下文后的函数。 注意: 多次bind是无效的。
function.bind(thisArg, arg1, arg2, ...)
function introduce(job) {
console.log(this.name + '是一名' + job)
}
let obj = { name: '瑾行' }
let job = '前端工程师'
let func = introduce.bind(obj, job)
func() // 瑾行是一名前端工程师
在返回的函数上再次绑定新的对象,输出值不变。
function introduce(job) {
console.log(this.name + '是一名' + job)
}
let obj = { name: '瑾行' }
let obj1 = { name: '七金' }
let job = '前端工程师'
let func = introduce.bind(obj, job).bind(obj1, job)
func() // 瑾行是一名前端工程师
第二次bind是改变返回函数执行上下文对象,而非原函数。编写简单的bind函数覆盖原生bind实现,并执行下述代码。
Function.prototype.bind = function(oThis) {
if(typeof this !== 'function') {
return;
}
var self = this,
// 当前arguments是bind函数的传参列表
args = Array.prototype.slice.call(arguments, 1);
return function() {
// 当前arguments是返回函数的传参
console.log(oThis,this)
return self.apply(oThis, args.concat(Array.prototype.slice.call(arguments)));
}
}
function introduce(job) {
console.log(this.name + '是一名' + job)
}
let obj = { name: '瑾行' }
let obj1 = { name: '七金' }
let job = '前端工程师'
let func = introduce.bind(obj, job).bind(obj1, job)
func() // 瑾行是一名前端工程师
从截图可以得出结论:
第一行是func, this指向是window。
第二行是introduce.bind(obj, job), this指向obj1。也就是说再次绑定的bind改变的是introduce.bind(obj, job)的执行上下文对象,而非原函数introduce。✌
对象调用
若调用位置上下文有对象,this则绑定到指定对象。但注意会存在丢失绑定对象的情况。
var person = {
name: '瑾行',
getName: getName
}
var name = '七金'
function getName() {
console.log(this.name)
}
person.getName() // 瑾行
var func = person.getName
func() // 七金
默认情况
指在任何作用域下,直接通过函数名调用。
特点: 非严格模式下绑定到全局对象window,否则绑定到undefined。
// 非严格模式
var name = '瑾行'
function getName() {
console.log(this, this.name)
}
getName() // Window{},瑾行
// 严格模式
var name = '瑾行'
function getName() {
"use strict"
console.log(this, this.name)
}
getName() // undefined,报错
// 函数作用域下调用
var name = '瑾行'
function getName() {
console.log(this,this.name)
}
function func() {
getName()
}
func() // Window{},瑾行
箭头函数
上文规则不再适用箭头函数。
箭头函数与function函数有如下区别:
- 箭头函数语法简洁、清晰。
- 箭头函数本身没有绑定
this。 - 箭头函数
this是从上层的作用域寻找的,被定义时就已确定,且this指向永远不变。
var name = '瑾行'
function getName() {
setTimeout(() => {
console.log(this.name)
}, 1000)
}
var obj = {name: '七金'}
getName(obj) // 瑾行
getName.call(obj) // 七金
// 原因:
// 箭头函数定义时找到上层函数getName的this,getName函数的this指向window,所以输出 瑾行。
// getName.call(obj) 改变了getName的this, 又因为箭头函数定义时就继承了上层作用域的this,所以输出 七金。
- 箭头函数不能作为构造函数使用。
let getName = () => {
this.name = '瑾行'
}
var obj = new getName()
// 报错:Uncaught TypeError: getName is not a constructor。
// 原因:因为箭头函数不存在显式原型,无法完成构造。底层实现相关代码:obj.__proto__ = Constructor.prototype。
- new、call、apply、bind无法改变箭头函数执行上下文对象。
let obj = {name: '瑾行'}
let obj1 = {name: '七金'}
function getName() {
return () => {
// 继承foo
console.log(this.name)
}
}
let func = getName.bind(obj)
let func1 = func().bind(obj1)
func1() // 输出瑾行
- 箭头函数没有
arguments对象。
var name = '瑾行'
let getName = () => {
console.log(arguments)
}
getName()
// 报错:ReferenceError: arguments is not defined
// 原因:箭头函数 和 全局环境 均没有 arguments参数
- 箭头函数没有原型
prototype。
var name = '瑾行'
let getName = () => {
}
console.log(getName.prototype) // undefined
- 箭头函数不可当做Generator函数,也不能内部使用yield。
参考
彻底搞懂JavaScript中的this指向问题
JavaScript的this指向问题深度解析
JavaScript深入之bind模拟实现