javaScript關於原型鏈的理解

js原型鏈

js主要由對象主要分爲原型對象、構造函數對象、實例對象。

new原理

使用new可以根據構造函數創建對象,如果構造函數返回非對象值,則創建空對象;反之使用構造函數返回的值

function f1() {
    this.a = "hello"
    return 1
}
function f2() {
    this.a = "hello"
    return {
        b:"world"
    }
}
console.log(new f1());  // {a: "hello"}
console.log(new f2());  // {b: "world"}

原理

new一個新對象的原理:創建空對象,改變該對象prototype,調用構造函數

// instanceof 判斷該實例是否屬於該對象
function fn() {
    this.a = "123";
}
var p1 = new fn;
console.log(p1);
console.log(p1 instanceof fn);  // 判斷p1是否是fn的實例

//等價於
var p2 = new Object();   // 創建一個空對象
p2.__proto__ = fn.prototype;  // 將對象原型指向構造函數的原型
fn.call(p2);  // 將實例對象賦值給構造函數
console.log(p2);
console.log(p2 instanceof fn); // 判斷p2是否是fn的實例

constructor構造函數

構造函數對象:構造函數是用來初始化對象用的,構造函數對象有prototype指向原型
創建對象-》使用構造函數初始化對象f1
使用構造函數創建,創建prototype原型=》在原型上添加一個構造函數屬性constructor

function f1() {
    this.a = 1;
}
var p = new f1;
console.log(p.constructor);  // f1() {this.a = 1;}
console.log(p.__proto__.constructor);  // f1() {this.a = 1;}
console.log(p.constructor === p.__proto__.constructor);  // true

實例上的constructor是從原型上繼承來的,所以實例和原型上的constructor指向一個對象

原型對象介紹

原型對象:創建實例對象時,構造函數先創建原型對象,然後原型對象是每個實例對象的父對象,原型對象上的屬性和方法,實例對象都可以訪問。

function f1() {
    this.a = 1;
}
var p = new f1;
console.log(p.__proto__);   // 實例通過__proto__訪問原型對象
console.log(f1.prototype);  // 構造函數使用prototype訪問原型對象

實例訪問原型使用__proto__方法,構造函數訪問原型使用prototype方法

原型鏈

通過簡單函數我們可以畫出他的原型鏈

function Fn(){
    this.a = "123";
}
var p = new Fn;
console.log(p);  // {a: "123"}
console.log(p.__proto__);

// 從實例對象開始,向下找原型
console.log(p);  // {a: "123"}
console.log(p.__proto__);  // Fn.prototype
console.log(p.__proto__.__proto__);  // Object.prototype
console.log(p.__proto__.__proto__.__proto__);  // null

// 構造函數
console.log(p.constructor);  // Fn  從原型繼承過來的
console.log(p.__proto__.constructor);  // Fn
console.log(p.__proto__.__proto__.constructor);  // Object
// null 沒有構造函數

// Fn 和 Object
// 構造函數
console.log(p.__proto__.constructor.constructor);  // Function
console.log(p.__proto__.__proto__.constructor.constructor);  // Function
// 將FN,Object看成實例,去找他們的原型
console.log(p.__proto__.constructor.__proto__);  // Function.prototype
console.log(p.__proto__.__proto__.constructor.__proto__);  // Function.prototype
// Function的原型
console.log(p.__proto__.constructor.constructor.prototype);  //  Function.prototype      

// Function.prototype的構造函數
console.log(p.__proto__.constructor.constructor.prototype.constructor);  // constructor 
// 將Function.prototype作爲實例去找他的原型
console.log(p.__proto__.constructor.constructor.prototype.__proto__);  // Object.prototype

結論:

  1. 實例向下一直找原型,最終指向Object.prototype,而這個原型指向null。也就是說在Object.prototype上定義的屬性和方法任何對象都可以使用
  2. 實例和其原型的構造函數是Fn,Object.prototype的構造函數是Object,而他兩和Function.prototype都是Function構造,構造函數最終指向Function
  3. 從Function上找原型找到Function.prototype,而他的原型指向Object.prototype,這最終也正式了所有對象原型最終指向Object.prototype

prototype屬性的作用

prototype代表對象的原型,相當於對象的父類。我們可以通過將屬性和方法或者另一個對象掛載到該對象上,實現js的繼承。注意:prototype上的屬性和方法都會成爲對象的共有屬性

// 在原型上添加屬性和方法
var Person = function () {
    this.name = "alex";
};
Person.prototype.age = 18;
Person.prototype.showName = function () {
    console.log(this.name);   // this 指向對象Obj    
};
var p1 = new Person;
var p2 = new Person();   // new對象時,如果沒有參數,加不加括號都可以,結果都一樣  

console.log(p1);  //Person{name: "alex"}
console.log(p2);  // Person{name: "alex"}
// p1和p1都共享一個age
p2.age = 22;  // 在實例上創建age屬性
console.log(p1.age);  // 18  p1實例上沒有age屬性,原型上有age屬性
console.log(p2.age);  // 22  p2實例上有age屬性,直接取實例上age屬性

// p1和p1都調用原型上的prototype屬性
p1.showName();  // alex
p2.showName();  // alex

constructor屬性

構造函數的屬性和作用,構造函數用來初始化函數,初始化原型對象時,在原型對象上添加constructor用來指向構造函數。
Fn原型訪問構造函數:Fn.prototype.constructor
f1實例訪問構造函數:f1.constructor
實例上的constructor實際上是從其原型對象上找到的。

var Person = function () {
    this.name = "alex";
};
// Person 就是構造函數
var p = new Person();
console.log(Person.prototype.constructor);    // Person對象
console.log(p.constructor);  // Person對象
console.log(Person.prototype.constructor === p.constructor); // true

案例

對象擁有數組的方法

相當於函數的繼承,js中繼承函數的方法

// js讓對象繼承數組  
function Fruit() {
}
// 繼承Array數組類  
Fruit.prototype = Array.prototype;  // 修改原型對象,將Fruit的原型指向Array的原型
Fruit.prototype.constructor = Fruit;  // 將構造函數的原型指向Fruit
// 因爲修改原型後,原型上的構造函數就改變爲Array,即修改構造函數指向Fruit。即使用Fruit來初始化函數
var apple = new Fruit();
apple.push("big","middle","small","tiny");  // 可以使用數組的push方法,將元素加入對象中
// 只有數組對象纔有forEach方法,作用是遍歷數組
apple.forEach(element => {
    console.log(element);
});

一般繼承對象的方法,先設置原型,再修改構造函數的指向。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章