跳到主要内容

Function类型

属性

prototype

并不是每个函数都具有原型,唯一要注意的就是Function.prototype是一个函数,但是它没有原型;至于其他函数的原型指向Function.prototype

image-20200620185806469

一个经常被讨论的话题是函数和对象的关系,也就是网上说的先有函数还是先有 Object,源于instanceof的结果

Object instanceof Function; // true
Function instanceof Object; // true

Object instanceof Object; // true
Function instanceof Function; // true

个人感觉这种问题真的毫无意义,尤其网上还流传着一张 Hursh Jain 的原型链的图 —— JavaScript Object Layout,讲道理看了那张图都有放弃学习 JS 的想法了,其实我觉得只要在了解原型链是通过对象的私有属性[[prototype]]构建起来的这一点,基本上都能解释上述等式成立的原因。

  • Object角度考虑,当Object作为构造函数的时候,其必是Function类型的实例,所以Object.__proto__ === Function.prototype肯定是成立的;根据 ES 规范,Object.prototype[[prototype]]null
  • 从函数角度看,根据 ES2020,Function.prototype是一个built-in function object,也就是函数,但是它不能作为构造函数使用new调用,其内部的[[prototype]]指向Object.prototype;同时,构造函数Function也具有[[prototype]],指向Function.prototype,我们用一张图表示

image-20200623172241563

  • 从 JS 引擎角度看,JS 里的函数和对象都是 C++中类的实例,函数对应 JSFunction 这个类的实例,对象对应 JSObject 类的实例,而 JSFunction 继承自 JSObject,所以Object的属性和方法,在Function中也能访问到;从运行时的角度来看,原型链这张东西就是个莫须有的东西,它只不过是从人性化的角度方便我们理解 JS 的属性和方法的继承,让我们感受以下 JS 原来也是面向对象的,实际实现其实还是通过真正的面向对象的 C++的类继承来实现

__proto__

由于每一个函数其实都是Function类型的实例,所以每一个函数都会具有__proto__属性,这个属性是指向Function.prototype的指针,以此从原型对象上继承一些属性和方法

name

函数名

length

表示函数定义时理论上必须传入的参数个数,这个和arguments.length是相反的,arguments.length表示调用函数实际接收的参数个数

  • 不包括已经初始化默认值的参数
  • 不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数
function f(a, b = 1, c) {}

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

arguments

arguments是一个类数组对象,主要用来保存函数被调用时传进来的参数,也就是说从函数初始化创建直到函数被调用这个值一直是null;在函数被调用的时候,才会分配这个值。

箭头函数里没有这个属性

function foo() {
console.log(arguments);
}
foo(1, 2);

image-20200622174819476

arguments利用Symbol.iterator实现了迭代器,所以arguments支持for...infor...of遍历

function foo() {
for (var index in arguments) {
console.log(`${index}:${arguments[index]}`);
}
}

foo('a', 'b', 'c'); //0:a 1:b 2:c

function foo() {
for (var value of arguments) {
console.log(value);
}
}

foo('a', 'b', 'c'); //a b c
// slice() 方法返回一个新的数组对象,可以用作原数组的浅拷贝
// call 使用调用者提供的 this 值和参数调用该函数的返回值
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES6
const args = Array.from(arguments);
const args = [...arguments];

此外 arguments 还具有两个属性:

  • length:是当前函数实际接收的参数个数

  • callee指向函数本身,可以用这个属性实现递归函数,避免函数的引用变量被修改而导致递归出错

function foo(num) {
if (num < 1) {
return 1;
} else {
return num * foo(num - 1);
}
}

// 使用arguments.callee实现
function foo(num) {
if (num < 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}

caller

ES5 定义了caller属性,这个属性是调用当前函数的函数的引用,可以使用这个属性遍历函数的调用栈,在严格模式下访问这个属性同样会抛出异常:TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them.

[[scope]]

如果在浏览器下,还会观察到函数的一个私有属性[[scope]],它是函数保存的作用域链,这是实现闭包的基础。代码中是无法通过任何手段获取这个属性值的,但是可以在 devtool 里观察到。

原型方法

Function.prototype.call

call使用给定的this和逗号,分隔的参数列表来调用一个函数,并返回函数执行后的结果

利用call实现借用构造函数继承

function SuperType(arg){
this.prop = arg;
}

function SubType(arg){
SuperType.call(this,arg);
}

var obj = new SubType("test"); SubType {prop: "test"}

call的强大之处就在于修改函数运行时的作用域,换句话说就是指定函数内部的this

var color = 'red';
var o = { color: 'blue' };

function sayColor() {
var color = 'green';
console.log(this.color);
}

sayColor(); //"red",当前在全局环境调用,所以this指向window
sayColor('yellow'); //"red",当前在全局环境调用,所以this指向window
sayColor.call(o); //"blue",因为指定this为o

Function.prototype.apply

apply方法和call唯一的区别就是apply只有两个参数,且第二个参数必须是一个数组或者类数组对象(arguments),否则会抛出异常TypeError

function SuperType(arg){
this.prop = arg;
}

function SubType(arg){
SuperType.apply(this, [arg]);
}

var obj = new SubType("test"); SubType {prop: "test"}

Function.prototype.bind

bind使用指定的this和逗号,分隔的参数列表来创建一个新的绑定函数;在bind中后续指定的参数将作为新创建的函数的参数使用

基本用法

function foo(y) {
console.log(this.x + y);
}

var obj = { x: 1 };
var newFoo = foo.bind(obj);
newFoo(2); // 3

bind的强大之处在于其实现函数柯里化的特性

函数柯里化是指创建一个已经指定好了一个或者多个参数的函数,其实现方法是利用闭包返回一个函数,事实上bind也是利用闭包返回一个设置好this的函数,所以我们可以利用bind实现函数柯里化

function foo(y, z) {
console.log(this.x + y + z);
}

//返回一个指定了参数 y 的函数
var newFoo = foo.bind({ x: 1 }, 2);

//传入的参数将赋值给 z
newFoo(3); // 6

Object.prototype.toString

在上面的讨论中说到,根据 ES2020,Function.prototype是一个函数,其内部的[[prototype]]指向Object.prototype,所以每一个函数都能继承Object.prototype的属性和方法。如果对自定义的函数使用toString()方法时,返回的是函数定义的源码字符串,而对 JS 内置的函数使用时,返回的只是[native code]字符串

console.log(Function.prototype); // ƒ () { [native code] }

Object.prototype.valueOf

函数调用valueOf方法会返回自身

function foo() {}

console.log(foo.valueOf() === foo); //true