文章目录
写在前面
这里是小飞侠Pan🥳,立志成为一名优秀的前端程序媛!!!
本篇文章收录于我的专栏:前端精进之路
同时收录于我的github前端笔记仓库中,持续更新中,欢迎star~
👉https://github.com/mengqiuleo/myNote
这里并不是介绍原型链的基础知识,而是记录一些自己在看文章过程中以前没有注意到的点。
构造函数constructor
实例对象 中并没有 constructor 属性
constructor其实值存在于构造函数的原型身上,实例对象没有constructor。那为什么我们可以打印出实例对象的constructor属性是它的构造函数呢?
通过一个例子来说明:
function Person() {
}
var person = new Person();
console.log(person.constructor === Person); // true
**当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,
会从 person 的原型也就是 Person.prototype 中读取
,正好原型中有该属性,所以:**
person.constructor === Person.prototype.constructor
Symbol 是构造函数吗
Symbol
是基本数据类型,它不支持语法
new Symbol()
,如果要生成实例直接使用
Symbol()
即可.
newSymbol(123);// Symbol is not a constructor Symbol(123);// Symbol(123)
虽然是基本数据类型,但
Symbol(123)
实例可以获取
constructor
属性值。
var sym =Symbol(123);
console.log( sym );// Symbol(123)
console.log( sym.constructor );// ƒ Symbol() { [native code] }
这里的
constructor
属性来自哪里?其实是
Symbol
原型上的,即
Symbol.prototype.constructor
返回创建实例原型的函数, 默认为
Symbol
函数
constructor 值只读吗
这个得分情况,创建出来的实例分为引用类型和基本类型。例如使用JavaScript自带的
new Number(), new String()
创建出来的就是基本类型。
对于引用类型来说
constructor
属性值是可以修改的,但是对于基本类型来说是只读的。
引用类型情况其值可修改这个很好理解,比如原型链继承方案中,就需要对
constructor
重新赋值进行修正。
functionFoo(){this.value =42;}Foo.prototype ={method:function(){}};functionBar(){}// 设置 Bar 的 prototype 属性为 Foo 的实例对象Bar.prototype =newFoo();Bar.prototype.foo ='Hello World';Bar.prototype.constructor === Object;// 打印结果为true。(这行代码是一个测试,可忽略)// !!! 修正 Bar.prototype.constructor 为 Bar 本身Bar.prototype.constructor = Bar;var test =newBar()// 创建 Bar 的一个新实例
console.log(test);
对于基本类型来说是只读的,比如
1、“muyiy”、true、Symbol
,当然
null
和
undefined
是没有
constructor
属性的。
functionType(){};var types =[1,"muyiy",true,Symbol(123)];for(var i =0; i < types.length; i++){
types[i].constructor = Type;
types[i]=[ types[i].constructor, types[i]instanceofType, types[i].toString()];};
console.log( types.join("\n"));// function Number() { [native code] }, false, 1// function String() { [native code] }, false, muyiy// function Boolean() { [native code] }, false, true// function Symbol() { [native code] }, false, Symbol(123)
为什么呢?因为创建他们的是只读的原生构造函数(
native constructors
),这个例子也说明了依赖一个对象的
constructor
属性并不安全.
prototype
和
__proto__
__proto__
是每个实例上都有的属性,
prototype
是构造函数的属性,在实例上并不存在,所以这两个并不一样。构造函数其实也是一个实例,所以它也有
__proto__
属性。
原型链的构建是依赖于
prototype
还是
__proto__
呢?
Foo.prototype
中的
prototype
并没有构建成一条原型链,其只是指向原型链中的某一处。原型链的构建依赖于
__proto__
,如上图通过
foo.__proto__
指向
Foo.prototype
,
foo.__proto__.__proto__
指向
Bichon.prototype
,如此一层一层最终链接到
null
。
prototype 如何产生的
当我们声明一个函数时,这个属性就被自动创建了。
function Foo() {}
并且这个属性的值是一个对象(也就是原型),只有一个属性
constructor
constructor
对应着构造函数,也就是
Foo
。
图解原型链中的各个难点
这里会分析那张著名的原型链图中比较难理解的地方。
Object.prototype
Object.prototype 表示 Object 的原型对象,Object.prototype 对象是没有
[[Prototype]]
特性的
访问器属性
__proto__
暴露了一个对象的内部
[[Prototype]]
。 Object.prototype 并不是通过
Object
函数创建的,为什么呢?
Object.prototype.__proto__
是 null,所以 Object.prototype 并不是通过 Object 函数创建的,那它如何生成的?其实 Object.prototype 是浏览器底层根据 ECMAScript 规范创造的一个对象。
如果Object.prototype 并不是通过 Object 函数创建的,根据实例对象的
__proro__
属性应该指向构造函数Object的
prorotype
属性,即
Object.prototype.__proto__ ===Object.prototype
但事实是:
Object.prototype.__proto__ === null
Object.prototype 就是原型链的顶端(不考虑 null 的情况下),所有对象继承了它的 toString 等方法和属性。
Function.prototype
Function.prototype 对象是一个对象,其
[[Prototype]]
内部属性值指向内建对象 Object.prototype。Function.prototype 对象自身没有
valueOf
属性,其从 Object.prototype 对象继承了
valueOf
属性。
Function.prototype 的
[[Class]]
属性是
Function
,所以这是一个函数,但又不大一样。为什么这么说呢?因为我们知道只有函数才有 prototype 属性,但并不是所有函数都有这个属性,因为 Function.prototype 这个函数就没有prototype属性。
Function.prototype
// ƒ () { [native code] }Function.prototype.prototype
// undefined
为什么没有呢,我的理解是
Function.prototype
是引擎创建出来的函数,引擎认为不需要给这个函数对象添加
prototype
属性,不然
Function.prototype.prototype…
将无休无止并且没有存在的意义。
很类似与于上面说到的:Object.prototype 就是原型链的顶端(不考虑 null 的情况下)
function Object
Object 作为构造函数时,其
[[Prototype]]
内部属性值指向 Function.prototype,即
Object.__proto__ ===Function.prototype // true
function Function
Function 构造函数是一个函数,其
[[Class]]
属性是
Function
。Function 的
[[Prototype]]
属性指向了
Function.prototype
(注意,这里的
[[Prototype]]
就是
__proto__
属性),即
Function.__proto__ ===Function.prototype
// true
什么是函数(function)?
对象类型的成员,标准内置构造器
Function
的一个实例,并且可做为子程序被调用。
functiondemo(){
console.log('jawil');}
上面这几行代码就是一个函数,这个函数是标准内置构造器
Function
的一个实例,因此函数是一个对象。
demo
就是这个函数的名字,对象是保存在内存中的,函数名
demo
则是指向这个对象的指针。
console.log('jawil')
则是可执行代码。
上面只是创建函数的一种方式, JavaScript 中有三种创建形式,分别是:
①声明式
function fn(){ }; //这种定义的方式,无论在哪儿定义,别处都可以调用 ;
②函数的字面量或叫直接量或称表达式
var fn=function () { }; //此时函数作为表达式存在,调用只能在定义的后面;
//解释一下表达式:凡是将数据和运算符等联系起来有值得式子就是表达式。
③以new Function 的形式
var fn = new Function (arg1 , arg2 ,arg3 ,…, argN , body)
/*Function 构造函数所有的参数都是字符串类型。除了最后一个参数, 其余的参数都作为生成函数的参数即形参。
这里可以没有参数。最后一个参数, 表示的是要创建函数的函数体。
使用Function构造器生成的Function对象是在函数创建时解析的。这比你使用函数声明或者函数表达式(function)
并在你的代码中调用更为低效,因为使用后者创建的函数是跟其他代码一起解析的。
*/
上面三种创建方式,第三种
var fn = new Function (arg1 , arg2 ,arg3 ,…, argN , body)
是最直观的,
很容易就联想到
fn instanceof Function
,
fn
一眼就看出来就是
Function
的实例,但是为什么JavaScript还要创造用
function
来创建一个函数呢?
答案显而易见,用function创建一个函数更优雅,灵活,书写方便,浏览器看到function时候其实已经帮你做了new Function()这一步了,function和new Function()创建的函数都是Function的一个实例,只是方式不一样,其实本质都是一样。就如同创建一个对象一样,我们可以
var obj=new Object()
,当然我们也可以
var obj={}
;
function创建函数只是new Function()创建函数的一个语法糖,
探究Function.proto===Function.prototype⭐
引入
我们知道,Array,Date,Number,String,Boolean,Error,Object相当于构造函数,通过new可以创建出对应的实例。而构造函数就是一个普通函数,是Function的实例
所以Array,Date,Number,String,Boolean,Error,Object都是Function的一个实例,那么Function是谁的实例呢?
functionFoo(){}let f =newFoo();
console.log(f.__proto__ ===Foo.prototype)//true
我们都知道,实例对象的隐式原型指向构造函数的显示原型
所以上面的代码说明,实例对象f的构造函数是Foo
而对于下面的代码:
console.log(Function.__proto__ ===Function.prototype);//true
这就意味着:Function实例的构造函数是Funcion.难道这代表着
Function
自己产生了自己?
Function.__proto__ === Function.prototype
导致
Function.constructor === Function
,即: Function 是它自己的构造函数
探究 Object.prototype
所有对象都可以通过原型链最终找到
Object.prototype
,虽然
Object.prototype
也是一个对象,但是这个对象却不是
Object
创造的,而是引擎自己创建了
Object.prototype
。这也就是为什么
Object.prototype.__proto__ === null
Object.prototype instanceofObject// falseObject.prototype instanceofFunction// false
所以可以这样说,所有实例都是对象,但是对象不一定都是实例。比如,Object.prototype是一个对象,但是它却不是由什么构造函数创建出来的实例,而是由引擎自己创建
到现在,这已经某种程度上解开了鸡和蛋的问题:Object.prototype是对象,但它不是通过Object函数创建的。
那么Function.prototype可以用相同的理论解释。
探究 Function.prototype
Function.prototype
是个特殊的对象,如果你在浏览器将这个对象打印出来,会发现这个对象其实是一个函数。
let fun = Function.prototype.bind()
如果你以上述方法创建一个函数,那么可以发现这个函数是不具有
prototype
属性的。
解释
我们知道函数都是通过
new Function()
生成的,难道
Function.prototype
也是通过
new Function()
产生的吗?答案也是否定的,这个函数也是引擎自己创建的。首先引擎创建了
Object.prototype
,然后创建了
Function.prototype
,并且通过
__proto__
将两者联系了起来。这里也很好的解释了上面的一个问题,为什么
let fun = Function.prototype.bind()
没有
prototype
属性。因为
Function.prototype
是引擎创建出来的对象,引擎认为不需要给这个对象添加
prototype
属性。
**结论:不是所有函数都是
new Function()
产生的,比如Function.prototype虽然是一个函数,但是它是由引擎创建的。**
有了
Function.prototype
以后才有了
function Function()
,然后其他的构造函数都是
function Function()
生成的。
现在可以来解释
Function.__proto__ === Function.prototype
这个问题了。因为先有的
Function.prototype
以后才有的
function Function()
,所以也就不存在鸡生蛋蛋生鸡的悖论问题了。对于为什么
Function.__proto__
会等于
Function.prototype
,一种理解是:其他所有的构造函数都可以通过原型链找到
Function.prototype
,并且
function Function()
本质也是一个函数,为了不产生混乱就将
function Function()
的
__proto__
联系到了
Function.prototype
上。
上面又说到了引擎创建了
Object.prototype
,然后创建了
Function.prototype
。那么这里来解释一下下图的关系:
这里红框圈出的说明
Funtion.prototype.__proto__ = Object.prototype
解释:每个对象都有一个
__proto__
属性,指向创建该对象的构造函数的原型(prototype)
总结
所以,大概回答就是,
Object.prototype
是个神之对象,由它诞生了
Function.prototype
,以之为原型又诞生了
Function
和
Object
,接着创造了对象世界的万物吧。
console.log(Function.prototype instanceofFunction);//false
console.log(Function.prototype instanceofObject);//true
console.log(Function.prototype.__proto__ ===Object.prototype);//true
或许我们可以这样理解:👇🏻
JavaScript引擎是个工厂。
最初,工厂做了一个最原始的产品原型。
这个原型叫Object.prototype,本质上就是一组无序key-value存储({})之后,工厂在Object.prototype的基础上,研发出了可以保存一段“指令”并“生产产品”的原型产品,叫函数。
起名为Function.prototype,本质上就是[Function: Empty](空函数)此时,就有了两个产品原型:Object.prototype 和 Function.prorotype(这两个相当于是最高级别的产品原型)
为了规模化生产,工厂在函数的基础上,生产出了两个构造器:
生产函数的构造器叫Function,生产kv存储的构造器叫Object。你在工厂定制了一个产品,工厂根据Object.prototype给你做了一个Foo.prototype。
此时就有了一个比较普通的产品原型:Foo.prototype
然后工厂发现你定制的产品很不错。就在Function.prototype的基础上做了一个Foo的构造器,叫Foo。
此时,Foo是一个构造器,可以按照Foo.prototype这个产品原型的样子 进行批量生产
工厂在每个产品上打了个标签__proto__,以标明这个产品是从哪个原型生产的。
**比如,你的产品的
__proto__
属性就是Foo.prototype,这表明你的产品是按照 Foo.prototype这个原型产品 生产的**
为原型打了个标签constructor,标明哪个构造器可以依照这个原型生产产品。
constructor指向了你的构造器Foo(这个构造器Foo可以实现批量生产)
为构造器打了标签prototype,标明这个构造器可以从哪个原型生产产品。
为构造器打了标签prototype,相当于 Foo.prototype 。而Foo.prototype正好是你的产品原型,Foo构造器会批量生产出与产品原型一个样子的产品
所以,先有Function还是Object,就看工厂先造谁了。其实先做哪个都无所谓。因为在你定制之前,他们都做好了。
最后总结:先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数(Array,Number,String…)继承Function.prototype而产生。
引用:javascript 中的根对象是 Object.prototype 对象----《Javascript设计模式与编程实践》
灵魂之问
1.
Object.prototype
是对象吗?
- 当然是。> An object is a collection of properties and has a single prototype object. The prototype may be the null value.> > 对象是属性的集合,并且有一个原型对象。原型可能是空值。这是object的定义,
Object.prototype
显然是符合这个定义的。 - 但是,**
Object.prototype
并不是Object
的实例。** 这也很好理解Object.prototype.__proto__
是null
。 - 用代码来解释:
typeofObject.prototype //objectObject.prototype instanceofObject// false
2.
Function.prototype/Function.__proto__
是 function 吗?
- 当然是。比如我们可以正常执行
Function.prototype()
。当然,还是看function的定义更好:> member of the Object type that may be invoked as a subroutine. In addition to its properties, a function contains executable code and state that determine how it behaves when invoked. A function’s code may or may not be written in ECMAScript.> > 可以作为子例程调用的 Object 类型的成员。除了属性之外,函数还包含可执行代码和状态,这些代码和状态决定了它在调用时的行为方式。函数的代码可能用 ECMAScript 编写,也可能不编写。> > > ECMAScript function objects encapsulate parameterized ECMAScript code closed over a lexical environment and support the dynamic evaluation of that code. An ECMAScript function object is an ordinary object and has the same internal slots and the same internal methods as other ordinary objects.> > ECMAScript 函数对象封装了封闭在词法环境中的参数化 ECMAScript 代码,并支持对该代码的动态评估。 ECMAScript 函数对象是一个普通对象,与其他普通对象具有相同的内部槽和相同的内部方法。 - 然而
Function.prototype
不是Function
的实例。Funtion.prototype.__proto__ = Object.prototype
,所以 Function.prototype 是 Object.prototype 的实例 - 用代码解释:
typeofFunction.prototype;// functionFunction.prototype instanceofObject;//trueFunction.prototype instanceofFunction;// false
3.到底是先有 Function 还是先有 Object?
现在我们已经知道了,先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数(Array,Number,String…)继承Function.prototype而产生。
那么,是先有 Function 还是先有 Object?
除了 Object.prototype 之外所有对象都会有
[[Prototype]]
特性 即
__proto__
属性。
为什么说所有类型都是 Function 的实例,同时也是 Object 的实例:
因为所有的类型的
[[Prototype]]
特性 即
__proto__
属性均指向的是 Function.prototype ,同时 Function.prototype 的
[[Prototype]]
特性 即
__proto__
属性又指向了 Object.prototype 。
先有Function还是Object,就看工厂先造谁了。其实先做哪个都无所谓。因为在你定制之前,他们都做好了。
浏览器在初始化 JavaScript 环境时都发生了什么?
1.用 C/C++ 构造内部数据结构创建一个 OP 即(Object.prototype)以及初始化其内部属性但不包括行为。
2.用 C/C++ 构造内部数据结构创建一个 FP 即(Function.prototype)以及初始化其内部属性但不包括行为。
3.将 FP 的[[Prototype]]指向 OP。
4.用 C/C++ 构造内部数据结构创建各种内置引用类型。
5.将各内置引用类型的[[Prototype]]指向 FP。
6.将 Function 的 prototype 指向 FP。
7.将 Object 的 prototype 指向 OP。
8.用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。
9.用 Object 实例化出除 Object 以及 Function 的其他内置引用类型的 prototype 属性对象。
10.用 Function 实例化出除Object 以及 Function 的其他内置引用类型的 prototype 属性对象的行为并挂载。
11.实例化内置对象 Math 以及 Grobal
至此,所有 内置类型构建完成。
现在我们可以回答为什么使用 typeof 获取 Object.prototype 会返回 object 了。
因为我们在使用 typeof 的时候得到的 object 类型并不完全代表是 Object 类型实例化出来的对象,有可能是底层实现的内部数据结构,包括 null。真正想知道这个类型还是需要去到当前该类的内部
[[Class]]
属性,至于如何获取可以使用Object的toString方法。
同样,我们也可以解释:为什么使用 typeof 获取 Function.prototype 会返回 function了。
参考文章
深度解析原型中的各个难点
从探究Function.proto===Function.prototype过程中的一些收获
从__proto__和prototype来深入理解JS对象和原型链
高能!typeof Function.prototype 引发的先有 Function 还是先有 Object 的探讨
版权归原作者 小飞侠Pan 所有, 如有侵权,请联系我们删除。