原型的概述程序員
咱們建立的每一個函數都有一個 prototype(原型)屬性,這個屬性是一個對象,它的用途是包含能夠由特定類型的全部實例共享的屬性和方法。瀏覽器
邏輯上能夠這麼理解:prototype 經過調用構造函數而建立的那個對象的原型對象。函數
使用原型的好處可讓全部對象實例共享它所包含的屬性和方法。也就是說,沒必要在構造函數中定義對象信息,而是能夠直接將這些信息添加到原型中測試
注意:若是函數做爲普通函數調用prototype沒有任何做用this
使用原型建立對象spa
function Box() {} //聲明一個構造函數,函數體內什麼都沒有,若是有叫作實例屬性,實例方法 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; /*比較一下原型內的方法地址是否一致:*/ var box1 = new Box(); var box2 = new Box(); console.log(box1) // Box {} //向box1中添加a屬性,a屬性是box1本身的,其餘對象沒有 box1.a = "我是box1中的a"; console.log(box1) // Box { a: '我是box1中的a' } console.log(box1.a) // 我是box1中的a console.log(box2.a) // undefined, box2中沒有a這個屬性 console.log(box1.run()) // Lee100運行中... console.log(Box.prototype) // Box { name: 'Lee', age: 100, run: [Function] } console.log(box1.run == box2.run); //true,方法的引用地址保持一致
當函數以構造函數的形式調用時,它所建立的對象中都會有一個隱含的屬性,指向該構造函數的原型對象,咱們能夠經過__proto__來訪問該屬性prototype
function fn() { } console.log(fn.__proto__) // [Function]
原型對象就至關於一個公共的區域,全部同一個類的實例均可以訪問到這個原型對象,咱們能夠將對象中共有的內容,統一設置到原型對象中指針
之後咱們建立構造函數時,能夠將這些對象共有的屬性和方法,統一添加到構造函數的原型對象中,這樣不用分別爲每個對象添加,也不會影響到全局做用域,就可使每一個對象都具備這些屬性和方法了code
當咱們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,若是有則直接使用,若是沒有則會去原型對象中尋找,若是找到則直接使用對象
原型對象的原理
在原型模式聲明中,多了兩個屬性,這兩個屬性都是建立對象時自動生成的。
__proto__屬性是實例指向原型對象的一個指針,它的做用就是指向構造函數的原型屬性 constructor。經過這兩個屬性,就能夠訪問到原型裏的屬性和方法了。
IE 瀏覽器在腳本訪問__proto__會不能識別,火狐和谷歌瀏覽器及其餘某些瀏覽器均能識別。雖然能夠輸出,但沒法獲取內部信息。
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); var box2 = new Box(); console.log(box1.prototype);//這個屬性是一個對象,訪問不到:undefined console.log(box1.__proto__); //這個屬性是一個指針指向prototype原型對象,打印結果是[object Object] 在IE中結果是undefined // constructor是構造函數的屬性,獲取構造函數自己 //做用是被原型指針定位,而後等到構造函數自己,其實就是對象實例對應的原型對象 console.log(box1.constructor); // [Function: Box] console.log(box1.age);//能夠直接訪問原型對象中的屬性和方法,由於底層會自動調用prototype和__proto__和constructor等屬性
判斷一個對象是否指向了該構造函數的原型對象(即判斷一個對象實例是否是指向了對象的原型對象)可使用 isPrototypeOf()方法來測試。
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); var box2 = new Box(); console.log(box1.age);//結果打印出age的值:100 console.log(Box.prototype.isPrototypeOf(box1)); //只要實例化對象,即都會指向:true
每一個函數都有一個prototype屬性, 它默認指向一個Object空對象(即稱爲: 原型對象),原型對象中有一個屬性constructor, 它指向函數對象
// 每一個函數都有一個prototype屬性, 它默認指向一個對象(即稱爲: 原型對象) console.log(Date.prototype, typeof Date.prototype) // Date {} 'object' function fn() { } console.log(fn.prototype, typeof fn.prototype) // fn {} 'object' // 原型對象中有一個屬性constructor, 它指向函數對象 console.log(Date.prototype.constructor===Date) // true console.log(fn.prototype.constructor===fn) // true
原型模式的執行流程
先查找構造函數實例裏的屬性或方法,若是有,馬上返回;
若是構造函數實例裏沒有,則去它的原型對象裏找,若是有,就返回;
雖然咱們能夠經過對象實例訪問保存在原型中的值,但卻不能經過對象實例重寫原型中的值。
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); console.log(box1.name); //Lee,原型裏的值 box1.name = 'Jack'; console.log(box1.name); //Jack,就近原則, var box2 = new Box(); console.log(box2.name); //Lee,原型裏的值,沒有被 box1 修改
若是想要 box1 也能在後面繼續訪問到原型裏的值,能夠把構造函數裏的屬性刪除便可,具體以下
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); console.log(box1.name); //Lee,原型裏的值 box1.name = 'Jack'; console.log(box1.name); //Jack,就近原則,訪問的是box1本身的name屬性 delete box1.name; //刪除box1實例中屬性 console.log(box1.name); // box1實例屬性name被刪除了,找到的是原型裏面的name:Lee Box.prototype.name = 'kkk'//覆蓋原型中name屬性的值 console.log(box1.name);//結果是kkk delete Box.prototype.name;//刪除原型中的屬性值,以後結果是undefined console.log(box1.name);
如何判斷屬性是在構造函數的實例裏,仍是在原型裏?可使用 hasOwnProperty()函數來驗證
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); console.log(box1.name); //Lee,原型裏的值 box1.name = 'Jack'; console.log(box1.name); //Jack,就近原則, console.log(box1.hasOwnProperty('name'));//判斷實例是否存在指定屬性,實例裏有返回 true,不然返回 false
使用in檢查對象中是否含有某個屬性時,若是對象中沒有可是原型中有,也會返回true
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); console.log('name' in box1); //true,存在實例中或原型
咱們能夠經過hasOwnProperty() 方法檢測屬性是否存在實例中,也能夠經過 in 來判斷實例或原型中是否存在屬性。那麼結合這兩種方法,能夠判斷原型中是否存在屬性。
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); console.log(box1.hasOwnProperty('name')); console.log('name' in box1); //若是第一個爲false說明實例中沒有該屬性,而第二個爲true說明屬性存在原型中 //若是第一個爲true,說明屬性存在實例中
也能夠定義一個函數來判段,原理是同樣的
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; function isProperty(object, property) { //判斷原型中是否存在屬性 return !object.hasOwnProperty(property) && (property in object); } var box1 = new Box(); console.log(isProperty(box1, 'name')) //true,若是原型有
顯式原型與隱式原型
每一個函數function都有一個prototype,即顯式原型(在定義函數時自動添加的, 默認值是一個空Object對象)
每一個實例對象都有一個__proto__,可稱爲隱式原型(建立對象時自動添加的, 默認值爲構造函數的prototype屬性值)
function Fn() {} var fn = new Fn() console.log(Fn.prototype, fn.__proto__) // Fn {} Fn {} console.log(Fn.prototype === fn.__proto__) // true,構造函數的顯示原型的值就是實例對象的隱式原型的值
程序員能直接操做顯式原型, 但不能直接操做隱式原型(ES6以前)
原型鏈概述
原型對象也是對象,因此它也有原型,當咱們使用一個對象的屬性或方法時,會如今自身中尋找,自身中若是有,則直接使用
若是沒有則去原型對象中尋找,若是原型對象中有,則使用,若是沒有則去原型的原型中尋找,直到找到Object對象的原型
Object對象的原型沒有原型,若是在Object原型中依然沒有找到,則返回undefined
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; }; var box1 = new Box(); console.log(box1); //Box {} console.log(box1.__proto__); // Box { name: 'Lee', age: 100, run: [Function] } console.log(box1.__proto__.__proto__); // {} console.log(box1.__proto__.__proto__.__proto__); // 找到Object對象(Object對象的原型沒有原型),因此是null, console.log(box1.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); // true console.log('name' in box1);
原型鏈(圖解)
訪問一個對象的屬性時,先在自身屬性中查找,找到返回,若是沒有, 再沿着__proto__這條鏈向上查找, 找到返回,若是最終沒找到, 返回undefined
別名: 隱式原型鏈,做用: 查找對象的屬性(方法)
function Fn() { this.test1 = function () { console.log('test1()') } } Fn.prototype.test2 = function () { console.log('test2()') } var fn = new Fn() fn.test1() fn.test2() console.log(fn.toString()) console.log(fn.test3)//undefined fn.test3() // 報錯:fn.test3 is not a function
構造函數/原型/實體對象的關係(圖解)
var o1 = new Object(); var o2 = {};
構造函數/原型/實例對象的關係2(圖解)
function Foo(){ }
原型測試題
var A = function() {} 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