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

猜你喜欢

转载自blog.csdn.net/qq_35404844/article/details/129981635