JS 原型与原型链详解 (深度学习)
一. 普通对象与函数对象. (万物皆对象)
JavaScript 中,分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。下面举例说明:
var obj1 = {};
var obj2 = new Object();
var obj3 = new fun1();
function fun1(){};
var fun2 = function(){};
var fun3 = new Function();
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof obj1); //object
console.log(typeof obj2); //object
console.log(typeof obj3); //object
console.log(typeof fun1); //function
console.log(typeof fun2); //function
console.log(typeof fun3); //function
在上面的例子中 obj1 obj2 obj3 为普通对象,fun1 fun2 fun3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。fun1,fun2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
一定要分清楚普通对象和函数对象,下面我们会常常用到它。
如图:
二. 构造函数
我们先复习一下构造函数的知识:
function Fun(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) }
}
var fun1 = new Fun('dragon1', 29, 'hello');
var fun2 = new Fun('dragon2', 26, 'hello');
上面的例子中 fun1 和 fun2 都是 Fun 的实例。这两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Fun。 即:
console.log(fun1.constructor == Fun); //true
console.log(fun2.constructor == Fun); //true
我们要记住两个概念(构造函数,实例):
fun1 和 fun2 都是 构造函数 Fun 的实例。
注意:实例的构造函数属性(constructor)指向构造函数。
三. 原型对象
在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。
下面举例说明:
function Fun() {}
Fun.prototype.name = 'dragon';
Fun.prototype.age = 28;
Fun.prototype.job = 'Web';
Fun.prototype.sayName = function() {
alert(this.name);
}
var fun1 = new Fun();
fun1.sayName(); // 'dragon'
var fun2 = new Fun();
fun2.sayName(); // 'dragon'
console.log(fun1.sayname == fun2.sayname); //true
注意:
每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性。
1、那么什么是原型对象呢?
根据上面例子举例说明:
Fun.prototype = {
name: 'dragon',
age: 28,
job: 'Web',
sayName: function() {
alert(this.name);
}
}
原型对象,顾名思义,它就是一个普通对象。从现在开始你要牢牢记住原型对象就是 Fun.prototype。
在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Fun)。
如果上面这句话有点拗口, 那就具体说明下:
如:var F = Fun.prototype ;
F 有一个默认的 constructor 属性,这个属性是一个指针,指向 Fun。
即:Fun.prototype.constructor == Fun; //true
实例的构造函数属性(constructor)指向构造函数 :
fun1.constructor == Fun; //true
2、fun1 为什么有 constructor 属性?
因为 fun1 是 Person 的实例。
3、Fun.prototype 为什么有 constructor 属性?
Fun.prototype (你把它想象成 F) 也是Fun 的实例。
也就是在 Fun 创建的时候,创建了一个它的实例对象并赋值给它的 prototype,基本过程如下:
var F= new Fun();
Fun.prototype = F;
结论:原型对象(Fun.prototype)是 构造函数(Fun)的一个实例。
原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))。看下面的例子:
function Fun(){};
console.log(Fun.prototype) //Fun{}
console.log(typeof Fun.prototype) //Object
console.log(typeof Function.prototype) // Function
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined
4、Function.prototype 为什么是函数对象呢?
var F = new Function ();
Function.prototype = F;
凡是通过 new Function( ) 产生的对象都是函数对象。
因为 F 是函数对象,所以Function.prototype 是函数对象
四. __proto__
JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。
对象 fun1 有一个 __proto__属性,创建它的构造函数是 Fun,构造函数的原型对象是 Fun.prototype ,所以:
fun1.__proto__ == Fun.prototype
Fun.prototype.constructor == Fun;
fun1.__proto__ == Fun.prototype;
fun1.constructor == Fun;
不过,要明确的真正重要的一点就是,这个连接存在于实例(fun1)与构造函数(Fun)的原型对象(Fun.prototype)之间,而不是存在于实例(fun1)与构造函数(Fun)之间。
注意:因为绝大部分浏览器都支持__proto__属性,所以它才被加入了 ES6 里(ES5 部分浏览器也支持,但还不是标准)。
五. 构造器
熟悉 Javascript 的童鞋都知道,我们可以这样创建一个对象:
var obj = {}
它等同于下面这样:
var obj = new Object()
obj 是构造函数(Object)的一个实例。所以:
obj.constructor === Object
obj.__proto__ === Object.prototype
新对象 obj 是使用 new 操作符后跟一个构造函数来创建的。构造函数(Object)本身就是一个函数(就是上面说的函数对象),它和上面的构造函数 Person 差不多。只不过该函数是出于创建新对象的目的而定义的。所以不要被 Object 吓倒。
同理,可以创建对象的构造器不仅仅有 Object,也可以是 Array,Date,Function等。
所以我们也可以构造函数来创建 Array、 Date、Function.
var a = new Array();
a.constructor === Array; //true
a.__proto__ === Array.prototype; //true
var b = new Date();
b.constructor === Date; //true
b.__proto__ === Date.prototype; //true
var c = new Function();
c.constructor === Function; //true
c.__proto__ === Function.prototype; //true
下面这些都是函数对象:
typeof Boolean 'function'
typeof Number 'function'
typeof Date 'function'
typeof Array 'function'
typeof String 'function'
typeof Function 'function'
typeof Object 'function'
六. 原型链
利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(__proto__)。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(__proto__),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。
如图:
举例说明:
function Fun(){
this.type = "web";
}
Fun.prototype.getType = function(){
return this.type;
}
function Fun1(){
this.name = "dragon";
}
Fun1.prototype = new Fun();
Fun1.prototype.getName = function(){
return this.name;
}
var fl = new Fun1();
输出:
fun1.__proto__ === Fun.prototype //true
Fun.__proto__ === Function.prototype //true
Fun.prototype.__proto__ === Object.prototype //true
Object.prototype.__proto__ === null //true
七、总结:
由上面得出结论:
(一)、所有函数对象的__proto__都指向Function.prototype,它是一个空函数(Empty function)
举例:
Number.__proto__ === Function.prototype // true
Number.constructor == Function //true
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
String.__proto__ === Function.prototype // true
String.constructor == Function //true
(二)、 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
举例:
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
Array.__proto__ === Function.prototype // true
Array.constructor == Function //true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true
Error.__proto__ === Function.prototype // true
Error.constructor == Function //true
Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
(三)、 仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。如下:
Math.__proto__ === Object.prototype // true
Math.construrctor == Object // true
JSON.__proto__ === Object.prototype // true
JSON.construrctor == Object //true
知道了所有构造器(含内置及自定义)的__proto__都是Function.prototype,那Function.prototype的__proto__是谁呢?
相信都听说过JavaScript中函数也是一等公民,那从哪能体现呢?如下
console.log(Function.prototype.__proto__ === Object.prototype) // true
这说明所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。(你也应该明白第一句话,第二句话我们下一节继续说,不用挖坑了,还是刚才那个坑;
最后Object.prototype的proto是谁?
Object.prototype.__proto__ === null // true
(四)、 Prototype
在 ECMAScript 核心所定义的全部属性中,最耐人寻味的就要数 prototype 属性了。对于 ECMAScript 中的引用类型而言,prototype 是保存着它们所有实例方法的真正所在。换句话所说,诸如 toString()和 valuseOf() 等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了。
1、 创建一个对象实例时:
var Person = new Object()
Person 是 Object 的实例,所以 Person 继承了Object 的原型对象Object.prototype上所有的方法:
Object 的每个实例都具有以上的属性和方法。
所以我可以用 Fun.constructor 也可以用 Fun.hasOwnProperty。
2、创建一个数组实例时:
var num = new Array()
num 是 Array 的实例,所以 num 继承了Array 的原型对象Array.prototype上所有的方法:
3、创建一个函数时:
var fun = new Function("x","return x*x;");
//当然你也可以这么创建 fun = function(x){ return x*x }
console.log(fun.arguments) //null arguments 方法从哪里来的?
console.log(fun.call(window)) //NaN call 方法从哪里来的?
console.log(Function.prototype) // function() {} (一个空的函数)
console.log(Object.getOwnPropertyNames(Function.prototype));
//['length', 'name', 'arguments', 'caller', 'constructor', 'apply', 'bind', 'call', 'toString']
所有函数对象proto都指向 Function.prototype,它是一个空函数(Empty function)
嗯,我们验证了它就是空函数。不过不要忽略前半句。我们枚举出了它的所有的方法,所以所有的函数对象都能用,比如:
(五)、__proto__
所有函数对象的 __proto__ 都指向 Function.prototype,它是一个空函数(Empty function)
先看看 JS 内置构造器:
var obj = {name: 'dragon'};
var arr = [1,2,3,4,5];
var reg = /hello/g;
var date = new Date;
var err = new Error('pp');
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype) // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype) // true
console.log(err.__proto__ === Error.prototype) // true
再看看自定义的构造器,这里定义了一个 Fun:
function Fun(name) {
this.name = name;
}
var pp = new Fun('dragon');
console.log(pp.__proto__ === Person.prototype); // true
pp 是 Fun 的实例对象,pp 的内部原型总是指向其构造器 Fun 的原型对象 prototype。
每个对象都有一个 constructor 属性,可以获取它的构造器,因此结果也是恒等的:
function Fun(name) {
this.name = name
}
var pp = new Fun('jack')
console.log(pp.__proto__ === pp.constructor.prototype) // true
上面的Person没有给其原型添加属性或方法,这里给其原型添加一个getName方法:
function Fun(name) {
this.name = name
}
// 修改原型 增加一个方法
Fun.prototype.getName = function() {}
var pp = new Fun('dragon')
console.log(pp.__proto__ === Fun.prototype) // true
console.log(pp.__proto__ === pp.constructor.prototype) // true
可以看到pp.__proto__与Fun.prototype,pp.constructor.prototype都是恒等的,即都指向同一个对象。
如果换一种方式设置原型,结果就有些不同了:
function Fun(name) {
this.name = name
}
// 重写原型
Fun.prototype = {
getName: function() {}
}
var pp = new Fun('dragon')
console.log(pp.__proto__ === Fun.prototype) // true
console.log(pp.__proto__ === pp.constructor.prototype) // false
这里直接重写了 Fun.prototype(注意:上一个示例是修改原型)。输出结果可以看出pp.__proto__仍然指向的是Fun.prototype,而不是pp.constructor.prototype。
这也很好理解,给Fun.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。如下:
var pp = {}
console.log(Object.prototype) // 为一个空的对象{}
console.log(pp.constructor === Object) // 对象直接量方式定义的对象其constructor为Object
console.log(pp.constructor.prototype === Object.prototype) // 为true
(五)、原型链。(复习下)
看完例子就明白了:
function Fun(){}
var fun1 = new Fun();
console.log(fun1.__proto__ === Fun.prototype); // true
console.log(Fun.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
console.log(Fun.__proto__ == Function.prototype); //true
console.log(Function.prototype); // function(){}(空函数)
var num = new Array();
console.log(num.__proto__ == Array.prototype); // true
console.log(Array.prototype.__proto__ == Object.prototype); // true
console.log(Array.prototype); // [] (空数组)
console.log(Object.prototype.__proto__); //null
console.log(Array.__proto__ == Function.prototype); // true