call()、apply()、bind()

bind()方法会建立一个新函数,当这个新函数被调用时,它的this值是传递给bind()的第一个参数, 它的参数是bind()的其余参数和其本来的参数.javascript

apply() 与 call() 很是类似,不一样之处在于提供参数的方式:java

call() 方法接受的是若干个参数的列表
算法

apply() 方法接受的是一个包含多个参数的数组。apply 可使用数组字面量,如 fun.apply(this, ['eat', 'bananas']),或数组对象,如  fun.apply(this, new Array('eat', 'bananas'))。数组


 

call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法.浏览器

语法闭包

fun.call(thisArg[, arg1[, arg2[, ...]]])

参数

thisArg:
fun函数运行时指定的this须要注意的是,指定的this值并不必定是该函数执行时真正的this值,若是这个函数处于非严格模式下,则指定为nullundefinedthis值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
arg1, arg2, ...
指定的参数列表。

示例

使用call方法调用父构造函数

在一个子构造函数中,你能够经过调用父构造函数的 call 方法来实现继承相似于Java中的写法。下例中,使用 Food 和 Toy 构造函数建立的对象实例都会拥有在 Product 构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。app

function Product(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' +
                      this.name + ' with a negative price'); } } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } //等同于
function Food(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' +
                this.name + ' with a negative price'); } this.category = 'food'; } //function Toy 同上
function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food('feta', 5); var fun = new Toy('robot', 40);

使用call方法调用匿名函数

在下例中的for循环体内,咱们建立了一个匿名函数,而后经过调用该函数的call方法,将每一个数组元素做为指定的this值执行了那个匿名函数。这个匿名函数的主要目的是给每一个数组元素对象添加一个print方法,这个print方法能够打印出各元素在数组中的正确索引号。固然,这里不是必须得让数组元素做为this值传入那个匿名函数(普通参数就能够),目的是为了演示call的用法。dom

var animals = [ {species: 'Lion', name: 'King'}, {species: 'Whale', name: 'Fail'} ]; for (var i = 0; i < animals.length; i++) { (function (i) { this.print = function () { console.log('#' + i  + ' ' + this.species + ': ' + this.name); } this.print(); }).call(animals[i], i); }

使用call方法调用函数而且指定上下文的'this'

在下面的例子中,当调用 greet 方法的时候,该方法的 this 值会绑定到 对象。函数

function greet() { var reply = [this.person, 'Is An Awesome', this.role].join(' '); console.log(reply); } var i = { person: 'Douglas Crockford', role: 'Javascript Developer' }; greet.call(i); // Douglas Crockford Is An Awesome Javascript Developer

apply() 方法在指定 this 值和参数(参数以数组或类数组对象的形式存在)的状况下调用某个函数。oop

语法
fun.apply(thisArg[, argsArray])

thisArg( 和call同样 )
argsArray :
一个数组或者类数组对象,其中的数组元素将做为单独的参数传给 fun 函数。若是该参数的值为null 或 undefined,则表示不须要传入任何参数。从ECMAScript 5 开始可使用类数组对象。

可使用 arguments 对象做为 argsArray 参数。 arguments 是一个函数的局部变量。 它能够被用做被调用对象的全部未指定的参数。 这样,你在使用apply函数的时候就不须要知道被调用对象的全部参数。 你可使用arguments来把全部的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。

从 ECMAScript 第5版开始,可使用任何种类的类数组对象,就是说只要有一个 length 属性和[0...length) 范围的整数属性。例如如今可使用 NodeList 或一个本身定义的相似 {'length': 2, '0': 'eat', '1': 'bananas'} 形式的对象。

须要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。若是传入类数组对象,它们会抛出异常。

示例

使用apply来连接构造器

你可使用apply来给一个对象连接构造器,相似于Java. 在接下来的例子中咱们会建立一个叫作construct的全局的Function函数,来使你可以在构造器中使用一个类数组对象而非参数列表。

Function.prototype.construct = function (aArgs) { var oNew = Object.create(this.prototype); this.apply(oNew, aArgs); return oNew; };

注意: 上面使用的Object.create()方法相对来讲比较新。另外一种可选的方法是使用闭包,请考虑以下替代方法:

Function.prototype.construct = function(aArgs) { var fConstructor = this, fNewConstr = function() { fConstructor.apply(this, aArgs); }; fNewConstr.prototype = fConstructor.prototype; return new fNewConstr(); };

案例:

function MyConstructor () { for (var nProp = 0; nProp < arguments.length; nProp++) { this["property" + nProp] = arguments[nProp]; } } var myArray = [4, "Hello world!", false]; var myInstance = MyConstructor.construct(myArray); console.log(myInstance.property1); // logs "Hello world!"
console.log(myInstance instanceof MyConstructor); // logs "true"
console.log(myInstance.constructor);              // logs "MyConstructor"

注意: 这个非native的Function.construct方法没法和一些native构造器(例如Date)一块儿使用。 在这种状况下你必须使用Function.bind方法(例如,想象有以下一个数组要用在Date构造器中: [2012, 11, 4];这时你须要这样写: new (Function.prototype.bind.apply(Date, [null].concat([2012, 11, 4])))()– -不管如何这不是最好的实现方式而且也许不应用在任何生产环境中).

使用apply和内置函数

聪明的apply用法容许你在某些原本须要写成遍历数组变量的任务中使用内建的函数。在接下里的例子中咱们会使用Math.max/Math.min来找出一个数组中的最大/最小值。

/* min/max number in an array */
var numbers = [5, 6, 2, 3, 7]; /* using Math.min/Math.max apply */
var max = Math.max.apply(null, numbers); /* This about equal to Math.max(numbers[0], ...) or Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers); /* vs. simple loop based algorithm */ max = -Infinity, min = +Infinity; for (var i = 0; i < numbers.length; i++) { if (numbers[i] > max) max = numbers[i]; if (numbers[i] < min) min = numbers[i]; }

可是小心:若是用上面的方式调用 apply, 你极可能会遇到方法参数个数越界的问题. 当你对一个方法传入很是多的参数 (好比超过1W多个参数) 时, 就很是有可能会致使越界问题, 这个临界值是根据不一样的 JavaScript 引擎而定的 (JavaScript 核心中已经作了硬编码  参数个数限制在65536),由于这个限制(实际上也是任何用到超大栈空间的行为的天然表现)是未指定的. 有些引擎会抛出异常.  更糟糕的是其余引擎会直接限制传入到方法的参数个数,致使参数丢失. (举个例子: 若是某个引擎限制了方法参数最多为4个 [实际真正的参数个数限制固然要高得多了, 这里只是打个比方], 上面的代码中, 真正经过 apply传到目标方法中的参数为 5, 6, 2, 3, 而不是完整的 numbers 数组.) 若是你的参数数组可能很是大, 那么推荐使用下面这种策略来处理: 将参数数组切块后循环传入目标方法:

function minOfArray(arr) { var min = Infinity; var QUANTUM = 32768; for (var i = 0, len = arr.length; i < len; i += QUANTUM) { var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len))); min = Math.min(submin, min); } return min; } var min = minOfArray([5, 6, 2, 3, 7]);

在"monkey-patching"中使用apply

Apply能够做为monkey-patch一个Firefox或JS库内建函数的最好方式。对于someobject.foo 函数,你能够用一种旁门左道的方式来修改这个函数,像这样:

var originalfoo = someobject.foo; someobject.foo = function() { //在调用函数前干些什么
 console.log(arguments); //像正常调用这个函数同样来进行调用:
  originalfoo.apply(this,arguments); //在这里作一些调用以后的事情。
}

语法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数

thisArg
当绑定函数被调用时,该参数会做为原函数运行时的 this 指向。当使用new 操做符调用绑定函数时,该参数无效。
arg1, arg2, ...
当绑定函数被调用时,这些参数加上绑定函数自己的参数会按照顺序做为原函数运行时的参数。

返回值

      返回由指定的this值和初始化参数改造的原函数拷贝

 

描述

 
bind() 函数会建立一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具备相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操做符建立对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
 
 
 

示例

 

建立绑定函数

 

bind() 最简单的用法是建立一个函数,使这个函数不论怎么调用都有一样的 this 值。JavaScript新手常常犯的一个错误是将一个方法从对象中拿出来,而后再调用,但愿方法中的 this 是原来的对象。(好比在回调中传入这个方法。)若是不作特殊处理的话,通常会丢失原来的对象。从原来的函数和原来的对象建立一个绑定函数,则能很漂亮地解决这个问题:

this.x = 9; var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 返回 81

var retrieveX = module.getX; retrieveX(); // 返回 9, 在这种状况下,"this"指向全局做用域

// 建立一个新函数,将"this"绑定到module对象 // 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module); boundGetX(); // 返回 81

偏函数

bind()的另外一个最简单的用法是使一个函数拥有预设的初始参数。这些参数(若是有的话)做为bind()的第二个参数跟在this(或其余对象)后面,以后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

 
function list() { return Array.prototype.slice.call(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37); var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
 

配合 setTimeout

在默认状况下,使用 window.setTimeout() 时,this 关键字会指向 window (或全局)对象。当使用类的方法时,须要 this 引用类的实例,你可能须要显式地把 this 绑定到回调函数以便继续使用实例。

function LateBloomer() { this.petalCount = Math.ceil(Math.random() * 12) + 1; } // Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() { window.setTimeout(this.declare.bind(this), 1000); }; LateBloomer.prototype.declare = function() { console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!'); }; var flower = new LateBloomer(); flower.bloom(); // 一秒钟后, 调用'declare'方法

做为构造函数使用的绑定函数

警告 :这部分演示了 JavaScript 的能力而且记录了 bind() 的超前用法。如下展现的方法并非最佳的解决方案且可能不该该用在任何生产环境中。

天然而然地,绑定函数适用于用new操做符 new 去构造一个由目标函数建立的新的实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。然而, 原先提供的那些参数仍然会被前置到构造函数调用的前面。

function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function() { return this.x + ',' + this.y; }; var p = new Point(1, 2); p.toString(); // '1,2'

var emptyObj = {}; var YAxisPoint = Point.bind(emptyObj, 0/*x*/); // 如下这行代码在 polyfill 不支持, // 在原生的bind方法运行没问题: //polyfill的bind方法若是加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},
  那么也能够支持)
var YAxisPoint = Point.bind(null, 0/*x*/); var axisPoint = new YAxisPoint(5); axisPoint.toString(); // '0,5' axisPoint instanceof Point; // true axisPoint instanceof YAxisPoint; // true new Point(17, 42) instanceof YAxisPoint; // true

你知道不须要作特别的处理就能够用new操做符 new 建立一个绑定函数。必然地,你须要知道不须要作特别处理就能够建立一个能够被直接调用的绑定函数,即便你更但愿绑定函数是用new操做符 new来调用。

// 这个例子能够直接在你的 javascript 控制台运行 // ...接着上面的代码继续(译注:

// 仍然能做为一个普通函数来调用 // (即便一般来讲这个不是被指望发生的)
YAxisPoint(13); emptyObj.x + ',' + emptyObj.y;   // '0,13'

若是你但愿一个绑定函数只支持使用new操做符 new,或者只能直接调用它,那么模板函数必须强制执行那限制。

快捷调用

在你想要为一个须要特定的 this 值的函数建立一个捷径(shortcut)的时候,bind() 方法也很好用。

你能够用 Array.prototype.slice 来将一个相似于数组的对象(array-like object)转换成一个真正的数组,就拿它来举例子吧。你能够建立这样一个捷径:

 
var slice = Array.prototype.slice; // ...
 slice.apply(arguments);
 

用 bind() 可使这个过程变得简单。在下面这段代码里面,slice 是 Function.prototype的 call() 方法的绑定函数,而且将 Array.prototype 的 slice() 方法做为 this 的值。这意味着咱们压根儿用不着上面那个 apply() 调用了。

// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); // ...
 slice(arguments);

Polyfill(兼容旧浏览器)

bind 函数在 ECMA-262 第五版才被加入;它可能没法在全部浏览器上运行。你能够部份地在脚本开头加入如下代码,就能使它运做,让不支持的浏览器也能使用 bind() 功能。

if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }

上述算法和实际的实现算法还有许多其余的不一样 (尽管可能还有其余不一样之处,却没有那个必要去穷尽):

  • 这部分实现依赖于Array.prototype.slice() Array.prototype.concat(), Function.prototype.call()这些原生方法。
  • 这部分实现建立的函数的实现并无caller 以及会在 get,set或者deletion上抛出TypeError错误的 arguments 属性这两个不可改变的“毒药” 。(假如环境支持{jsxref("Object.defineProperty")}}, 或者实现支持__defineGetter__ and__defineSetter__ 扩展)
  • 这部分实现建立的函数有 prototype 属性。(正确的绑定函数没有的)
  • 这部分实现建立的绑定函数全部的 length 属性并非同ECMA-262标准一致的:它的 length 是0,而在实际的实现中根据目标函数的 length 和预先指定的参数个数可能会返回非零的 length。
相关文章
相关标签/搜索