摘要:原型鏈原型鏈實際上是中的實現繼承的機制,在搞懂原型鏈之前首先要搞懂幾個概念,普通對象和函數對象,構造函數,對于很多人來說是混雜不清的概念,但是想要弄清楚原型鏈我們必須了解什么是首先,只能在存在與函數中其次,其實是當前函數所在上下文環境,再簡
原型鏈
原型鏈實際上是JavaScript中的實現繼承的機制,在搞懂原型鏈之前首先要搞懂幾個概念:this,普通對象和函數對象,構造函數,new
thisthis對于很多人來說是混雜不清的概念,但是想要弄清楚原型鏈我們必須了解什么是this
首先,this只能在存在與函數中
其次,this其實是當前函數所在上下文環境,再簡單一點也可以理解為this返回一個當前函數所在的對象,也就是說想要知道this是什么我們只需要關注是誰調用了this所在的函數就可以了
如下邊的代碼zhao.sayName()是zhao調用的sayName函數所以sayName中的this自然指的就是zhao這個對象,而下方 var liName = zhao.sayName語句是將sayName這個函數賦值給liName,調用liName就相當于在最頂層也就是window下直接調用sayName,this的指向自然就是window這個最頂層對象
var name = "Li" var zhao = { name: "Zhao", sayName: function () { console.log(this.name); } } zhao.sayName() // Zhao var liName = zhao.sayName; liName() // Li普通對象與函數對象
JavaScript中一切都可以看作對象,但是實際上對象也是有區別的,對象分為普通對象和函數對象
// 普通對象 var o1 = {} var o2 = new Object() var o3 = new f1() // 函數對象 function f1(){} var f2 = function(){} var f3 = new Function() console.log(typeof f1); //function console.log(f1.prototype); //true console.log(typeof f2); //function console.log(f2.prototype); //true console.log(typeof f3); //function console.log(f3.prototype); //true console.log(typeof o1); //object console.log(o1.prototype); //undefined console.log(typeof o2); //object console.log(o2.prototype); //undefined console.log(typeof o3); //object console.log(o3.prototype); //undefined
凡是通過function構建的對象都是函數對象,并且只有函數對象才有prototype屬性,普通對象沒有
prototype 原型prototype又是什么呢?
當我們創建函數的時候,編譯器會自動為該函數創建一個prototype屬性,這和屬性指向一個包含constructor屬性的對象,而這個屬性又默認指回原函數,讀起來有點繞對吧,大概是這樣的
function Person() { // prototype = { // constructor: Person, // } }
每個函數對象都有一個prototype(原型)屬性,在我看來prototype屬性的意義:
創建對象的模板
公開的共享空間
這兩點等學習了下邊new命令你就會明白了
constructor 構造函數函數對象的一種用法就是構造函數,通過構造函數可以構建一個函數對象的實例(普通對象)
function Person(name, age ){ this.name = name; this.age = age; this.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; } var person1 = new Person("kidder", 28); person1.sayHello(); // Hello! my name is kidder console.log(person1.constructor); //[Function:Person]
按照慣例,構造函數的命名以大寫字母開頭,非構造函數以小寫字母開頭,通過構造函數構造的普通對象都會有一個constructor(構造函數)屬性,該屬性指向構造該對象的構造函數
new命令 new命令的工作機制創建一個空對象作為要返回對象的實例
將這個空對象的原型(__ proto __)指向構造函數的prototype屬性
將這個空對象賦值給構造函數內部的this
執行構造函數內部的代碼
原型鏈下面我們來看看構造函數構建一個普通對象的時候發生了什么
var Person = function (name) { this.name = name; this.age = 18; }; Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person("Li"); console.log(li.name); // Li console.log(li.age); // 18 li.sayHello(); // Hello! my name is Li
創建一個空對象作為要返回對象的實例
{}
將這個空對象的原型(__ proto __)指向構造函數的prototype屬性
{ __proto__:Person.prototype; }
將這個空對象賦值給構造函數內部的this
this = { __proto__:Person.prototype; }
執行構造函數內部的代碼
this = { __proto__:Person.prototype; name: "Li"; age: 18; }
所以li這個對象中只有name和age兩個屬性,為什么li.sayHello()會輸出Hello! my name is Li呢?
這就是原型鏈,當給定的屬性在當前對象中找不到的情況下,會沿著__proto__這個屬性一直向對象的上游去尋找,直到__proto__這個屬性指向null為止,如果找到指定屬性,查找就會被截斷,停止
上面這張圖是整個JavaScript的原型鏈體系,為了讓這張圖更直觀所以我將構造函數的prototype屬性多帶帶提了出來,恩,其實畫在構造函數內部也可,但同時因為對象是引用類型,所以這樣畫也沒毛病吧
_ proto _ 和 prototype這兩個屬性經常會被我們混淆,那么我們回過頭再來總結一下
prototype:只有函數對象才具有的屬性,它用來存放的是構造函數希望構建的實例具有的共享的屬性和方法,主要用于構造函數的實例化
_proto_ : 所有對象都具有的屬性,它指向的是當前對象在原型鏈上的上級對象,主要作用是讓編譯器在由__proto__這個屬性構成的原型鏈上查找特定的屬性和方法
補充 prototype的共享屬性var Person = function (name) { this.name = name; this.age = 18; }; Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person("Li"); var Person1 = function () { }; Person.prototype.name = "Li" Person.prototype.age = 18 Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var Li = new Person1();
關于Person和Person1兩種構造函數的寫法有什么不同呢?
一般來說寫在prototype原型對象中的屬性和方法都是公用的,也就是說寫在構造函數中的屬性在構建普通對象的時候,都會在新對象中重新定義,也就是從內存的角度來說又會多占用一些內存空間,所以我們將構造函數的所有屬性和方法都寫在prototype原型中不好嗎?
但是原型函數也是有缺點的:
不夠靈活
var Person = function () { }; Person.prototype.name = "Li" Person.prototype.age = 18 Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person(); var zhao = new Person();
這種方式構造的所有對象都是一個模板,雖然我們也可以在當前對象下進行修改,但這樣一點也不優雅,不規整,而且從某種意義上來說也是對內存的浪費
對于引用類型的修改會被全部共享
var Person = function () { }; Person.prototype.name = "Li" Person.prototype.age = 18 Person.prototype.friends = ["ZhangSan", "LiSi"] Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person(); var zhao = new Person(); li.friends.push("WangWu"); console.log(zhao.friends); // [ "ZhangSan", "LiSi", "WangWu" ]
在JavaScript中,基本類型的修改可以明確的通過創建或修改在當前對象下的屬性對原型鏈進行截斷,但是像數組,對象這種引用類型的值雖然也可以通過在當前對象中創建該屬性來對原型鏈進行截斷,但是一不注意就可能會出現上面這種情況直接對原型進行了修改
構造函數與原型相結合所以,用構造函數來定義實例屬性,用原型定義方法和共享的屬性,這樣寫就比較優雅了
function Person(name, age){ this.name = name; this.age = age; this.friends = ["ZhangSan", "LiSi"]; } Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name},${this.age}歲了`); }; var li = new Person("li", 18); var zhao = new Person("zhao", 16); li.sayHello(); // Hello! my name is li, 18歲了 zhao.sayHello(); // Hello! my name is zhao,16歲了 li.friends.push("WangWu"); console.log(zhao.friends); // [ "ZhangSan", "LiSi" ]創建對象的幾種方式
構造函數方式
法一用構造函數構造一個新對象
var A = function () { }; var a = new A(); console.log(a.constructor); // [Function:A] console.log(a.__proto__ === A.prototype); //true
字面量方式
法二的本質來說和法一是一樣的,就是隱式調用原生構造函數Object來構造新對象
var a = {}; // var a = new Object(); console.log(a.constructor); // [Function:Object] console.log(a.__proto__ === Object.prototype); //true
create方式
法三Object.create是以一個普通對象為模板創建一個新對象
var a1 = {a:1} var a2 = Object.create(a1); console.log(a2.constructor); // [Function:Object] console.log(a2.__proto__ === a1);// true console.log(a2.__proto__ === a1.prototype); //false
所以除了Object.create創建對象的方式,可以說:__ proto __ === constructor.prototype;
constructor前面我們說道prototype的時候進行原型屬性的賦值的時候,采用的是逐項賦值,那么當我直接將對象賦值給prototype屬性的時候會發生什么呢?
function Person() { } Person.prototype = { name : "Li", age : 18, sayHello : function () { console.log(`Hello! my name is ${this.name},${this.age}歲了`); } }; var li = new Person(); console.log(li instanceof Object); // true console.log(li instanceof Person); // true console.log(li.constructor === Person); // false console.log(li.constructor === Object); // true console.log(Person.prototype.constructor); // Object
這時候我們就發現我們構建的li對象的constructor不再指向它的構造函數Person,而是指向了Object,并且Person原型Person.prototype的constructor指向也指向了Object,這是什么原因呢?
其實,根源出現在Person.prototype上,上邊我們提到過,其實我們在寫構造函數的時候實際上是這樣的
function Person() { // prototype = { // constructor : Person // } }
當我們構建Person構造函數的時候,編譯器會自動生成一個帶有指向Person的constructor屬性的對象,并把這個對象賦值給Person.prototype,我們又知道js中對象是引用類型,當我們使用Person.prototype.name=...的時候實際上是對這個對象的修改,而使用Person.prototype={...}實際上是將這個屬性原本的指針指向了另一個新創建的對象而不是原來編譯器自動創建的那個:
而li的constructor屬性自然是繼承自Person.prototype,所以constructor自然也就跟著改變了,如果在編程的過程中constructor這個屬性很重要的話可以通過下面的方式
function Person() { } Person.prototype = { constructor:Person name : "Li", age : 18, sayHello : function () { console.log(`Hello! my name is ${this.name},${this.age}歲了`); } }; var li = new Person(); console.log(li instanceof Object); // true console.log(li instanceof Person); // true console.log(li.constructor === Person); // true console.log(li.constructor === Object); // false console.log(Person.prototype.constructor); // Person結語:
參考:《JavaScript高級程序設計》
這是我對JS原型鏈部分的總結與思考,也是我寫的第一篇這么正式的技術文檔,如有紕漏之處,歡迎大家批評指正
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102330.html
摘要:要用作原型的對象。函數對象可以創建普通對象,這個我們上面講過了回顧一下這是一個自定義構造函數普通對象沒法創建函數對象,凡是通過創建的對象都是函數對象,其他都是普通對象通常通過創建,可以通過來判斷。 關于js的原型和原型鏈,有人覺得這是很頭疼的一塊知識點,其實不然,它很基礎,不信,往下看要了解原型和原型鏈,我們得先從對象說起 創建對象 創建對象的三種方式: 對象直接量 通過對象直接量創建...
摘要:每個類有三部分構成第一部分是構造函數內,供實例對象化復制用。第二部分是構造函數外,直接通過點語法添加,供類使用,實例化對象訪問不到。組合繼承還有一個要注意的地方在代碼處,將子類原型的屬性指向子類的構造函數。 前言 前一陣面試,過程中發現問到一些很基礎的問題時候,自己并不能很流暢的回答出來。或者遇到一些基礎知識的應用,由于對這些點理解的不是很深入,拿著筆居然什么都寫不出來,于是有了回顧一...
摘要:簡單回顧一下構造函數原型和實例對象之間的關系每個構造函數都有一個原型對象。找到生成構造函數的原型對象的構造函數,搜索其原型對象,找到了。 JS面向對象的程序設計之繼承的實現 - 原型鏈 前言:最近在細讀Javascript高級程序設計,對于我而言,中文版,書中很多地方翻譯的差強人意,所以用自己所理解的,嘗試解讀下。如有紕漏或錯誤,會非常感謝您的指出。文中絕大部分內容引用自《JavaS...
摘要:不區分類和實例的概念,而是通過原型來實現面向對象編程。新創建的的原型鏈是也就是說,的原型指向函數的原型。最后,創建一個對象代碼和前面章節完全一樣小明繼承用定義對象的另一個巨大的好處是繼承更方便了。 JavaScript不區分類和實例的概念,而是通過原型(prototype)來實現面向對象編程。 原型是指當我們想要創建xiaoming這個具體的學生時,我們并沒有一個Student類型可用...
摘要:最近看到一個關于原型鏈的問題,回顧一下原型鏈的知識點。說說為什么為什么是。首先不是自身的屬性,而是原型鏈上的,即的原型中。類似通過這樣來找到的值。,不是復制了對象,而是把指向了,所以對的修改會影響到的值。再看看這張圖一切都明朗了。 最近看到一個關于原型鏈的問題,回顧一下原型鏈的知識點。 function person(name) { this.name = name; ...
閱讀 710·2021-09-29 09:34
閱讀 2561·2019-08-30 15:53
閱讀 3368·2019-08-29 17:17
閱讀 766·2019-08-29 16:08
閱讀 1130·2019-08-29 13:03
閱讀 956·2019-08-27 10:54
閱讀 693·2019-08-26 13:39
閱讀 2864·2019-08-26 13:34