this
JavaScript 中 this 总是指向一个对象
this 的指向
- 作为对象的方法调用
this 指向该对象,如:
1 | var obj = { |
- 作为普通函数调用
当函数不作为对象的属性被调用时,普通函数的 this 总是指向全局对象,浏览器里就是 window 对象。
1 | var name = "globalName"; |
obj.getName()
作为 obj 对象的属性被调用,this 指向 obj 对象;
getName()
使用变量 getName 引用 obj.getName,此时是函数调用方式,this 指向全局 window;
在严格模式,情况有所不同:this 不会指向全局对象,而是 undefined:
1 | function func() { |
当函数作为某个对象的方法调用时,this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
1 | var gName = "The window"; |
创建了一个全局对象 gName
,这个对象包含一个方法 getName()
, 这个方法返回一个匿名函数,这个匿名函数返回 this.name
。因此调用 gObject.getName()()
会立即执行匿名函数,并返回一个字符串 'The window'
。
为什么匿名函数没有取得包含作用域的 this 对象呢?
每个函数再被调用的时候,会自动取得两个特殊变量:this 和 arguments,内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
所以,可以在外部作用域中设置一个变量来保存 this 对象:
1 | var gName = "The window"; |
当然,arguments 对象也可以如此使用:对该对象的引用保存到另一个闭包能够访问的变量中。
- 构造器调用
当使用 new 运算符调用函数时,该函数会返回一个对象,一般情况下,构造器里的 this 指向返回的这个对象,如:
1 | var MyClass = function() { |
但是,当显式返回一个 object 类型的对象时,那最终会返回这个对象,并不是之前的 this:
1 | var MyClass = function() { |
- Function.prototype.call 或 Function.prototype.apply 调用
call 和 apply 可以动态地改变传入函数的 this:
1 | var personObj = { |
丢失的 this
我们一般会重写这个获取 id 的方法:
1 | var getId = function(id) { |
那可不可以这样呢:
1 | getId2 = document.getElementById; |
结果直接报错,当 getElementById
方法作为 document 对象的属性被调用时, 方法内部的 this 是指向 document 的。如果 getId2('divBox')
,相当于是普通函数调用,函数内部的 this 指向的是 window。
所以,按照这个思路,我们可以这样模拟一下它的实现:
1 | document.getElementById = (function(func) { |
call 和 apply
fun.apply(thisArg, [argsArray])
fun.call(thisArg, arg1, arg2, …)
在函数式编程中,call 和 apply 方法尤为有用,两者用法一致,只是传参的形式上有所区别而已。
区别
apply() 接受两个参数,第一个参数指定了函数体内 this 对象,第二个是数组或者类数组,apply() 方法将这个集合中的元素作为参数传递给被调用的函数。
call() 方法的作用和 apply() 方法类似,区别就是 call()方法接受的是参数列表,而 apply()方法接受的是一个参数数组。
第一个参数为 null,函数体内的 this 会指向默认的宿主对象,但是在严格模式下,依然是 null。
1 | var applyFunc = function(a, b, c) { |
用途
- 改变 this 指向
假如在一个点击事件函数中有一个内部函数 func,当点击事件被触发时,就会出现如下情况:
1 | document.getElementById("divBox").onclick = function() { |
这时,我们用 call() 来改变一下:
1 | document.getElementById("divBox").onclick = function() { |
- 模拟 bind 方法
function.bind(thisArg[, arg1[, arg2[, …]]])
bind()方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
1 | Function.prototype.bind = function(context) { |
这是一个简化版的 Function.prototype.bind
实现,self.apply(context, arguments)
才是执行原来的 bindFunc 函数,并且指定 context 对象为 bindFunc 函数体内的 this。
我们再继续修改下,使之可以预先添加一些参数:
1 | Function.prototype.bind = function() { |
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
,执行新函数的时候,会把之前传入的 context 作为 this,[].slice.call(arguments)
将新函数传入的参数转化为数组,并作为[].concat.call(args)
的给定参数,组合两次,作为新函数最终的参数。
- 借用其他对象的方法
第一种,”借用构造函数“实现一些类似继承的效果:
1 | var A = function(name) { |
第二种,给类数组对象使用数组方法,比如:
1 | (function() { |
再比如之前用到的,把 arguments 转成真正的数组的时候可以借用 Array.prototype.slice.call(arguments)
,想截去头一个元素时,借用Array.prototype.shift.call(arguments)
虽然我们可以把”任意“对象传入 Array.prototype.push
:
1 | var aObj = {}; |
但是,这个对象也得满足以下两个条件:
- 对象本身要可以存取属性
- 对象的 length 属性可读写
如果是其他类型,比如 number,无法存取;比如函数,length 属性不可写,使用 call 或 apply 就会报错:
1 | var num = 1; |
学习资料:
- 《JavaScript 高程 3》第七章
- 《JavaScript 设计模式与开发实践 · 曾探》第 2 章
发布: