1 - 原型是什麼?
1.1 - 原型指的是原型屬性,原型也是一個對象。
- 原型指的是兩個原型屬性:
- prototype: 顯示原型屬性
- _proto_: 隱式原型屬性
- 所有函數都有顯示原型屬性。 它的值叫做原型對象,這個原型對象默認有兩個屬性:
- constructor 指向函數本身
- _proto_ 指向Object.prototype
- 特殊: Object.prototype._proto_ === null 這個就是原型鏈盡頭
- 注意:箭頭函數: 沒有 顯示原型 屬性,不能被new調用 ,只有隱式原型。
- 所有對象都有隱式原型屬性。
- 對象的隱式原型屬性的值指向其構造函數顯示原型屬性的值
- 如果這個普通對象是你 new 的,構造函數就是你 new 的函數
- 如果不是,就是 new Obejct 產生的,所以構造函數就是 Object
- 所有函數都是 new Function 產生的,包括它自己 Function
- 所有數組都是 new Array 產生的
<script type="text/javascript">
/*
<<1>> 原型是什麼? 原型指的是原型屬性:
prototype: 所有函數都有prototype屬性, prototype屬性的值是對象。這個對象叫原型對象。
這個原型對象上默認有兩個屬性:
constructor: 指向函數自己本身
__proto__: Object.prototype __proto__的值默認指向它的大寫的Object的原型屬性(原型對象)上
__proto__: 所有實例對象都有__proto__屬性,它的值的是對象。
值爲它對應構造函數.prototype的值
*/
// 下面我們來驗證一下,上面說的這些結論:
// 原型只有函數有,其他的數組對象都沒有
// 我們用最常見的兩個典型函數,來打印輸出,發現值都是對象,
// 而且對象裏還有prototype屬性:
//1 - 用js內置的函數:Object
console.log(Object.prototype); //Object
//2 - 用我們隨便寫的一個函數:person
// 我們自己定義的函數,只會有兩個屬性:constructor和__proto__屬性
function person() { };
console.log(person.prototype);
// 打印結果:是一個對象,而且只有兩個屬性
// Object
//constructor: ƒ person()
//__proto__: Object
// 接下來,我們看一下person這個顯示原型上的constructor屬性的值 是不是等於 person 這個函數自己自身呢?
console.log(person.prototype.constructor === person); //true 說明constructor: 指向的是函數自己本身
//原型鏈盡頭是null (不用深究,是內置函數幫我們定義封裝好的,這個只需要記結論即可)
console.log(Object.prototype.__proto__); // null
// 問題:現有內置函數Object.prototype屬性呢,還是先有person.prototype.__proto__屬性呢?
// 答案是:
// 先有的這個內置函數:Object.prototype
// 纔有的 person.prototype.__proto__
// 因爲js有個特點:js在執行之前,Object和array這些就已經創建好了,
// js都是這些內置的函數,數組,對象等創建好之後,纔開始執行的我們所寫的代碼:
// 所以像 這些 內置的函數的話,都會放一些自己的方法。(即自帶一些方法)
console.log(person.prototype.__proto__ === Object.prototype); //true
// 接下來驗證的是:除了函數之外,其他的類型不含有原型屬性:
// 簡單舉例兩個看看吧:
var obj = {};
var arr = [];
console.log(obj.prototype); //undefined 證明對象沒有原型屬性
console.log(arr.prototype); //undefined 證明了數組也沒有原型屬性
// 2 - 現在來驗證這些結論:
// __proto__: 所有實例對象都有__proto__屬性,它的值的是對象。
// __proto__ 的值:爲它對應構造函數.prototype的值
// 雖然我們平時生活中,都是把new 出來的,才叫做實例對象,
// 但是事實上:所有對象都是實例對象(只不過我們看不到過程罷了)
var obj = {}; //其實這個對象是 new Object() 所產生的,只是我們沒有看到這個過程罷了
var array = []; // new Array()
var fn = function () { }; // new Function()
// 打印的結果是:這三個對象類型的,都有值,說明,都含有__proto__屬性
console.log(obj.__proto__);
console.log(array.__proto__);
console.log(fn.__proto__);
console.log(obj.__proto__ === Object.prototype); //true
console.log(array.__proto__ === Array.prototype); //true
console.log(fn.__proto__ === Function.prototype); //true
//寫一個Person 的函數
function Person() {
}
var p = new Person();
console.log(p.__proto__ === Person.prototype); //true
1.2 - 顯示原型和隱式原型
- 每個函數function都有一個prototype,即顯式原型
- 每個實例對象都有一個_proto_,可稱爲隱式原型
- 對象的隱式原型的值爲其對應構造函數的顯式原型的值
- 內存結構(圖)
- 總結:
-
函數的prototype屬性: 在定義函數時自動添加的, 默認值是一個對象
-
對象的__proto__屬性: 創建對象時自動添加的, 默認值爲構造函數的prototype屬性值
-
程序員能直接操作顯式原型, 但不能直接操作隱式原型(ES6之前),
-
2015年就推出的版本,ES6之後就可以了,直接操作了隱式原型了。
1.2.1 - new關鍵字到底做了什麼:
-
調用了函數
-
建了實例對象。並將函數的this指向實例對象
-
實例對象._proto_ = 構造函數.prototype (即構造函數上的所有的東西,我們都可以拿到了)
(即所有的實例對象都可以通過繼承來得到構造函數上的顯示原型的所有的東西) -
返回實例對象(如果函數返回值是對象,就返回這個對象,如果不是,返回實例對象)
<script type="text/javascript">
/*
new關鍵字:
1. 調用了函數
2. 創建了實例對象。並將函數的this指向實例對象
3. 實例對象.__proto__ = 構造函數.prototype (即構造函數上的所有的東西,我們都可以拿到了)
(即所有的實例對象都可以通過繼承來得到構造函數上的顯示原型的所有的東西)
4. 返回實例對象(如果函數返回值是對象,就返回這個對象,如果不是,返回實例對象)
*/
function Person(name, age) {
this.name = name; // p.name= name; p 中添加屬性name,值爲name
this.age = age;
// 如果寫了下面這個具體的返回值對象的話,就會返回這個:
// 返回一個對象,一般都不會這麼寫:
// return {a:123}
}
// 實例對象.__proto__ = 構造函數.prototype
// 因爲 實例對象的隱式原型屬性 等於 構造函數的顯示原型屬性
Person.prototype.setName = function (name) {
this.name = name;
};
// 打印結果裏的P1和p2 都有自身屬性:age 和 name 屬性和值
// 因爲我new的時候,會調用函數,並且把this指向實例對象P1,
// 就會把this.name = name;改爲: p.name= name; p 中添加屬性name,值爲name
// 所以 p身上就會有name和 age的直接屬性;
// 而且setName() 方法,就是會在p 身上的隱式原型屬性身上
// 所以this就等於是實例對象:
// 那我創建多個函數。他們指向的還是同一個原型對象,這樣就可以更加的複用代碼
var p1 = new Person('jack', 18);
var p2 = new Person('jack', 18);
console.log(p1, p2);
//Person
// age: 18
// name: "jack"
</script>
</body>
</html>
1.3 - 原型鏈
- 原型鏈(圖解)
- 訪問一個對象的屬性時,
- 先在自身屬性中查找,找到返回
- 如果沒有, 再沿着__proto__這條鏈向上查找, 找到返回
- 如果最終沒找到, 返回undefined
- 別名: 隱式原型鏈
(因爲我們的隱式原型和顯示原型是沒有關係,雖然值是同個值,但是其實看起來是沒有任何關係,
原型鏈是由隱式原型屬性構成的一種結構,它永遠不會看顯示原型,所以才又取了個名字:隱式原型鏈) - 原型鏈的作用: 就是用來查找對象的屬性或方法
<script type="text/javascript">
function Person(name) {
this.name = name;
}
// 這裏用了原型 來替代屬性,但是一般平時我們不用,
// 在這裏只是爲了更好的來理解原型鏈的用法:
Person.prototype.age = 18;
// 創建一個 p 的實例對象:
var p = new Person('jack');
console.log(p);
// {
// name: 'jack',
// __proto__: { 隱式原型的值
// age: 18,
// constructor: Person, 這個值指向函數自己本身
// __proto__: object.prototype 這個值指向函數原型
// }
console.log(p.name, p.age) //jack 18
//p.name:通過這個p找到P這個對象:然後找到name屬性,返回它的值
//p.age :先沿着自身屬性上找,發現沒有age屬性,然後就會沿着隱式原型屬性在找,發現找到了age屬性,
// 然後我們就着上面的案例,再增加些屬性代碼:
// 案例二:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.age = 18;
var p = new Person('jack', 20);
/*
現在的實例對象長成:這樣:
{
name: 'jack',
__proto__: {
age: 18,
constructor: Person, 這個值指向函數自己本身
__proto__: {
toString: fn,
valueOf: fn,
hasOwnProperty: fn,
__proto__: null --> 原型鏈的盡頭: Object.prototype.__proto__ === null
(即原型鏈盡頭,存在哪裏呢?存在大寫的Object的顯示原型上面的隱式原型上的值等於null)
}
}
}
*/
console.log(p.name, p.age, p.sex); //jack 20 undefined
// 上訴所講的這些規則就是原型鏈,由多個隱式原型來組成這樣的結構,這樣的叫原型鏈,
// 就是有個對象,對象裏面有個隱式原型屬性__proto__,然後隱式原型屬性__proto__又指向一個對象,
// 然後這個對象也有一個隱式原型屬性,又會指向另一個對象.就是這樣的一層層的指向關係。
// 然後我們找屬性的話,會先在自身裏面找, 找不到的話,再沿着隱式原型屬性__proto__再接着找,
// 再找不到,還會沿着隱式原型屬性__proto__接着找,直到找到值,找到返回。如果最終沒找到,找到的就會是原型鏈的盡頭:__proto__: null ,就會返回undefined
// 所以原型鏈的作用:
// 就是對象查找屬性的規則,即對象查找屬性的規則會沿着原型鏈找,自身找不到,也可以沿着自身屬性接着找,如果找到就會返回,
// 如果找不到,就會找到原型鏈的盡頭Object.prototype.__proto__ === null,返回值爲undefined。
</script>
</body>
</html>
1.4 - 原型圖:
1.4.1 - 拆分出來的原型圖
- 第一張圖:
這是一個構造函數,叫Foo
另外一個構造函數,是大寫的Object
所有函數都有一個顯示原型屬性prototype,
然後顯示原型屬性的值 是不是指向 原型對象Foo.prototype
然後原型對象Foo.prototype 裏面有一個constructor 屬性 指向了函數的本身
- 第二張圖:
- 隱式原型屬性指向了大寫的Object的顯示原型屬性
- 你new出來實例對象,構造函數就是你new的那個函數。
- 不是你new出來的實例對象,默認就是new Object產生的, 所以構造函數就是Object
下面:一樣,所有的函數都有顯示原型,指向了它的原型對象,
然後原型對象就默認有兩個屬性,一個constructor指向自己本身。
還有一個隱式原型值爲null
實例對象的隱式原型屬性指向的是其構造函數的顯示原型屬性。
首先這是一個對象,對象裏面有隱式原型
構造函數
這個是構造函數的顯示原型
- 第三張:說明這個顯示原型對象的構造函數,就是這個大寫的Object
-
第四張: f1 和 f2 就是 new Foo()產生出來的實例對象,
然後實例對象都有隱式原型屬性,它的值都是指向顯示原型的值
-
第五張:所有的普通對象都是new Object 產生的 ,所以普通對象也有隱式原型屬性,
指向其構造函數的顯示原型屬性。
-
第六張 function Foo()函數是怎麼產生的?
這句話就相當於是 var Foo = new Function
所以大寫的Fun是構造函數
Foo是實例對象,
那麼自然就有個結論:
實例對象的隱式原型的值指向構造函數顯示原型屬性的值,
-
第七張圖:
-
現在有個大寫的Object,首先函數也是對象,所以這個函數也會擁有對象的特性,
- 所以函數肯定也是new 某個東西 產生的
所有函數都是大寫的new Function產生的,包括它自己
所以,Object也是new Function 產生的。
-
其實大寫的Array和String也都是new Function 產生的,
-
所以這些大寫的構造函數都有這個特點:有一個隱式原型屬性,
-
第八張圖:
-
但是上面這個是個例外,大寫的Function可以看做是new自己產生的,
(所以這個大寫的Function即是構造函數又是實例對象)
-
所以,實例對象的隱式原型又指向構造函數的顯示原型,
-
因爲大寫的Function即是構造函數又是實例對象,所以就會指向同一個對象。
正常的隱式原型和顯式原型不是指向同一個對象,
-
-
第九張圖:
-
即所有函數都有顯示原型prototype屬性
-
因爲這個對象就是new Object 產生的,因爲不是你new出來的對象,就都是new Object產生的,
1.4.2 - 完整的原型圖:
總結的結論:
- 對象 都有隱式原型
- 對象的隱式原型就看它的構造函數是誰,構造函數就看如果是對象,你new的就是你new的那個函數,不是你new的就是new Object 產生的。
- 所有函數 都有 顯示原型prototype屬性
- 如果是函數,就是new Function 產生的,所以函數都有隱式原型,都是指向大寫的Function的顯示原型。
- 即所有的函數都是new Function產生的,包括Function
- Function._proto_ === Function.prototype
- 原型對象又都指向原型對象:
- 然後這個原型對象上默認有兩個屬性:
- constructor: 指向函數自己本身
- __proto__: Object.prototype
- (__proto__的值默認指向它的大寫的Object的原型屬性(原型對象)上)
- _proto_: 所有實例對象都有__proto__屬性,它的值的是對象,值爲它對應構造函數.prototype的值
1.5 - 原型讀取對象和設置對象屬性值需要注意的地方
接下來我們研究一下,原型鏈的一個小小的例子:
注意:
-
讀取 對象的屬性值時: 會自動到原型鏈中查找
-
設置 對象的屬性值時: 不會查找原型鏈, 如果當前對象中沒有此屬性, 直接添加此屬性並設置其值
-
方法 一般定義在原型中, 屬性 一般通過構造函數定義在對象本身上
<script type="text/javascript">
function Person(name, age) {
// 添加屬性一般都是在這裏面寫的:
this.name = name;
this.age = age;
}
// 思考問題:爲什麼我們往往會把方法放在原型上,而不把屬性放在原型上呢?
// (屬性一般是寫在對象裏面,方法寫在外面)
// 一般我們只會在這裏添加方法,
// 方法往往都是爲了複用,往往都是公共的,所以會放在原型上
// 這樣不管我們創建多少個實例對象,實例對象的隱式原型都是指向這個構造函數的顯示原型,
// 而我們的原型始終還是隻有這一個,
// 而隱式原型通過原型對象的方式,就能得到顯示原型上的這個setName方法
// 所以從頭到尾的setName() 方法,就只會有一份,這樣就大大提高效率,節省空間
// 原型上就是要放一些公共的東西
Person.prototype.setName = function (name) {
this.name = name;
};
// 一般我們都不會這麼寫屬性的,這裏是爲了解釋案例使用的
// 因爲屬性一般都是不一樣的,達不到複用的目的
// 而且寫在這裏的話,屬性都寫死了,不會變化
Person.prototype.sex = '男';
// 我們創建一個實例對象,並且傳參:
// 這裏不管我們創建多少個實例對象,實例對象的隱式原型都是指向這個構造函數的顯示原型,
var p = new Person('jack', 18);
/*
{
name: 'jack',
age: 18,
__proto__: {
setName: fn,
sex: '男'
}
}
*/
// 只有讀取屬性的時候,纔會查找原型鏈
console.log(p);
// 設置對象的屬性值時: 是不會從原型鏈上查找的,
// 自身有就有,沒有就添加一個,
// 自身有就會修改原來的值,
// 自身沒有這個屬性,則會添加一個,並設置值
// 接着我們修改:
p.age = 20; //自身有就會修改原來的值,
p.sex = '女'; //這個是自身沒有的屬性,則會添加一個,
/*
{
name: 'jack',
age: 20,
sex: '女',
__proto__: {
setName: fn,
sex: '男'
}
}
*/
// 會先在自身屬性上找,找到之後,原型就不看了
console.log(p.sex); //女
</script>
</body>
</html>