《JavaScript设计模式与开发实践》之this 、 call 和 apply

this 、 call 和 apply

this

跟别的语言大相径庭的是,JavaScript的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。

this 的指向

  • 作为对象的方法调用。

    当函数作为对象的方法被调用时, this 指向该对象。

var obj = {
  a: 1,
  getA: function() {
    console.log(this === obj); //true
    return this.a; //1
  }
}

console.log(obj.getA());
  • 作为普通函数调用。

    this 总是指向全局对象window。

window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // 输出:globalName
  • 构造器调用。

    当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。

 var MyClass = function() {
   this.name = 'steven';
   return { // 显式地返回一个对象
     name: 'anne'
   }
 };
var obj = new MyClass();
console.log("使用构造器调用:" + obj.name); // 输出:anne
  • Function.prototype.call 或 Function.prototype.apply 调用。

    可以动态地改变传入函数的 this

var obj1 = {
name: 'steven',
getName: function() {
return this.name;
}
};

var obj2 = {
name: 'anne'
}
console.log(obj1.getName.call(obj2));// 输出:anne

丢失的 this

​一个例子:使用getId变量来代替document.getElementById

var getId = function(id){
  return document.getElementById(id);
};
console.log(getId('div1').id)

​ 如果getId直接 来引用 document.getElementById 之后,再调用getId ,此时就成了普通函数调用,函数内部的 this指向了window ,而不是原来的 document,会出现报错,情况如下:

 var getId = document.getElementById;
console.log(getId('div1').id); //Uncaught TypeError: Illegal invocation (非法调用)

​ 我们可以尝试利用 apply 把 document 当作 this 传入 getId 函数,帮助“修正” this:

 var getId = (function(obj) {
   return function() {
     return obj.apply(document, arguments);
   }
 })(document.getElementById);
console.log(getId('div1').id)

call和apply

call和apply 的区别

Function.prototype.call 和 Function.prototype.apply 都是非常常用的方法。它们的作用一模一样,区别仅在于传入参数形式的不同

  • apply 接受两个参数:

    • 第一个参数指定了函数体内 this 对象的指向
    • 第二个参数为一个带下标的集合(数组或类数组), apply方法把这个集合中的元素作为参数传递给被调用的函数
    var func = function( a, b, c ){
    alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
    };
    func.apply( null, [ 1, 2, 3 ] );//1参为null,函数体内的 this 会指向默认的宿主对象,即window
    
  • call 传入的参数数量不固定

    • 第一个参数指定了函数体内 this 对象的指向
    • 从第二个参数开始往后,每个参数被依次传入函数
    var func = function( a, b, c ){
    alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
    };
    func.call( null, 1, 2, 3 );//1参为null,函数体内的 this 会指向默认的宿主对象,即window
    

    当调用一个函数时,JavaScript 的解释器并不会计较形参和实参在数量、类型以及顺序上的区别,JavaScript的参数在内部就是用一个数组来表示的。从这个意义上说, applycall的使用率更高,我们不必关心具体有多少参数被传入函数,只要用 apply 一股脑地推过去就可以了。

    当使用 call或者 apply的时候,如果我们传入的第一个参数为 null,函数体内的 this 会指向默认的宿主对象,在浏览器中则是 window ,但如果是在严格模式下,函数体内的 this还是为 null

    有时候我们使用 call或者 apply的目的不在于指定 this指向,而是另有用途,比如借用其他对象的方法。那么我们可以传入 null来代替某个具体的对象。

call 和 apply 的用途

改变 this 指向

var obj1 = {
  name: 'steven'
};
var obj2 = {
  name: 'anne'
};
window.name = 'window';
var getName = function(name) {
  console.log(this.name);
};
getName(); //'window'
getName.apply(obj1); //'steven'
getName.apply(obj2); //'anne'

在执行getName.apply(obj1)时, getName函数体内的 this就指向 obj1对象,实际相当于

var getName = function(name) {
  console.log(obj1.name);
};

一个点击的实例场景:

document.getElementById('div1').onclick = function() {
  var _that = this;  //需要额外申明一个中转变量来存储对象的this
  var foo = function() {
    alert(_that.id);
  }
  return foo();
};

在使用applycall后:

document.getElementById('div1').onclick = function() {
  var foo = function() {
    alert(this.id);
  }
  foo.apply(this);
  return foo();
};

Function.prototype.bind

大部分现代浏览器都实现了内置的 Function.prototype.bind用来指定函数内部的 this 指向,如果没有的话模拟起来不困难:

Function.prototype.bind = function(context) {
   var self = this; //保存原函数
   return function() {
     // 这句代码才是执行原来的 func 函数,并且指定 context对象为 func 函数体内的 this ,也是我们想修正的 this 对象
     return self.apply(context, arguments);
   }
 }
var obj = {
   name: 'steven'
 };
var func = function() {
  alert(this.name); //输出'steven'
}.bind(obj);

func();

再稍微复杂一些,使得可以往func函数内预先填写一些参数

Function.prototype.bind = function(){
var self = this, // 保存原函数
context = [].shift.call( arguments ), // 需要绑定的 this 上下文
args = [].slice.call( arguments ); // 剩余的参数转成数组
return function(){ // 返回一个新的函数
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
// 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
// 并且组合两次分别传入的参数,作为新函数的参数
}
};
var obj = {
name: 'sven'
};
var func = function( a, b, c, d ){
alert ( this.name ); // 输出:sven
alert ( [ a, b, c, d ] ) // 输出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );

借用其他对象的方法

  • 场景一:鸠占鹊巢
 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('steven');
console.log(b.getName());
  • 场景二:arguments中添加一个新的元素,通常会借用Array.prototype.push
(function(){
  Array.prototype.push.call(arguments,3);
  console.log(arguments);
})(1,2,5,c)

在操作 arguments的时候,我们经常非常频繁地找Array.prototype对象借用方法,例如:

  • 想把 arguments 转成真正的数组的时候,可以借用 Array.prototype.slice 方法。
  • 想截去arguments 列表中的头一个元素时,可以借用Array.prototype.shift方法。

我们甚至可以把“任意”对象传入 Array.prototype.push,但是对象要满足以下两个条件:

  1. 对象本身要可以存取属性
  2. 对象的 length 属性可读写
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容