你的位置:首页 > Java教程

[Java教程]原生JS:Function对象(apply、call、bind)详解


Function对象(apply、call、bind)

本文参考MDN做的详细整理,方便大家参考[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript)

Function 构造器会创建一个新的 Function 对象。 在 JavaScript 中每个函数都是一个Function对象。

构造器

 new Function ([arg1[, arg2[, ...argN]],] functionBody)

  • arg1, arg2, ... arg被函数使用的参数的名称必须是有效的JavaScript标识符的字符串
  • functionBody 一个含有包括函数定义的JavaScript语句的字符串。
  • 以调用函数的方式调用Function的构造函数 (不是用new关键字) 跟以构造函数来调用是一样的.

例:var adder = new Function("a", "b", "return a + b"); // 创建了一个能返回两个参数和的函数

       adder(2, 6); //  8

使用Function构造器生成的Function对象是在函数创建时解析的。这比你使用函数声明或者函数表达式(function)并在你的代码中调用更为低效,因为使用后者创建的函数是跟其他代码一起解析的。

所有被传递到构造函数中的参数,都将被视为将被创建的函数的参数,并且是相同的标示符名称和传递顺序。

使用Function构造器生成的函数,并不会在创建它们的上下文中创建闭包;它们一般在全局作用域中被创建。当运行这些函数的时候,它们只能访问自己的本地变量和全局变量,不能访问Function构造器被调用生成的上下文的作用域。这和使用带有函数表达式代码的 eval 不同。

属性:

Function.length

length 是函数对象的一个属性值,指明该函数期望多少个参数,即形参的个数。数量不包括剩余参数。相比之下,  arguments.length 是函数被调用时实际传参的个数。

  • Function 构造器本身也是个Function。他的 length 属性值为 1 。该属性 Writable: false, Enumerable: false, Configurable: true.
  • Function  原型对象的 length 属性值为 0 。

方法

Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])

在一个对象thisArg的上下文中应用另一个对象的方法;参数能够以列表形式传入,即在使用一个指定的this值(thisArg)和若干个指定的参数值的前提下调用某个函数或方法。

  • 需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
call方法的应用:
  • 使用call方法调用函数并且指定上下文的'this'
function greet() {  var reply = [this.person, 'Is An Awesome', this.role].join(' ');  console.log(reply);}var a = {  person: 'Douglas Crockford', role: 'Javascript Developer'};greet.call(a); // Douglas Crockford Is An Awesome Javascript Developer

  • 使用call方法调用父构造函数:在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承

function Food(name, price) {  Product.call(this, name, price);  this.category = 'food';}

 

  • 使用call方法调用匿名闭包函数,以达到绑定this和传参数的目的
for(var i =0;i<10;i++){  setTimeout(function(){    console.log(i);  }.call(null,i),0)}//可以顺利输出0~9,如果不用call,则i的最终值,输出10个10

 

Function.prototype.apply(thisArg[, argsArray])

在一个对象的上下文中应用另一个对象的方法;参数能够以数组形式传入。

  • thisArg 在函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
  • argsArray 一个数组或者类数组对象(ES5新增,Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。),其中的数组元素将作为单独的参数传给函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。
  • 使用 apply和call, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
  • 可以使用 arguments  对象作为 argsArray 参数。 arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象。

使用apply和内置函数

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

var numbers = [5, 6, 2, 3, 7];var max = Math.max.apply(null, numbers);var min = Math.min.apply(null, numbers);


  • 当你对一个方法传入非常多的参数 (比如超过1W多个参数) 时, 就非常有可能会导致越界问题, 这个临界值是根据不同的 JavaScript 引擎而定的 (JavaScript 核心中已经做了硬编码  参数个数限制在65536),因为这个限制(实际上也是任何用到超大栈空间的行为的自然表现)是未指定的. 有些引擎会抛出异常.  更糟糕的是其他引擎会直接限制传入到方法的参数个数,导致参数丢失.
  • 如果你的参数数组可能非常大, 那么推荐使用下面这种策略来处理: 将参数数组切块后循环传入目标方法:
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]);



 

Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])

bind()方法会创建一个新函数(新函数与被调函数具有相同的函数体)称为绑定函数。

  • 当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind()方法的第二个以及以后的参数作为绑定函数的预设参数,加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数.
  • 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
function Point(x, y) {  this.x = x;  this.y = y;}Point.prototype.add = function() {  return this.x + this.y;};var p = new Point(1, 2);console.log(p); //Point { x: 1, y: 2 }var YAxisPoint = Point.bind({}, 5);var x = new YAxisPoint(6);console.log(x); //Point { x: 5, y: 6 }console.log(x.add()); //11 



bind用法:

bind() 最简单的用法是为原函数创建一个绑定函数,绑定到指定的对象上,使这个函数不论怎么调用都有同样的 this 值

this.x = 9;var module = {  x: 81,  getX: function() { return this.x; }};module.getX(); // 返回 81var retrieveX = module.getX;retrieveX(); // 返回 9, 在这种情况下,"this"指向全部作用域// 创建一个新函数,将"this"绑定到module对象// 新手可能会被全局的x变量和module里的属性x所迷惑var boundGetX = retrieveX.bind(module);boundGetX(); // 返回 81


bind()的另一个最简单的用法是使一个函数拥有预设的初始参数

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

function foo(x,y){  return y ? x+y : foo.bind(void 0,x)}console.log(foo(1,4)); //3console.log(foo(1)(4)); //3


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

function LateBloomer() {  this.petalCount = Math.ceil(Math.random() * 12) + 1;}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\'方法


Function.prototype.toString()

获取函数的实现源码的字符串。覆盖了 Object.prototype.toString 方法。