zoukankan      html  css  js  c++  java
  • JavaScript call和apply的三大用途

    -----------------------------------------------------------------《JavaScript 设计模式与开发实践》

    它两的区别:

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

    apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下
    标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传
    递给被调用的函数。

    call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,
    从第二个参数开始往后,每个参数被依次传入函数。

     用途:

    一. 改变this的指向

      这个是最常见的用途

      

    var obj1 = { 
     name: 'sven' 
    }; 
    var obj2 = { 
     name: 'anne' 
    }; 
    window.name = 'window'; 
    var getName = function(){ 
     alert ( this.name ); 
    }; 
    getName(); // 输出: window 
    getName.call( obj1 ); // 输出: sven 
    getName.call( obj2 ); // 输出: anne 
    //在开发中,经常会遇到 this 指向被不经意改变的场景,比如有一个 div 节点,div节点的 onclick 事件中的 this 本来是指向这个 div 的:
    document.getElementById( 'div1' ).onclick = function(){ 
     alert( this.id ); // 输出:div1 
    }; 
    //假如该事件函数中有一个内部函数 func,在事件内部调用 func 函数时,func 函数体内的this就指向了 window,而不是我们预期的 div,见如下代码:
    document.getElementById( 'div1' ).onclick = function(){ 
     alert( this.id ); // 输出:div1 
     var func = function(){ 
     alert ( this.id ); // 输出:undefined 
     } 
     func(); 
    }; 
    //这时候我们用 call 来修正 func 函数内的 this,使其依然指向 div:
    document.getElementById( 'div1' ).onclick = function(){ 
     var func = function(){ 
     alert ( this.id ); // 输出:div1 
     } 
     func.call( this ); 
    }; 

    二. Function.prototype.bind

    大部分高级浏览器都实现了内置的 Function.prototype.bind,用来指定函数内部的 this指向,

    即使没有原生的 Function.prototype.bind 实现,简化版的 Function.prototype.bind 实现:

    Function.prototype.bind = function( context ){ 
     var self = this; // 保存原函数
     return function(){ // 返回一个新的函数
     return self.apply( context, arguments ); // 执行新的函数的时候,会把之前传入的 context 
     // 当作新函数体内的 this 
     } 
    }; 
    var obj = { 
     name: 'sven' 
    }; 
    var func = function(){ 
     alert ( this.name ); // 输出:sven 
    }.bind( obj); 
    func(); 

    通过 Function.prototype.bind 来“包装”func 函数,并且传入一个对象 context 当作参
    数,这个 context 对象就是我们想修正的 this 对象。
    在 Function.prototype.bind 的内部实现中,我们先把 func 函数的引用保存起来,然后返回一
    个新的函数。当我们在将来执行 func 函数时,实际上先执行的是这个刚刚返回的新函数。在新
    函数内部,self.apply( context, arguments )这句代码才是执行原来的 func 函数,并且指定 context
    对象为 func 函数体内的 this。

    复杂版的 Function.prototype.bind 实现:

    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( 'sven' ); 
    console.log( b.getName() ); // 输出: 'sven'
    借用方法的第二种运用场景跟我们的关系更加密切。
    函数的参数列表 arguments 是一个类数组对象,虽然它也有“下标”,但它并非真正的数组,
    所以也不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这种情况下,我们常常
    会借用 Array.prototype 对象上的方法。比如想往 arguments 中添加一个新的元素,通常会借用
    Array.prototype.push:
    (function(){ 
     Array.prototype.push.call( arguments, 3 ); 
     console.log ( arguments ); // 输出[1,2,3] 
    })( 1, 2 ); 
    在操作 arguments 的时候,我们经常非常频繁地找 Array.prototype 对象借用方法。
    想把 arguments 转成真正的数组的时候,可以借用 Array.prototype.slice 方法;想截去
    arguments 列表中的头一个元素时,又可以借用 Array.prototype.shift 方法。那么这种机制的内
    部实现原理是什么呢?我们不妨翻开 V8 的引擎源码,以 Array.prototype.push 为例,看看 V8 引
    擎中的具体实现:
    function ArrayPush() { 
     var n = TO_UINT32( this.length ); // 被 push 的对象的 length 
     var m = %_ArgumentsLength(); // push 的参数个数
     for (var i = 0; i < m; i++) { 
     this[ i + n ] = %_Arguments( i ); // 复制元素 (1) 
     } 
     this.length = n + m; // 修正 length 属性的值 (2) 
     return this.length; 
    }; 
    通过这段代码可以看到,Array.prototype.push 实际上是一个属性复制的过程,把参数按照
    下标依次添加到被 push 的对象上面,顺便修改了这个对象的 length 属性。至于被修改的对象是
    谁,到底是数组还是类数组对象,这一点并不重要。
    由此可以推断,我们可以把“任意”对象传入 Array.prototype.push:
    var a = {}; 
    Array.prototype.push.call( a, 'first' ); 
    alert ( a.length ); // 输出:1 
    alert ( a[ 0 ] ); // first 
    这段代码在绝大部分浏览器里都能顺利执行,但由于引擎的内部实现存在差异,如果在低版
    本的 IE 浏览器中执行,必须显式地给对象 a 设置 length 属性:
    var a = { 
     length: 0 
    }; 
    前面我们之所以把“任意”两字加了双引号,是因为可以借用 Array.prototype.push 方法的对
    象还要满足以下两个条件,从 ArrayPush 函数的(1)处和(2)处也可以猜到,这个对象至少还要满足:
     对象本身要可以存取属性;
     对象的 length 属性可读写。
    对于第一个条件,对象本身存取属性并没有问题,但如果借用 Array.prototype.push 方法的
    不是一个 object 类型的数据,而是一个 number 类型的数据呢? 我们无法在 number 身上存取其他
    数据,那么从下面的测试代码可以发现,一个 number 类型的数据不可能借用到 Array.prototype. 
    push 方法:
    var a = 1; 
    Array.prototype.push.call( a, 'first' ); 
    alert ( a.length ); // 输出:undefined 
    alert ( a[ 0 ] ); // 输出:undefined 
    对于第二个条件,函数的 length 属性就是一个只读的属性,表示形参的个数,我们尝试把
    一个函数当作 this 传入 Array.prototype.push:
    var func = function(){}; 
    Array.prototype.push.call( func, 'first' ); 
    alert ( func.length ); 
    // 报错:cannot assign to read only property ‘length’ of function(){} 
  • 相关阅读:
    解决在火狐中不支持onreadystatechange的方法
    jquery ajax与分页的实现(mvc下)
    通过ChekBox设置EditText内容是否呈现密码显示
    Android自定义dialog
    Android Intent笔记
    启动另外的一个应用程序的Activity(三种方式)(转载)
    Android代码中声明一个layout
    在Linux下创建函数库
    五分钟讲清楚UML
    QT中使用QProcess启用外部程序
  • 原文地址:https://www.cnblogs.com/liamlee/p/12215800.html
Copyright © 2011-2022 走看看