引用类型之函数

135 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

在前面值类型和引用类型这篇文章中我们已经简单介绍了函数的概念,现在开始详细介绍一下函数(Function对象)的用法。

基本介绍

每个 JavaScript 函数实际上都是一个 Function 对象。可以通过运行 (function(){}).constructor === Function // true 验证。像其他对象一样,Function对象也有自己的构造函数,实例属性和实例方法

构造函数

创建一个新的 Function 对象。直接调用此构造函数可以动态创建函数,Function 构造函数创建的函数只能在全局作用域中运行。

1. 语法
new Function(functionBody)
new Function(arg0, functionBody)
new Function(arg0, arg1, functionBody)
new Function(arg0, arg1, /* … ,*/ argN, functionBody)

Function(functionBody)
Function(arg0, functionBody)
Function(arg0, arg1, functionBody)
Function(arg0, arg1, /* … ,*/ argN, functionBody)
2. 用法
const recursiveFn = new Function("count", `
(function recursiveFn(count) {
  if (count < 0) {
    return;
  }
  console.log(count);
  recursiveFn(count - 1);
})(count);
`);

实例属性

1. Function.arguments

function.arguments 属性代表传入函数的实参,它是一个类数组对象。这个实例属性已经被弃用了,就不详细介绍了。

2. Function.caller

返回调用指定函数的函数。这个实例属性不建议使用,了解一下概念就可以。

3. Function.displayName

function.displayName 属性获取函数的显示名称。这个实例属性也不建议使用,了解一下概念就可以。

4. Function.length

length 属性指明函数的形参个数。这个实例属性是比较重要的一个属性,我们调用函数的时候一般都会涉及到参数。需要注意的是Functioneference/Global_Objects/Function)。它的 length 属性值为 1。Function.prototype对象的 length 属性值为 0。

console.log(Function.length); // 1

console.log((() => {}).length); // 0
console.log(((a) => {}).length); // 1
console.log(((a, b) => {}).length); // 2 etc.

console.log(((...args) => {}).length);
// 0, 剩余参数不计算在内

console.log(((a, b = 1, c) => {}).length);
// 1, 只有第一个具有默认值的参数之前的参数才会被计算
5. Function.name

function.name 属性返回函数实例的名称。name 属性是只读的,不能用赋值操作符修改, 需要借助Object.defineProperty().

function doSomething() { }
doSomething.name;  // "doSomething"

(new Function).name; // "anonymous"

实例方法

下面介绍的这几个实例方法在平时的开发中经常用到。

1. Function.prototype.apply()

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象的形式提供的参数。

1.1 语法

apply(thisArg)
apply(thisArg, argsArray)

1.2 参数说明

  • thisArg:在 func 函数运行时使用的 this 值。
  • argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null或 undefined,则表示不需要传入任何参数。

1.3 应用

1.3.1 将数组各项添加到另一个数组

const array = ['a', 'b'];
const elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

1.3.2 apply替代内置函数,避免循环

// 使用 Math.min/Math.max 以及 apply 函数时的代码
let max = Math.max.apply(null, numbers); // 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..)
let min = Math.min.apply(null, numbers);

1.3.3 链接构造器(改变this指向)

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

call()  方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。和上面的apply类似,只是参数不同。

2.1 语法

function.call(thisArg, arg1, arg2, ...)

call() 允许为不同的对象分配和调用属于一个对象的函数/方法。

call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

2.2 参数说明

  • thisArg:可选的。在 function 函数运行时使用的 this 值。
  • arg1, arg2, ...:指定的参数列表。

2.3 应用

2.3.1 调用父类构造函数

function Product(name, price) {
  this.name = name;
  this.price = price;
}

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

2.3.2 调用匿名函数

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);
}

2.3.3 改变this上下文

function greet() {
  var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
  console.log(reply);
}

var obj = {
  animal: 'cats', sleepDuration: '12 and 16 hours'
};

greet.call(obj);  // cats typically sleep between 12 and 16 hours

2.3.4 调用函数并不指定第一个参数

var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call();  // sData value is Wisen
3. Function.prototype.bind()

bind()  方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

bind()  函数会创建一个新的绑定函数,具有以下内部属性:

  • [[BoundTargetFunction]]  - 包装的函数对象
  • [[BoundThis]]  - 在调用包装函数时始终作为 this 值传递的值。
  • [[BoundArguments]]  - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。
  • [[Call]]  - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数的列表。

3.1 语法

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

3.2 参数说明

  • thisArg:调用绑定函数时作为 this 参数传递给目标函数的值。如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg
  • arg1, arg2, ...:当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

3.3 应用

3.3.1 创建绑定函数

this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

3.3.2 在定时器中使用

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
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' 方法  I am a beautiful flower with 9 petals!
4. Function.prototype.toString()

toString()  方法返回一个表示当前函数源代码的字符串。在 Function 需要表示为字符串时,JavaScript 会自动调用函数的 toString 方法.

function test(fn) {
  console.log(fn.toString());
}

function f() {}
class A { a() {} }
function* g() {}

test(f); // "function f() {}"
test(A); // "class A { a() {} }"
test(g); // "function* g() {}"
test((a) => a); // "(a) => a"
test({ a() {} }.a); // "a() {}"
test({ *a() {} }.a); // "*a() {}"
test({ [0](){} }[0]); // "[0]() {}"
test(Object.getOwnPropertyDescriptor({
  get a() {},
}, "a").get); // "get a() {}"
test(Object.getOwnPropertyDescriptor({
  set a(x) {},
}, "a").set); // "set a(x) {}"
test(Function.prototype.toString); // "function toString() { [native code] }"
test(function f() {}.bind(0)); // "function () { [native code] }"
test(Function("a", "b")); // function anonymous(a\n) {\nb\n}