Javascript的原型和原型鏈你了解嗎
一、為什么要使用原型?怎樣去理解原型的出現(xiàn)
1、對(duì)象字面量創(chuàng)建對(duì)象的缺點(diǎn)
想要介紹原型,就不得不提為什么我們要使用原型,在js早期,我們創(chuàng)建一個(gè)對(duì)象,比較流行的做法是使用對(duì)象字面量去創(chuàng)建一個(gè)對(duì)象,例如:
const person = {
name: "wywy",
age: 21,
hobby: "聽周杰倫"
}
用這種方式去創(chuàng)建對(duì)象,雖然簡(jiǎn)潔明了,但是我們?nèi)绻枰笈康膭?chuàng)建這一類的對(duì)象,就像person這個(gè)對(duì)象,我們可能需要去創(chuàng)建多個(gè)不同的人,那么每一次都需要去聲明創(chuàng)建,這樣工作量是巨大的,為了解決這個(gè)問(wèn)題我們引入了工廠函數(shù)的概念。
2、工廠函數(shù)
什么是工廠函數(shù),顧名思義就可以把工廠函數(shù)看作是一個(gè)流水線的工廠,這個(gè)工廠的作用就是批量生產(chǎn)person對(duì)象,就像下面這樣:
function createPerson(name,age,hobby){
const obj = {};
obj.name = name;
obj.age = age;
obj.hobby = hobby;
return obj;
}
const person1 = createPerson("zs", 23, "滑板");
const person2 = createPerson("ls", 22, "聽歌");
console.log(person1);
console.log(person2);

這里我們?cè)趧?chuàng)建person對(duì)象的時(shí)候只需要調(diào)用這個(gè)函數(shù)即可,并把每個(gè)對(duì)象對(duì)應(yīng)的屬性值傳入就好了,這樣相對(duì)于用對(duì)象字面量去創(chuàng)建一個(gè)個(gè)的person確實(shí)簡(jiǎn)化了代碼量,但是工廠函數(shù)也有自身的缺點(diǎn),就是我們不能去判斷出這個(gè)對(duì)象的類型,按我們知道的在 js 中復(fù)雜引用數(shù)據(jù)類型進(jìn)行細(xì)分,有 Array,F(xiàn)unction,String 等,但是通過(guò)工廠函數(shù)模式創(chuàng)建的這些對(duì)象用控制臺(tái)打印去全部都是 Object 類型,假如你有多個(gè)工廠函數(shù),用多個(gè)工廠函數(shù)創(chuàng)建了多個(gè)實(shí)例,但是你卻并不知道這些對(duì)象屬于哪個(gè)工廠。為此又推出了構(gòu)造函數(shù)這個(gè)概念。
3、構(gòu)造函數(shù)
關(guān)于構(gòu)造函數(shù)其實(shí)它和工廠函數(shù)非常相似,在js的函數(shù)中其實(shí)并沒(méi)有單獨(dú)的一類函數(shù)叫做構(gòu)造函數(shù),構(gòu)造函數(shù)總是和new關(guān)鍵字一起使用,更準(zhǔn)確地說(shuō)一個(gè)函數(shù)被構(gòu)造調(diào)用了。當(dāng)一個(gè)構(gòu)造函數(shù)不使用new關(guān)鍵字調(diào)用時(shí),他和普通的函數(shù)無(wú)異。
function CreatePerson(name, age, hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
const person1 = new CreatePerson("zs", 23, "滑板");
const person2 = new CreatePerson("ls", 22, "聽歌");
console.log(person1);
console.log(person2);

仔細(xì)觀察,不難發(fā)現(xiàn)我們對(duì)上面的工廠函數(shù)進(jìn)行了以下幾點(diǎn)改造:
1、我們將函數(shù)名的首字母大寫了,在 js 中有個(gè)規(guī)定如果你以后打算將一個(gè)函數(shù)作為構(gòu)造函數(shù)去使用那么最好把它的函數(shù)名首字母大寫,來(lái)提醒使用者這是一個(gè)構(gòu)造函數(shù)。其實(shí)不大寫也不會(huì)有什么語(yǔ)法錯(cuò)誤。
2、我們?nèi)∠嗽诤瘮?shù)內(nèi)部顯示的聲明一個(gè)對(duì)象 “const obj = {}”,并一并取消了最后返回這個(gè)對(duì)象的操作 “return obj”。
3、在調(diào)用這個(gè)這個(gè) CreatePerson 函數(shù)時(shí)在前面加上了 new 關(guān)鍵字。

然后我們看這個(gè)結(jié)果,這個(gè)時(shí)候我們打印的對(duì)象不在是 Object 了,而是我們自己創(chuàng)建的函數(shù) CreatePerson。看來(lái)構(gòu)造函數(shù)確實(shí)解決了對(duì)象無(wú)法判定類型的問(wèn)題。
那么神奇的new關(guān)鍵字在后臺(tái)做了什么呢?其實(shí)它做了下面的五件事
1、在內(nèi)存中創(chuàng)建一個(gè)新的對(duì)象。
2、讓新對(duì)象的內(nèi)部特性 [[Prototype]] 保存函數(shù) CreatePerson 的 prototype 的屬性值,也就是把函數(shù)的原型的指針保存到了 [[Prototype]] 中。
3、把函數(shù)內(nèi)部的 this 指向這個(gè)新創(chuàng)建的對(duì)象。
4、執(zhí)行函數(shù)內(nèi)部的代碼。
5、如果函數(shù)本身沒(méi)有返回對(duì)象,那么就把這個(gè)新對(duì)象返回。
我們看構(gòu)造函數(shù)原來(lái)在后臺(tái)為我們做了這么多事。那么構(gòu)造函數(shù)就完美了嗎?并不是的,我們想一個(gè) person 是不是應(yīng)該也該給他加一個(gè)方法呢?那么我們就給加一個(gè)說(shuō)話的方法吧:
function CreatePerson(name, age, hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
// 添加一個(gè)方法
this.sayHi = function() {
console.log("你好,我叫" + this.name)
}
}
const person1 = new CreatePerson("zs", 23, "滑板");
const person2 = new CreatePerson("ls", 22, "聽歌");
console.log(person1);
console.log(person2);
但是我們發(fā)現(xiàn)這樣做無(wú)疑為每一個(gè)實(shí)例對(duì)象都添加了一個(gè) sayHi 方法,而且每個(gè)實(shí)例上的方法都不相等,但我們的目的是讓每個(gè)實(shí)例都有這么一個(gè)功能就好了,不必創(chuàng)建這么多的 sayHi 方法而去浪費(fèi)內(nèi)存空間。說(shuō)的直白一點(diǎn),比如家里有五個(gè)孩子,每個(gè)孩子都想玩 switch 游戲,那么家長(zhǎng)要給每個(gè)孩子買一臺(tái) switch 嗎?當(dāng)然家里有礦的當(dāng)我沒(méi)說(shuō),一般家庭是不是就買一臺(tái),然后哪個(gè)孩子想玩就管家長(zhǎng)去要就行了是不是。
同樣的,代碼就是對(duì)現(xiàn)實(shí)生活的抽象,那么我們是不是也可以這樣做,把方法添加到這些實(shí)例都擁有的一個(gè)爸爸是不是就好了,而仔細(xì)想想在 new 的五步中第二步是不是做了這么一件事,沒(méi)錯(cuò)他就是 js 中的原型。這個(gè)原型就是這些實(shí)例的爸爸。
說(shuō)了這么多我們總算要講原形了!!!
二、使用原型
通過(guò)上面的講解我們似乎對(duì)原型的作用有了大致的理解,就是把實(shí)例對(duì)象上需要用到的共有方法添加到原型上,而實(shí)例對(duì)象的自己的私有屬性寫在構(gòu)造函數(shù)內(nèi)部。
接下來(lái)我們對(duì)構(gòu)造函數(shù)進(jìn)行再一次改造:
function CreatePerson(name, age, hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
// 添加一個(gè)方法
this.sayHi = function() {
console.log("你好,我叫" + this.name)
}
}
const person1 = new CreatePerson("zs", 23, "滑板");
const person2 = new CreatePerson("ls", 22, "聽歌");
console.log(person1);
console.log(person2);
首先向大家說(shuō)明一點(diǎn)我并沒(méi)有按著定義一個(gè)構(gòu)造函數(shù),然后在構(gòu)造函數(shù)的原型上添加 sayHi 方法,接著使用 new 創(chuàng)建實(shí)例的順序來(lái)寫代碼。而是先 new 創(chuàng)建了實(shí)例,然后再在原型上添加方法,這樣的目的是想告訴大家,原型是具有動(dòng)態(tài)性的,即你先創(chuàng)建了實(shí)例,在實(shí)例之后給原型添加了方法那么實(shí)例依然是可以訪問(wèn)的。而且可以看到通過(guò)比較 person1 和 person2 的 sayHi 方法我們發(fā)現(xiàn)這是同一個(gè)方法。這樣我們完美的解決了構(gòu)造函數(shù)的問(wèn)題。
三、原型概念辨析
首先清楚兩個(gè)概念:
- 引用類型,都具有對(duì)象特性,即可自由擴(kuò)展屬性。(引用類型:Object、Array、Function、Date、RegExp)
每個(gè)函數(shù)function都有一個(gè)顯示原型prototype,每個(gè)實(shí)例對(duì)象都有一個(gè)隱式原型__proto__
function Fn() { // 內(nèi)部語(yǔ)句:this.prototype = {}
}
// 1、每個(gè)函數(shù)function都有一個(gè)prototype,即顯示原型(屬性)
console.log(Fn.prototype);
// 2、每個(gè)實(shí)例對(duì)象都有一個(gè)__proto__,可稱為隱式原型(屬性)
var fn = new Fn(); // 內(nèi)部語(yǔ)句: this.__proto__ = Fn.prototype
console.log(fn.__proto__);
兩個(gè)準(zhǔn)則:
在設(shè)計(jì)js原型原型鏈的時(shí)候遵循以下兩個(gè)準(zhǔn)則:
準(zhǔn)則一: 原型對(duì)象(即Fn.prototype)的 constructor 指向構(gòu)造函數(shù)本身。
準(zhǔn)則二: 實(shí)例對(duì)象(即 fn )的__proto__ 指向其構(gòu)造函數(shù)的顯示原型。
function Fn() {}
var fn = new Fn();
// 原型對(duì)象的 constructor 指向構(gòu)造函數(shù)本身
console.log(Fn.prototype.constructor === Fn); // true
// 對(duì)象的隱式原型的值為其對(duì)應(yīng)構(gòu)造函數(shù)的顯示原型的值
console.log(Fn.prototype === fn.__proto__); // true
理解Function與Object特例
每個(gè)函數(shù)都是 Function 的實(shí)例,所以每個(gè)函數(shù)既有顯示原型又有隱式原型,所有函數(shù)的隱式原型指向 Function.prototype; 構(gòu)造器Function的構(gòu)造器是它自身。
// function Foo() {} 相當(dāng)于 var Foo = new Function()
// Function = new Function() => Function.__proto__ = Function.prototype
// Object 作為構(gòu)造函數(shù)時(shí),其 __proto__ 內(nèi)部屬性值指向 Function.prototype
// Object.__proto__ = Function.prototype
// Function.constructor=== Function;//true
Object構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象包裝器。JavaScript中的所有對(duì)象都來(lái)自 Object,所有對(duì)象都是Object的實(shí)例;所有對(duì)象從Object.prototype繼承方法和屬性,盡管它們可能被覆蓋。
// Fn的原型對(duì)象(Fn.prototype)也來(lái)自O(shè)bject,故Fn.prototype.__proto__ = Object.prototype
function Fn() {}
原型鏈:
讀取某個(gè)對(duì)象的屬性時(shí),會(huì)自動(dòng)找到原型鏈中查找。
- 1、現(xiàn)在自身屬性中查找,找到返回
- 2、找不到則繼續(xù)沿著__proto__這條鏈向上查找,找到返回
- 3、如果最終沒(méi)有找到。返回undefined設(shè)置對(duì)象的屬性值時(shí),不會(huì)查找原型鏈,如果當(dāng)前對(duì)象中沒(méi)有此屬性,直接添加此屬性并設(shè)置其值方法一般定義在原型中,屬性一般通過(guò)構(gòu)造函數(shù)定義在對(duì)象本身
原型鏈就是一個(gè)過(guò)程,原型是原型鏈這個(gè)過(guò)程中的一個(gè)單位,貫穿整個(gè)原型鏈
圖解

四、原型鏈練習(xí)
//練習(xí)題1
function A(){
}
A.prototype.n = 1;
var b = new A();
A.prototype = {
n:2,
m:3
}
var c = new A();
console.log(b.n,b.m,c.n,c.m)
// 1 undefined 2 3
// 測(cè)試題2
var F = function() {
};
Object.prototype.a = function() {
console.log('a()');
};
Function.prototype.b = function() {
console.log('b()');
};
var f = new F();
f.a(); // a()
f.b(); // 報(bào)錯(cuò):Uncaught TypeError: f.b is not a function
F.a(); // a()
F.b(); // b()
原型、原型鏈的意義與使用場(chǎng)景:
原型對(duì)象的作用,是用來(lái)存放實(shí)例中共有的那部分屬性、方法、可以大大減少內(nèi)存消耗。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
JavaScript數(shù)組常用方法解析及數(shù)組扁平化
這篇文章主要介紹了JavaScript數(shù)組常用方法解析及數(shù)組扁平化,數(shù)組作為在開發(fā)中常用的集合,除了for循環(huán)遍歷以外,還有很多內(nèi)置對(duì)象的方法,包括map,以及數(shù)組篩選元素filter等2022-07-07
JavaScript中style.left與offsetLeft的使用及區(qū)別詳解
這篇文章主要介紹了JavaScript中style.left與offsetLeft的使用及區(qū)別詳解的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
JS獲取指定月份的天數(shù)兩種實(shí)現(xiàn)方法
這篇文章主要介紹了JS獲取指定月份的天數(shù)兩種實(shí)現(xiàn)方法,需要的朋友可以參考下2018-06-06
【經(jīng)驗(yàn)總結(jié)】編寫JavaScript代碼時(shí)應(yīng)遵循的14條規(guī)律
這篇文章主要介紹了編寫JavaScript代碼時(shí)應(yīng)遵循的14條規(guī)律,涉及javascript變量的定義,函數(shù)、表單、json的使用,邏輯運(yùn)算與頁(yè)面元素操作技巧等,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2016-06-06
form.submit()不能提交表單的錯(cuò)誤原因及解決方法
button的id不要設(shè)置為submit,否則可能會(huì)引起混淆,導(dǎo)致表單的submit()方法不能提交表單2014-10-10
js使用oclif開發(fā)命令行工具實(shí)現(xiàn)批量修改文件名
前端開發(fā)工作中常用的很多CLI命令相信大家已經(jīng)很熟悉了,很方便很實(shí)用,能夠快速幫助你創(chuàng)建項(xiàng)目,快速執(zhí)行某些重復(fù)性操作,下面我們就來(lái)學(xué)習(xí)一下如何使用CLI命令批量修改文件名吧2023-12-12
JavaScript實(shí)現(xiàn)數(shù)字?jǐn)?shù)組正序排列的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)數(shù)字?jǐn)?shù)組正序排列的方法,涉及javascript中sort方法的使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
JavaScript實(shí)現(xiàn)頁(yè)面滾動(dòng)圖片加載(仿lazyload效果)
網(wǎng)上的很多這樣的效果都是用jQuery的方法,可是如果不用jQuery的站長(zhǎng)難道就不能用這種方法了么2011-07-07

