此次给你们带来一个关于 JavaScript 函数与原型链之间的关系的分享,将会从函数开始讲起,一直讲到整个原型链是什么样子的,但愿能给你们带来帮助。app
JavaScript 中的函数大体有如下三种使用方式:函数
例如优化
// 定义函数
function foo() {}
// 调用函数
foo();
复制代码
当一个函数被用来建立新对象的时候,就把它叫作:构造函数。ui
例如this
// 按照惯例,做为构造函数的函数名首字母须要大写
function Foo() {}
const obj = new Foo();
复制代码
这时对于对象 obj 来讲,函数 Foo 就叫作它的构造函数。spa
访问对象的属性可使用点操做符和中括号来访问prototype
例如code
function foo() {}
foo.name = 'tom';
foo['age'] = 20;
复制代码
以上这三种使用方式中,以普通函数来调用的方式你们都懂,这里再也不赘述。本文主要讲解的就是当一个函数被做为构造函数来使用和被做为对象来使用的时候,分别是什么样的,以及它们之间与原型链的关系是什么样的。cdn
在讲接下来的内容以前,咱们先来看几个问题:对象
// 首先,定义一个函数,将会做为构造函数
function Foo() {}
// 实例化出来一个对象
const obj = new Foo();
// 在 Object 的原型上定义一个属性:objProp
Object.prototype.objProp = '我是 Object 原型上的属性';
// 在 Function 的原型上定义一个属性:funcProp
Function.prototype.funcProp = '我是 Function 原型上的属性';
// 你预想一下,如下这些分别会输出什么?
console.log(obj.objProp) // ?
console.log(obj.funcProp) // ?
console.log(Foo.objProp) // ?
console.log(Foo.funcProp) // ?
console.log(Object.objProp) // ?
console.log(Object.funcProp) // ?
console.log(Function.objProp) // ?
console.log(Function.funcProp) // ?
console.log(Array.objProp) // ?
console.log(Array.funcProp) // ?
复制代码
接下来会讲解函数的第二个特性,做为构造函数来使用的时候,它是什么样的。
当咱们使用 new 操做符来从构造函数建立对象的时候,会经历如下几个步骤:
{}
。__proto__
指向构造函数的原型对象。this
,这样新对象才能访问到构造函数中的属性。文字描述不太好理解,咱们用代码来描述一遍,而后再对比着看一下:
// 新建一个构造函数
function Foo() {}
// 使用 new 操做符实例化一个对象
const obj_foo = new Foo()
// ========建立对象的过程:
// 1. 建立一个空对象:`{}`。
const obj = {};
// 2. 原型链的连接过程
obj.__proto__ = Foo.prototype;
// 3. 把新对象做为函数的上下文,即绑定 this
Foo.apply(obj, arguments);
// 4. 由于 Foo 没有返回对象,因此就返回这个新对象
return obj;
// ========END
// 这时 obj_foo 就是返回出来的新对象了
复制代码
__proto__
是什么?首先你要记住:只有对象才会有 __proro__
这个属性。它是 js 得以实现原型链的关键,由于它会指向构造函数的原型对象,即 prototype
属性上的对象,而构造函数原型对象上的 __proto__
又指向上一级,即:构造函数.prototype.__proto__ -> 上一级构造函数.prototype
,以此类推层层往上,就造成了咱们所说的原型链。若是不理解,请继续看下文:
前面一直提到 构造函数.prototype
,这个属性是函数特有的一个属性,它是构造函数的原型对象,全部由它构造出来的对象都会从这个原型对象上继承属性和方法(参考 MDN:JavaScript 是一种基于原型的语言,每一个对象都拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性)。就像前面的例子:const obj_foo = new Foo()
,obj_foo 的原型对象就是构造它的函数 Foo 的 prototype,obj_foo 从 Foo.prototype
继承了方法和属性。
又由于 构造函数.prototype
是一个对象,因此它就会有 __proto__
这个属性,说明原型对象也是由一个构造函数建立出来的,即:构造函数.prototype.__proto__ -> 上一级构造函数.prototype
。
这下你能理解 __proto__
、prototype 和原型链之间的关系了吧,正由于有了它们俩 JavaScript 才得以实现原型链。
先来回顾一下 JavaScript 中的一些内建构造函数吧,好比:Object
、Function
、Number
、Array
、String
等。
既然是构造函数,就能够用来建立对象,好比:
var a = new Number('123'); // a === 123 is false
// 由于 Number.prototype 上有 toString() 方法,因而 a 也就继承了该方法:
a.toString(); // a === '123' is true
var b = new String(123); // b === '123' is false
a instanceof Number; // is true
b instanceof String; // is true
复制代码
有了上述印象,接下来就跟你们讲解其中最核心最特殊的两个构造函数:Object
和 Function
。
用来建立对象,例如:
const obj = new Object();
复制代码
Object 除了是一个构造函数能够用来建立对象之外,它还拥有一个很硬核的能力:派生全部的其它构造函数。
这里用了“派生”二字,在理解上是把它当作一个基类或者说父类来看待的。
也就是:全部的其它构造函数都是由 Object 这个构造函数派生出来的(子类)。
用代码解释一下(注:JavaScript 中没有明确的类的概念):
// 父类 Object
class Object {}
// 子类 MyFunction
class MyFunction extends Object {}
复制代码
正由于是由 Object 构造出来的,那么构造函数的原型链就指向了 Object 的原型对象(参考 new 的第二步过程能够加以理解):
全部构造函数.prototype.__proto__ === Object.prototype
重点
由于 Function 也是一个构造函数,那么就能够推导出:
Function.prototype.__proto__ === Object.prototype
用来建立函数对象,例如:
const sum = new Function('a', 'b', 'return a + b');
sum instanceof Function; // true
复制代码
在这里把 sum 叫函数对象,是由于它确实是由 Function 构造出来的对象,可是它跟咱们日常定义一个 sum 函数是同样的,这就是我当初的迷惑点所在,即函数有多种表示形态,能够是普通函数,能够是构造函数也能够是函数对象。
咱们不可贵出:全部的函数对象都是由 Function 构造出来的
证实:sum.__proto__ === Function.prototype
重点
进一步能够推导出:Object.__proto__ === Function.prototype
__proto__
只有对象才有这个属性,它指向对象的构造函数的原型对象;前面咱们讲了构造函数、函数对象这些东西,那么函数是如何肯定最终形态的呢?答案是:在使用的时候。
使用 new 来调用它,那么它就是一个能够建立对象的构造函数,若是使用点或者中括号来调用它,那么它就又变成了一个对象(注:排除 prototype
属性)。
我在前面标了两个重点公式:
// 1
Function.prototype.__proto__ === Object.prototype
// 2
Object.__proto__ === Function.prototype
复制代码
再次解释一下:
一、看到 Function.prototype
就说明 Function 此时是被当作构造函数来使用的,前面说过全部构造函数都是由 Object 构造出来的。
二、看到 Object.__proto__
就说明 Object 此时是被当作对象来使用的,前面说过全部函数对象都是由 Function 建立出来的。
Object 和 Function 之间的关系很是特殊,根据不一样的形态能够互生。
这下咱们就能够来解决预留问题了。
// 首先,定义一个函数,将会做为构造函数
function Foo() {}
// 实例化出来一个对象
const obj = new Foo();
// 在 Object 的原型上定义一个属性:objProp
Object.prototype.objProp = '我是 Object 原型上的属性';
// 在 Function 的原型上定义一个属性:funcProp
Function.prototype.funcProp = '我是 Function 原型上的属性';
复制代码
问题:
一、
console.log(obj.objProp) // ?
复制代码
答案:
- 由于对象的原型链指向构造函数的原型对象,因此:
obj.__proto__ -> Foo.prototype
,- 由于全部构造函数都是由 Object 派生出来的,因此:
Foo.prototype.__proto__ -> Object.prototype
- 由于
Object.prototype
上有 objProp 这个属性,因此 obj.objProp === '我是 Object 原型上的属性'
二、
console.log(obj.funcProp) // ?
复制代码
答案:
- 由于对象的原型链指向构造函数的原型对象,因此:
obj.__proto__ -> Foo.prototype
,- 由于全部构造函数都是由 Object 派生出来的,因此:
Foo.prototype.__proto__ -> Object.prototype
- 因为在
Object.prototype
上找不到 funcProp 属性,根据原型链继续找:Object.prototype.__proto__ -> null
- 找不到,返回 undefined
三、
console.log(Foo.objProp) // ?
复制代码
答案:
Foo.objProp
是把 Foo 当作对象来使用的,此时它是一个函数对象,因此它是由 Function 建立出来的:Foo.__proto__ -> Function.prototype
- 因为在
Function.prototype
上找不到 objProp 属性,根据原型链继续找:Function.prototype.__proto__ -> Object.prototype
- 在
Object.prototype
上找到了 objProp 属性,因此 Foo.objProp === '我是 Object 原型上的属性'
四、
console.log(Foo.funcProp) // ?
复制代码
答案:
Foo.funcProp
是把 Foo 当作对象来使用的,此时它是一个函数对象,因此它是由 Function 建立出来的:Foo.__proto__ -> Function.prototype
- 在
Function.prototype
上找到了 funcProp 属性,因此 Foo.funcProp === '我是 Function 原型上的属性'
五、
console.log(Object.objProp) // ?
复制代码
答案:
Object.objProp
是把 Object 当作对象来使用的,此时它是一个函数对象,因此它是由 Function 建立出来的:Object.__proto__ -> Function.prototype
- 在
Function.prototype
上找不到 objProp 属性,根据原型链继续找:Function.prototype.__proto__ -> Object.prototype
- 在
Object.prototype
上找到了 objProp 属性,因此 Object.objProp === '我是 Object 原型上的属性'
六、
console.log(Object.funcProp) // ?
复制代码
答案:
Object.funcProp
是把 Object 当作对象来使用,此时它是一个函数对象,因此它是由 Function 建立出来的:Object.__proto__ -> Function.prototype
- 在
Function.prototype
上找到了 funcProp 属性,因此 Object.funcProp === '我是 Function 原型上的属性'
七、
console.log(Function.objProp) // ?
复制代码
注:为了不混淆,Function 表示对象, %Function 表示构造函数
答案:
Function.objProp
是把 Function 当作对象来使用的,此时它是一个函数对象,因此它是由 %Function 建立出来的:Function.__proto__ -> %Function.prototype
- 在
%Function.prototype
上找不到 objProp 属性,根据原型链继续找:%Function.prototype.__proto__ -> Object.prototype
- 在
Object.prototype
上找到了 objProp 属性,因此 Function.objProp === '我是 Object 原型上的属性'
八、
console.log(Function.funcProp) // ?
复制代码
答案:
Function.funcProp
是把 Function 当作对象来使用的,此时它是一个函数对象,因此它是由 $Function 建立出来的:Function.__proto__ -> %Function.prototype
- 在
%Function.prototype
上找到了 funcProp 属性,因此 Function.funcProp === '我是 Function 原型上的属性'
九、
console.log(Array.objProp) // ?
复制代码
答案:本身尝试着分析一下吧,欢迎在评论区给出
十、
console.log(Array.funcProp) // ?
复制代码
答案:本身尝试着分析一下吧,欢迎在评论区给出
我画了一张原型链的图片,供你们对比着上述的流程做参考:
若是本文有什么错误之处,还请你们帮忙指出,我会继续改进。
12-05:从新组织了内容结构,修复文字错误,优化原型链图片。