JavaScript中this指向问题
JavaScript中的this
对于许多开发者来说是一个非常头痛的问题,但是this
关键字却又是一个非常重要的语法点
为什么需要this?
在很多的编程语言中都有this
这个关键字,但是JavaScript中的this
和常见的那些拥有this
关键字的语言是有很大区别的
JavaScript中的this
是非常灵活的,无论是它出现的位置还是它代表的含义
- 我们编写一个对象的案例,看看有无
this
的区别
/* 有this的情况 */
var obj = {
name: 'jingzhi',
eating: function() {
console.log(this.name + '正在吃饭')
},
studying: function() {
console.log(this.name + '正在学习')
}
}
/* 无this的情况 */
var obj = {
name: 'jingzhi',
eating: function() {
console.log(obj.name + '正在吃饭')
},
studying: function() {
concole.log(obj.name + '正在学习')
}
}
可以看出this
提供了一种隐形的传递一个对象的引用,api可以设计的简洁并且灵活,但是其缺点是学习成本高
this到底指向什么?
函数中的this
到底指向什么,在函数的定义阶段我们是不能完全确定的,只有在函数调用的时候我们才能确定
看如下案例:定义一个函数,我们采用3中不同的方式去对它进行调用,看看它的结果分别有什么不同
function foo() {
console.log(this)
}
// 1、函数单独直接调用
foo() // window
// 2、将foo函数放在一个对象中,通过对象引用
var obj = {
name: 'jingzhi',
foo: foo
}
obj.foo() // obj
// 3、通过call/apply调用
foo.call('jingzhi') // String('jingzhi')
对于以上案例,我们可以得到什么启示呢?
- 函数在调用的时候,JavaScript会默认给
this
绑定一个值 this
的绑定和函数的定义位置是没有关系的this
的绑定和调用方式、调用位置有关this
是在函数调用时才绑定
this的绑定规则
在JavaScript中,this
到底是有怎么样的绑定规则呢?
- 规则一:默认绑定;
- 规则二:隐式绑定;
- 规则三:显式绑定;
- 规则四:new绑定;
默认绑定
独立的函数调用就是默认绑定,我们可以理解为函数没有绑定在某个对象上进行调用
- 我们通过几个案例看一些
function foo() {
console.log(this)
}
foo() // window
function test1() {
console.log('test1', this)
test2()
}
function test2() {
console.log('test2', this)
test3()
}
function test3() {
console.log('test3', this)
}
test1()
function foo(func) {
func()
}
var obj = {
name: 'jingzhi',
fn: function() {
console.log(this)
}
}
foo(obj.fn) // window
隐式绑定
通过某个对象进行调用的就是隐式绑定,也就是他的调用位置是在某个对象发起的函数调
- 我们通过几个案例看一下
function foo() {
console.log(this)
}
var obj = {
name: 'jingzhi',
foo: foo
}
obj.foo() // obj
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo
}
var obj2 = {
name: 'obj2',
obj1: obj1
}
obj2.obj1.foo() // obj1
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo
}
var bar = obj1.foo;
bar() // window (隐式丢失: 在特定情况下会存在隐式绑定丢失的问题,最常见的就是作为参数传递以及变量赋值)
/* bar 在此时是独立调用的 */
显式绑定
显式绑定需要有前提条件:
- 必须在调用的对象内部有一个对函数的引用
JavaScript所有的函数都可以使用call
和apply
方法
call
和apply
可以用来改变this
的指向;call
和apply
都是为了改变某个函数运行时的context
,即上下文而存在的,换句话说,就是为了改变函数体内部this
的指向
-
call
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数 -
apply
方法调用一个具有给定this
值的函数,以及以一个数组(或类数组对象)的形式提供的参数
function foo() {
console.log(this)
}
var obj = {
name: 'jingzhi',
foo: foo
}
foo() // window
obj.foo() // obj
foo.call(obj) // obj
foo.apply(obj) // obj
JavaScript函数总是显式的绑定到一个对象bind
方法
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用
bind
属于硬绑定,返回的函数的this
指向无法再次通过bind
、apply
或call
修改
function foo() {
console.log(this)
}
var obj = {
name: 'jingzhi'
}
var bar = foo.bind(obj)
bar() // obj
call
、apply
、bind
方法传入特殊值
call
、apply
、bind
若是把undefiend
, null
,那么执行作用域的this
会被视为window
var name = 'jingzhi'
function foo() {
this.name = 'haha'
console.log(this.name, this)
}
function bar() {
console.log(this)
foo.call(null)
foo.apply(null)
foo.bind(null)()
}
var obj = {
name: 'obj',
bar: bar
}
foo.call(null)
foo.apply(null)
foo.bind(null)()
obj.bar.call(null)
通过传入null
将函数内部this
绑定成了window对象,并且window.name
也从jingzhi
变成了haha
,obj的bar
函数也改变了this
的指向
显式绑定是指我们通过call
、apply
以及bind
方法改变this
的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程
内置函数的绑定
定时器
setTimeout(function() {
console.log(this)
}, 1000) // window
DOM事件
<body>
<button>click</button>
<script>
var btn = docment.querySeelector('button')
btn.addEventListener('click', function() {
console.log(this)
})
/* <button>click</button> */
</script>
</body>
数组的高阶函数
var nums = [1, 2, 3, 4, 5]
nums.forEach(function(item) {
console.log(this) // window
})
new绑定
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字
使用new关键字来调用函数是,会执行如下的操作
- 创建一个全新的对象
- 这个新函数会被执行
prototype
连接 - 这个新对象会绑定到函数调用的
this
上(this
绑定在此时完成) - 如果函数没有返回其他对象,表达式会返回这个新对象
这个过程我们称之为构造调用,我们来看个例子
function Person() {
console.log(this)
}
var p = new Person() // Person
绑定规则优先级
this
共有四条绑定规则,那如果同时出现多条规则的情况又该如何判断this
绑定?
- 默认规则的优先级最低
- 显式绑定的优先级比隐式绑定高
- new绑定的优先级高于隐式绑定
- new绑定高于
bind
绑定(new绑定无法与call
、apply
绑定比较,call
/apply
会直接执行函数)
function foo() {
console.log(this)
}
var obj = {
foo1: foo,
foo2: foo.bind('jingzhi')
}
// 1.默认规则的优先级最低
foo() // window
obj.foo1() // obj
// 2.显式绑定的优先级比隐式绑定高
var fn = obj.foo1.bind('jingzhi')
fn() // String {'jingzhi'}
obj.foo2() // String {'jingzhi'}
// 3.new绑定的优先级高于隐式绑定
var o1 = new obj.foo1() // foo
// 4.new绑定高于bind绑定(new绑定无法与call、apply绑定比较)
var o2 = new obj.foo2() // foo
this规则之外
间接函数引用
创建一个函数的间接引用,这种情况使用默认绑定规则
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo
}
var obj2 = {
name: 'obj2'
}
obj1.foo() // obj1
/*
1. 赋值(obj2.foo = obj1.foo)的结果是foo函数
2. foo函数被直接调用,相当于独立函数调用,就是默认绑定
*/
(obj2.foo = obj1.foo)() // window
ES6 箭头函数
ES6 中允许使用箭头函数的方式来定义一个函数,箭头函数表达式的语法比函数表达式更简洁,并且不绑定this
,根据外层作用域来决定this
绑定什么
setTimeout(() => {
console.log(this)
}, 1000) // window
var obj = {
foo: () => {
console.log(this)
},
bak: () => {
setTimeout(() => {
console.log(this)
}, 1000)
},
bar: function() {
setTimeout(() => {
console.log(this)
}, 1000)
}
}
/* 对象的大括号并不构成一个作用域 */
obj.foo() // window
obj.bak() // window
/* 外层包裹一个非箭头函数,有作用域 */
obj.bar() // obj
箭头函数并不绑定this
对象,那么this
引用就会从上层作用域中找到对应的this
本篇文章是作者对coderwhy(王红元老师)JavaScript高级语法课程中this绑定、优先级学习结束之后的总结,感谢coderwhy老师的认真、细致的讲解