授业至今,技艺日有进益,许是看的多了,先时似是而非的东西,今时也多有顿悟,call和apply学的早,用得少,同是作用于this,bind就用的多,但这个函数扩展方法是ECMAscript5里的,IE[6-8]不支持,移动端可以放心用,这里说与某家主公知道。
this之于call、apply和bind,五花肉之于东坡肉、红烧肉和回锅肉,反复思量,深感经世以来比喻之周全者未有过于此者。因其关键,蒙师授业之初费了大周折讲它,终了一句话“.前头谁调用,this就是谁,没有.前头,就是window”醍醐灌顶。
js中this总是指向一个对象,情况有4种
1、作为对象的方法调用
var persion = { name : "haha", getName : function(){ console.log(this === persion);//true console.log(this.name);//"haha" }};persion.getName();
2、作为普通函数调用
此时this总是指向全局对象window
window.name = "global"; var getName = function(){ return this.name;}console.log(getName());//"global"
或者
window.name = "global";var persion = { name : "haha", getName : function(){ return this.name; }}console.log(persion.getName());//"haha"var getName = persion.getName;console.log(getName());//"global"
DOM节点的事件函数内部,有个局部的callback方法,callback被当做普通函数调用时,callback内部的this指向了window,但是我们想让它指向该DOM节点
document.getElementById("btn").onclick = function(){ console.log(this.id)//"btn" var callback = function(){ console.log(this) //window console.log(this.id) //undefined } callback();}
常用的解决方案
document.getElementById("btn").onclick = function(){ console.log(this.id)//"btn" var that = this; var callback = function(){ console.log(that.id)//"btn" } callback();}
在ECMAScript5的strict严格模式下
document.getElementById("btn").onclick = function(){ "use strict" console.log(this.id)//"btn" var callback = function(){ console.log(this) //undefined console.log(this.id) //undefined } callback();}
strict这种模式多余的很
3、构造器调用
js函数大多数都可以当做构造器使用,当用new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象
var Persion = function(){ this.name = "haha";}var persion1 = new Persion();console.log(persion1.name);//"haha"
用new调用构造器时,如果构造器显式地返回了一个object类型的对象,那么运行的结果就是返回的这个对象,不是之前期待的那个this
var Persion = function(){ this.name = "haha"; return { name : "hehe" }}var persion1 = new Persion();console.log(persion1.name);//"hehe"
构造器不显式返回数据或者返回非对象类型的数据,不会造成上述问题
var Persion = function(){ this.name = "haha"; return "hehe";}var persion1 = new Persion();console.log(persion1.name);//"haha"
4、call和apply调用
call和apply可以动态地改变传入函数的this
var persion1 = { name : "haha", getName : function(){ console.log(this.name + " from persion1"); }};var persion2 = { name : "hehe", getName : function(){ console.log(this.name + " from persion2"); }};console.log(persion1.getName.call(persion2));// "hehe from persion2"
call和apply
call和apply都是改变this指向,区别在于参数传入的形式不同
var func = function(a,b,c){ console.log([a,b,c]);//[1,2,3]}func.call(null,1,2,3);func.apply(null,[1,2,3]);
call和apply第一个参数传入的都是函数体内的this指向,不同在于call第二参数往后依次传入与函数体形参相对应的实参,apply第二个参数是一个实参数组,数组中的元素同样与函数体的形参相对应。
孰用孰不用,有一种说法apply比call的使用效率更高,因为在js的参数在内部就是用一个数组来表示的,用arguments可以访问到。
使用call和apply的时候传入的第一个参数是null,函数执行的时候this会指向默认的宿主对象,也就是window。
var func = function(a,b,c){ console.log(this === window);//true}func.call(null,1,2,3);
严格模式下情况就不一样了
var func = function(a,b,c){ console.log(this === null);//true}func.call(null,1,2,3);
它们的用途
1、改变this指向
var persion1 = { name : "haha", getName : function(){ console.log(this.name + " from persion1"); }};var persion2 = { name : "hehe", getName : function(){ console.log(this.name + " from persion2"); }};console.log(persion1.getName.call(persion2));// "hehe from persion2"
一样的代码再来一遍
2、模拟bind方法
bind方法低级浏览器里没有,但可以用apply来封装
Function.prototype.bind = function(context){ var self = this; return function(){ return self.apply(context,arguments); }}var obj = { name:"haha"}var getName = function(){ console.log(this.name);//"haha"}.bind(obj);
3、借用其他对象方法
var A = function(name){ this.name = name;}var B = function(){ A.apply(this,arguments);}B.prototype.getName = function(){ return this.name;}var b = new B('hehe');console.log(b.getName());//"hehe"
上面是的场景是“借用构造函数”,通过这种技术,可以实现出类似继承的效果,此外还有一种借用场景
(function(){ Array.prototype.push.apply(arguments,3); console.log(arguments);//[1,2,3]})(1,2)
函数的参数列表arguments是一个类数组
(function(){ console.log(typeof arguments);//object Array.prototype.push.call(arguments,3); console.log(arguments);//[1,2,3]})(1,2)
本身不具备push方法,可以使用call或apply借用Array.prototype对象的方法,前提是借用方法的对象要可以存取属性、length属性可读写
《JavaScript设计模式与开发实践》是本好书