摘要:原型鏈原型鏈實(shí)際上是中的實(shí)現(xiàn)繼承的機(jī)制,在搞懂原型鏈之前首先要搞懂幾個(gè)概念,普通對(duì)象和函數(shù)對(duì)象,構(gòu)造函數(shù),對(duì)于很多人來說是混雜不清的概念,但是想要弄清楚原型鏈我們必須了解什么是首先,只能在存在與函數(shù)中其次,其實(shí)是當(dāng)前函數(shù)所在上下文環(huán)境,再簡(jiǎn)
原型鏈
原型鏈實(shí)際上是JavaScript中的實(shí)現(xiàn)繼承的機(jī)制,在搞懂原型鏈之前首先要搞懂幾個(gè)概念:this,普通對(duì)象和函數(shù)對(duì)象,構(gòu)造函數(shù),new
thisthis對(duì)于很多人來說是混雜不清的概念,但是想要弄清楚原型鏈我們必須了解什么是this
首先,this只能在存在與函數(shù)中
其次,this其實(shí)是當(dāng)前函數(shù)所在上下文環(huán)境,再簡(jiǎn)單一點(diǎn)也可以理解為this返回一個(gè)當(dāng)前函數(shù)所在的對(duì)象,也就是說想要知道this是什么我們只需要關(guān)注是誰調(diào)用了this所在的函數(shù)就可以了
如下邊的代碼zhao.sayName()是zhao調(diào)用的sayName函數(shù)所以sayName中的this自然指的就是zhao這個(gè)對(duì)象,而下方 var liName = zhao.sayName語句是將sayName這個(gè)函數(shù)賦值給liName,調(diào)用liName就相當(dāng)于在最頂層也就是window下直接調(diào)用sayName,this的指向自然就是window這個(gè)最頂層對(duì)象
var name = "Li" var zhao = { name: "Zhao", sayName: function () { console.log(this.name); } } zhao.sayName() // Zhao var liName = zhao.sayName; liName() // Li普通對(duì)象與函數(shù)對(duì)象
JavaScript中一切都可以看作對(duì)象,但是實(shí)際上對(duì)象也是有區(qū)別的,對(duì)象分為普通對(duì)象和函數(shù)對(duì)象
// 普通對(duì)象 var o1 = {} var o2 = new Object() var o3 = new f1() // 函數(shù)對(duì)象 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構(gòu)建的對(duì)象都是函數(shù)對(duì)象,并且只有函數(shù)對(duì)象才有prototype屬性,普通對(duì)象沒有
prototype 原型prototype又是什么呢?
當(dāng)我們創(chuàng)建函數(shù)的時(shí)候,編譯器會(huì)自動(dòng)為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這和屬性指向一個(gè)包含constructor屬性的對(duì)象,而這個(gè)屬性又默認(rèn)指回原函數(shù),讀起來有點(diǎn)繞對(duì)吧,大概是這樣的
function Person() { // prototype = { // constructor: Person, // } }
每個(gè)函數(shù)對(duì)象都有一個(gè)prototype(原型)屬性,在我看來prototype屬性的意義:
創(chuàng)建對(duì)象的模板
公開的共享空間
這兩點(diǎn)等學(xué)習(xí)了下邊new命令你就會(huì)明白了
constructor 構(gòu)造函數(shù)函數(shù)對(duì)象的一種用法就是構(gòu)造函數(shù),通過構(gòu)造函數(shù)可以構(gòu)建一個(gè)函數(shù)對(duì)象的實(shí)例(普通對(duì)象)
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]
按照慣例,構(gòu)造函數(shù)的命名以大寫字母開頭,非構(gòu)造函數(shù)以小寫字母開頭,通過構(gòu)造函數(shù)構(gòu)造的普通對(duì)象都會(huì)有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向構(gòu)造該對(duì)象的構(gòu)造函數(shù)
new命令 new命令的工作機(jī)制創(chuàng)建一個(gè)空對(duì)象作為要返回對(duì)象的實(shí)例
將這個(gè)空對(duì)象的原型(__ proto __)指向構(gòu)造函數(shù)的prototype屬性
將這個(gè)空對(duì)象賦值給構(gòu)造函數(shù)內(nèi)部的this
執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼
原型鏈下面我們來看看構(gòu)造函數(shù)構(gòu)建一個(gè)普通對(duì)象的時(shí)候發(fā)生了什么
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
創(chuàng)建一個(gè)空對(duì)象作為要返回對(duì)象的實(shí)例
{}
將這個(gè)空對(duì)象的原型(__ proto __)指向構(gòu)造函數(shù)的prototype屬性
{ __proto__:Person.prototype; }
將這個(gè)空對(duì)象賦值給構(gòu)造函數(shù)內(nèi)部的this
this = { __proto__:Person.prototype; }
執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼
this = { __proto__:Person.prototype; name: "Li"; age: 18; }
所以li這個(gè)對(duì)象中只有name和age兩個(gè)屬性,為什么li.sayHello()會(huì)輸出Hello! my name is Li呢?
這就是原型鏈,當(dāng)給定的屬性在當(dāng)前對(duì)象中找不到的情況下,會(huì)沿著__proto__這個(gè)屬性一直向?qū)ο蟮纳嫌稳ふ遥钡絖_proto__這個(gè)屬性指向null為止,如果找到指定屬性,查找就會(huì)被截?cái)啵V?/p>
上面這張圖是整個(gè)JavaScript的原型鏈體系,為了讓這張圖更直觀所以我將構(gòu)造函數(shù)的prototype屬性多帶帶提了出來,恩,其實(shí)畫在構(gòu)造函數(shù)內(nèi)部也可,但同時(shí)因?yàn)閷?duì)象是引用類型,所以這樣畫也沒毛病吧
_ proto _ 和 prototype這兩個(gè)屬性經(jīng)常會(huì)被我們混淆,那么我們回過頭再來總結(jié)一下
prototype:只有函數(shù)對(duì)象才具有的屬性,它用來存放的是構(gòu)造函數(shù)希望構(gòu)建的實(shí)例具有的共享的屬性和方法,主要用于構(gòu)造函數(shù)的實(shí)例化
_proto_ : 所有對(duì)象都具有的屬性,它指向的是當(dāng)前對(duì)象在原型鏈上的上級(jí)對(duì)象,主要作用是讓編譯器在由__proto__這個(gè)屬性構(gòu)成的原型鏈上查找特定的屬性和方法
補(bǔ)充 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();
關(guān)于Person和Person1兩種構(gòu)造函數(shù)的寫法有什么不同呢?
一般來說寫在prototype原型對(duì)象中的屬性和方法都是公用的,也就是說寫在構(gòu)造函數(shù)中的屬性在構(gòu)建普通對(duì)象的時(shí)候,都會(huì)在新對(duì)象中重新定義,也就是從內(nèi)存的角度來說又會(huì)多占用一些內(nèi)存空間,所以我們將構(gòu)造函數(shù)的所有屬性和方法都寫在prototype原型中不好嗎?
但是原型函數(shù)也是有缺點(diǎn)的:
不夠靈活
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();
這種方式構(gòu)造的所有對(duì)象都是一個(gè)模板,雖然我們也可以在當(dāng)前對(duì)象下進(jìn)行修改,但這樣一點(diǎn)也不優(yōu)雅,不規(guī)整,而且從某種意義上來說也是對(duì)內(nèi)存的浪費(fèi)
對(duì)于引用類型的修改會(huì)被全部共享
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中,基本類型的修改可以明確的通過創(chuàng)建或修改在當(dāng)前對(duì)象下的屬性對(duì)原型鏈進(jìn)行截?cái)啵窍駭?shù)組,對(duì)象這種引用類型的值雖然也可以通過在當(dāng)前對(duì)象中創(chuàng)建該屬性來對(duì)原型鏈進(jìn)行截?cái)啵且徊蛔⒁饩涂赡軙?huì)出現(xiàn)上面這種情況直接對(duì)原型進(jìn)行了修改
構(gòu)造函數(shù)與原型相結(jié)合所以,用構(gòu)造函數(shù)來定義實(shí)例屬性,用原型定義方法和共享的屬性,這樣寫就比較優(yōu)雅了
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" ]創(chuàng)建對(duì)象的幾種方式
構(gòu)造函數(shù)方式
法一用構(gòu)造函數(shù)構(gòu)造一個(gè)新對(duì)象
var A = function () { }; var a = new A(); console.log(a.constructor); // [Function:A] console.log(a.__proto__ === A.prototype); //true
字面量方式
法二的本質(zhì)來說和法一是一樣的,就是隱式調(diào)用原生構(gòu)造函數(shù)Object來構(gòu)造新對(duì)象
var a = {}; // var a = new Object(); console.log(a.constructor); // [Function:Object] console.log(a.__proto__ === Object.prototype); //true
create方式
法三Object.create是以一個(gè)普通對(duì)象為模板創(chuàng)建一個(gè)新對(duì)象
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創(chuàng)建對(duì)象的方式,可以說:__ proto __ === constructor.prototype;
constructor前面我們說道prototype的時(shí)候進(jìn)行原型屬性的賦值的時(shí)候,采用的是逐項(xiàng)賦值,那么當(dāng)我直接將對(duì)象賦值給prototype屬性的時(shí)候會(huì)發(fā)生什么呢?
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
這時(shí)候我們就發(fā)現(xiàn)我們構(gòu)建的li對(duì)象的constructor不再指向它的構(gòu)造函數(shù)Person,而是指向了Object,并且Person原型Person.prototype的constructor指向也指向了Object,這是什么原因呢?
其實(shí),根源出現(xiàn)在Person.prototype上,上邊我們提到過,其實(shí)我們?cè)趯憳?gòu)造函數(shù)的時(shí)候?qū)嶋H上是這樣的
function Person() { // prototype = { // constructor : Person // } }
當(dāng)我們構(gòu)建Person構(gòu)造函數(shù)的時(shí)候,編譯器會(huì)自動(dòng)生成一個(gè)帶有指向Person的constructor屬性的對(duì)象,并把這個(gè)對(duì)象賦值給Person.prototype,我們又知道js中對(duì)象是引用類型,當(dāng)我們使用Person.prototype.name=...的時(shí)候?qū)嶋H上是對(duì)這個(gè)對(duì)象的修改,而使用Person.prototype={...}實(shí)際上是將這個(gè)屬性原本的指針指向了另一個(gè)新創(chuàng)建的對(duì)象而不是原來編譯器自動(dòng)創(chuàng)建的那個(gè):
而li的constructor屬性自然是繼承自Person.prototype,所以constructor自然也就跟著改變了,如果在編程的過程中constructor這個(gè)屬性很重要的話可以通過下面的方式
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結(jié)語:
參考:《JavaScript高級(jí)程序設(shè)計(jì)》
這是我對(duì)JS原型鏈部分的總結(jié)與思考,也是我寫的第一篇這么正式的技術(shù)文檔,如有紕漏之處,歡迎大家批評(píng)指正
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/102330.html
摘要:要用作原型的對(duì)象。函數(shù)對(duì)象可以創(chuàng)建普通對(duì)象,這個(gè)我們上面講過了回顧一下這是一個(gè)自定義構(gòu)造函數(shù)普通對(duì)象沒法創(chuàng)建函數(shù)對(duì)象,凡是通過創(chuàng)建的對(duì)象都是函數(shù)對(duì)象,其他都是普通對(duì)象通常通過創(chuàng)建,可以通過來判斷。 關(guān)于js的原型和原型鏈,有人覺得這是很頭疼的一塊知識(shí)點(diǎn),其實(shí)不然,它很基礎(chǔ),不信,往下看要了解原型和原型鏈,我們得先從對(duì)象說起 創(chuàng)建對(duì)象 創(chuàng)建對(duì)象的三種方式: 對(duì)象直接量 通過對(duì)象直接量創(chuàng)建...
摘要:每個(gè)類有三部分構(gòu)成第一部分是構(gòu)造函數(shù)內(nèi),供實(shí)例對(duì)象化復(fù)制用。第二部分是構(gòu)造函數(shù)外,直接通過點(diǎn)語法添加,供類使用,實(shí)例化對(duì)象訪問不到。組合繼承還有一個(gè)要注意的地方在代碼處,將子類原型的屬性指向子類的構(gòu)造函數(shù)。 前言 前一陣面試,過程中發(fā)現(xiàn)問到一些很基礎(chǔ)的問題時(shí)候,自己并不能很流暢的回答出來。或者遇到一些基礎(chǔ)知識(shí)的應(yīng)用,由于對(duì)這些點(diǎn)理解的不是很深入,拿著筆居然什么都寫不出來,于是有了回顧一...
摘要:簡(jiǎn)單回顧一下構(gòu)造函數(shù)原型和實(shí)例對(duì)象之間的關(guān)系每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象。找到生成構(gòu)造函數(shù)的原型對(duì)象的構(gòu)造函數(shù),搜索其原型對(duì)象,找到了。 JS面向?qū)ο蟮某绦蛟O(shè)計(jì)之繼承的實(shí)現(xiàn) - 原型鏈 前言:最近在細(xì)讀Javascript高級(jí)程序設(shè)計(jì),對(duì)于我而言,中文版,書中很多地方翻譯的差強(qiáng)人意,所以用自己所理解的,嘗試解讀下。如有紕漏或錯(cuò)誤,會(huì)非常感謝您的指出。文中絕大部分內(nèi)容引用自《JavaS...
摘要:不區(qū)分類和實(shí)例的概念,而是通過原型來實(shí)現(xiàn)面向?qū)ο缶幊獭P聞?chuàng)建的的原型鏈?zhǔn)且簿褪钦f,的原型指向函數(shù)的原型。最后,創(chuàng)建一個(gè)對(duì)象代碼和前面章節(jié)完全一樣小明繼承用定義對(duì)象的另一個(gè)巨大的好處是繼承更方便了。 JavaScript不區(qū)分類和實(shí)例的概念,而是通過原型(prototype)來實(shí)現(xiàn)面向?qū)ο缶幊獭?原型是指當(dāng)我們想要?jiǎng)?chuàng)建xiaoming這個(gè)具體的學(xué)生時(shí),我們并沒有一個(gè)Student類型可用...
摘要:最近看到一個(gè)關(guān)于原型鏈的問題,回顧一下原型鏈的知識(shí)點(diǎn)。說說為什么為什么是。首先不是自身的屬性,而是原型鏈上的,即的原型中。類似通過這樣來找到的值。,不是復(fù)制了對(duì)象,而是把指向了,所以對(duì)的修改會(huì)影響到的值。再看看這張圖一切都明朗了。 最近看到一個(gè)關(guān)于原型鏈的問題,回顧一下原型鏈的知識(shí)點(diǎn)。 function person(name) { this.name = name; ...
閱讀 705·2021-09-29 09:34
閱讀 2555·2019-08-30 15:53
閱讀 3363·2019-08-29 17:17
閱讀 763·2019-08-29 16:08
閱讀 1121·2019-08-29 13:03
閱讀 952·2019-08-27 10:54
閱讀 690·2019-08-26 13:39
閱讀 2861·2019-08-26 13:34