美文网首页
《JavaScript设计模式与开发实践》之this 、 cal

《JavaScript设计模式与开发实践》之this 、 cal

作者: 我是白夜 | 来源:发表于2017-06-27 18:31 被阅读0次

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 属性可读写

相关文章

网友评论

      本文标题:《JavaScript设计模式与开发实践》之this 、 cal

      本文链接:https://www.haomeiwen.com/subject/xlgecxtx.html