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
結論:
- 實例向下一直找原型,最終指向Object.prototype,而這個原型指向null。也就是說在Object.prototype上定義的屬性和方法任何對象都可以使用
- 實例和其原型的構造函數是Fn,Object.prototype的構造函數是Object,而他兩和Function.prototype都是Function構造,構造函數最終指向Function
- 從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);
});
一般繼承對象的方法,先設置原型,再修改構造函數的指向。