写在前面
在前几篇博文中,关于this理解问题,我都有提到。 自认为理解很到位了,结果项目中,经常被this搞蒙头。简单的可能还好,如果函数调用位置不太明确的,只能大概加估计了。再次复习,加深理解。
js的作用域是基于函数的作用域的,在谈到this指向问题时,很多情况下都是结合函数来说的。this到底是什么,是个什么东西。单独的this就是js的一个关键字,无任何函数,函数调用时,他便指向一个对象。在理解问题之前,我们先得理解调用位置,函数调用栈。然后在介绍结合四法则,结合理解this
函数调用位置
调用栈 一种数据结构,可以理解为函数执行时调用的所有函数
调用位置 函数在代码中被调用的地方,包含函数调用的东西,或者说上下文。为什么需要介绍这个,this就是在函数调用时绑定的,需要找到调用位置,才能方便分析出this的指向。
先来看看这段代码
function baz () {
//当前调用栈 : baz
// 当前函数调用位置在 global
console.log('baz');
bar();
}
function bar () {
//当前调用栈: bar ---> baz. 从左到右的栈结构
//此时的调用位置在baz中
console.log( 'bar' );
foo();
}
function foo () {
//当前调用栈: foo---> bar--->baz
//调用位置: bar
console.log ('foo');
}
baz() ----> baz调用位置
我这里依照之前的debug结果,注释出结果,就不再细分析。直观的,我们不便分析出调用位置,我们可以结合浏览器或者编辑器debug工具,调试查看函数调用栈。
我们知道this是在函数调用时绑定的(前面已经提过),上面已经理解了调用位置。那么在函数调用时,this是如何绑定的。这里需要重点结合4个法则分析。
四法则 玩转this
默认绑定
函数调用不带任何修饰时,默认绑定到window顶层对象
这个很简单,就是普通函数调用,this一般都是绑定到global.
实列代码
function foo () {
console.log(this.a)
}
var a = "oop ,blobal!!!";
var obj = {
a: 'obj'
}
//带修饰的foo调用, 带对象引用的,函数调用
obj.foo() ===> obj
//不带修饰的调用
foo() ----> global
隐式绑定
函数调用处,是否包含上下文对象。如果函数位置出,拥有上下文对象。那么this则指向它。这里我叫他带修饰的函数调用。如果函数调用位置处带修饰,比如含有对象的引用。此时函数中的this则指向这个对象.
上面实列有说明,结合字面意思的实列很容易理解。重点说说隐式绑定丢失。这个是我这次复习this的主要原因。这个概念主要包括两个小内容。
- 赋值时丢失
- 隐式赋值丢失
解释:
第一中很常见,就是将对象的方法赋值到对象外面的变量。导致this绑定的对象丢失。应用默认绑定
.......
var a = obj.foo
a() // this---> window
第二种发生在函数当作参数传递的时候, 函数当作参数传递,其实也是一个隐式赋值
比如下面这样,传入值得时候,方法内部this得绑定就会丢失去,使用默认绑定 。当然这取决函数是否运行在严格模式下。只有在非严格模式下,才会使用默认邦定。
//定时器传入隐式绑定函数
setTimout(obj.foo) //这里的foo是obj对象的一个方法,此处省略具体声明
显示绑定
通过js 内置函数 Funtion本身提供的 call , apply 来强行修该 this
实列
var obj = {
a: '2'
}
foo.call(obj)
这种方法,用来解决隐式绑定丢失问题。但是目前还没发,需要稍作修改。即使显示绑定变种,
硬绑定
实列
var obj = {
a: 2
}
// 隐式绑定丢失
var doFoo = obj.foo //this---> window
//硬绑定
var doFoo = function () {
foo.call(obj) // 等价于 ===》 obj.foo
}
doFoo() ----> this--obj
硬绑定: 总的来说是解决了隐式绑定丢失问题,但同时也带来了问题,this固定,不能在修改。
硬绑定使用改进 , bind. 负责接受值,返回
function foo (something) {
console.log(something, this.a)
return this.a + something
}
function bind (fn, obj) {
return function () {
return fn.apply(obj, arguments);
}
}
var obj = {}
var doFoo = bind(foo, obj)
var a= doFoo(3)
es5内置bind
上面的使用方式可以修改一下
var doFoo = foo.bind(obj) 这里只需要传入需要绑定this的对象即可。
new绑定
通常在js opp的时候,会用到。经常会听到程序员调侃,没得对象new一个就完事儿了。说的就是着玩儿。 在js中通过new关键字 构造函数调用,会生成新对象, 这期间构造内的this会指向 新对象
new 构造函数调用发生的事情:
1.创建新对象
2.新对象会链接__proto__
3.新对象会绑定到函数调用的this
4.如果函数调用没有返回对象,那么new 表达式调用会自动返回新对象。
优先级
在前面说的四法则中,在实际的场景中优先级。
顺序是 new 构造-----> call,apply硬绑定 -----> 隐式绑定----> 默认绑定
这里重点说说 硬绑定和 new 构造调用优先级顺序
硬绑定 通过包装函数,包装内层的显示绑定函数调用, 绑定this。这里使用es5的内置bind。他会返回一个含有显示绑定的硬绑定函数。这个外层函数会忽略this的绑定。
new构造 他会修改硬绑定的this指向。以此看来 new优先级高于硬绑定
实列
var obj = {}
function foo(something) {
this.a = something
}
var doFoo = foo.bind(obj)
doFoo (3) //通过硬绑定对obj设置属性
obj.a = 3
//通过new 改变内层this指向
var bar = new doFoo(2)
//可以看到 obj.a == 3 依旧未变
但是bar.a = 2 说明了new 改变了内部this指向
应用: 分布应用或者柯里化, new 结合bind使用。
绑定列外
null,undefined 在当作call,apply,bind 在作为this绑定对象传入时,会被应用默认绑定
场景
apply,bind在某些情况 下,不需要传入对象作为this绑定。这个时候可以使用null忽略this绑定
function foo (a, b) {
console.log(a , b)
}
foo.apply(null, [1, 2])
或者
var doFoo = foo.bind(null, 2);
doFoo(3)
使用null作为忽略this。避免不必要this的绑定. 虽然方便,但也存在问题,他会应用默认绑定。导致一系列问题.
安全this
未必免上述情况, 这里介绍一种。更安全忽略this方法
//创建一个空对象,用于this的绑定
var empty = Object.create(null) //这样创建的对象不存在原型关联
//同样是上述面列子,我们可以这样调用foo
foo.apply(empty, [2, 3])
或者
var doFoo = foo.bind(empty, 2)
doFoo(3)
这样应用默认规则