JavaScript由淺及深瞭解原型鏈(一)

一.什麼是js對象

1.簡單理解js對象

在瞭解原型鏈以前,咱們先要弄清楚什麼是JavaScript的對象,JavaScript對象又由哪些組成。有人說一個程序就是一個世界,那麼咱們能夠把對象稱之爲這個世界的組成類型,能夠是生物,植物,生活用品等等。咱們在java中管這些類型叫作,可是在JavaScript中沒有類的說法,固然ES6新標準中開始出現了類。可是在此以前,咱們都管這些類型叫作對象。那麼對象建立出來的實例就是就是組成該世界的各個元素,如一我的、一隻小狗、一棵樹等等。這些就稱之爲對象的實例。那麼每種類型都有它不一樣的屬性和方法,一樣的JavaScript對象也是由對象屬性和對象方法組成。固然了每一個實例還能夠存在與對象不同的方法與屬性。java

var person = {
    name:"xiaoming",    //對象屬性
    sayName:function(){    //對象方法
        console.log(this.name);
    }
}

2.js對象屬性的特性

在JavaScript對象中,每一個屬性都有其各自的特性,好比你的性別具備不可修改的特性。那麼下面簡單粗略介紹一下這幾個特性。這些特性在JavaScript中是不能直接訪問的,特性是內部值。瀏覽器

  • [[Configurable]]: 表示能不能刪除從新定義屬性,能不能修改屬性等 默認true
  • [[Enumerable]]: 表示能不能經過for-in遍歷等 默認true
  • [[Writeable]]: 表示能不能修改屬性值 默認true
  • [[Value]]: 表示屬性的值,寫入到這裏,讀從這裏讀 默認undefined

若是要修改屬性的默認特性,可使用Object.defineProperty()方法,固然在這裏就再也不繼續展開了。接下來咱們開始介紹對象的建立函數

二.建立JavaScript對象

1.工廠模式

function createPerson(name,sex){
    let obj = new Object();
    obj.name = name;
    obj.sex = sex;
    obj.sayName = function(){
        console.log(this.name);
    }
    return obj;
}

let p1 = new createPerson("小明","男");

這就是工廠模式,在函數內建立對象,而後在函數內封裝好後返回該對象。可是該方法有個缺點就是看不出該對象的類型,因而乎構造函數模式應運而生。優化

2.構造函數模式

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.sayName = {
        console.log("我是"+name+"貓");
    }
}

let Tom = new Cat("Tom","灰白");
let HelloKity = new Cat("HelloKity","粉紅");

構造函數模式工廠模式的區別在於,構造函數模式沒有用return語句,直接把屬性賦給了this語句,而且沒有顯式的建立對象。固然,若是細心的朋友應該會發現函數名首字母大寫了,這是約定在構造函數時將首字母大寫。this

用構造函數建立新實例時,必需要用new操做符。同時,每一個由構造函數建立的實例都會有一個constructor指向該構造函數spa

Tom.constructor == Cat  //true

這時候咱們就會想一個問題,咱們在建立不一樣的Cat實例時,咱們就會建立多個不一樣sayName函數,可是他們執行的功能都是同樣的,這時候咱們就會想要一種更優化的方法。這時,咱們須要引入原型屬性(prototype)的概念了prototype

3.原型模式

咱們建立的每一個函數裏面都會有個prototype屬性,這個就是原型屬性,這個屬性是個指針,指向一個該函數的原型對象。咱們能夠捋一捋對象,對象原型,實例這三者的關係,簡單來講,咱們能夠把對象想象成爸爸,那麼對象原型就是爺爺,實例的話比如是兒子。爺爺有的東西(屬性、方法),每一個兒子都會遺傳到的,固然若是爸爸把爺爺的東西修改了一下,那麼到兒子手上的就是爸爸修改過的東西了(方法重寫)。固然,兒子也算是爺爺骨肉嘛,那麼兒子就會有個指針[[prototype]]指向爺爺,在Chrome、Firefox等瀏覽器上面能夠用屬性__proto__能夠訪問到。指針

  • 那麼prototype__proto__區別在哪?

這麼說,簡單的說prototype是指向各自的爸爸,__proto__是指向各自的爺爺。固然這說法只是爲了更好理解這二者是有區別的。接下來我給你們作一個圖讓你們更好的理解這二者的區別。code

未命名文件 (3).png

這大概也是明白爲何對象實例存在個constructor指針指向對象了,由於對象原型上面存在這個屬性指向該對象,並且原型最初只包含該constructor屬性。而實例尋找屬性值的時候會向上找,先在實例中搜索該屬性,沒有的話向對象原型尋找。因此最後找到並返回該值。這樣就能很清楚的分開prototype__proto__的區別了。prototype是對象的屬性,而__proto__是對象實例的屬性。對象

那麼咱們基本瞭解prototype屬性之後,咱們就能夠給你們說說原型模式了。

function Cat(){
    
}

Cat.prototype.name = "Tom";
Cat.prototype.color = "灰白";
Cat.prototype.sayName = function(){
    console.log(this.name);
}

let cat1 = new Cat();
let cat2 = new Cat();

cat1.sayName();     //"Tom" 
cat2.sayName();     //"Tom"

console.log(cat1.color);      //"灰白"
console.log(cat2.color);      //"灰白"

//由於對象原型是共享屬性與方法,因此全部實例均可以訪問到

//接下來玩點更復雜的

Cat.sayName = function(){
    console.log("我是Cat");
}

cat1.sayName = function(){
    console.log("我是cat1");
}

let cat3 = new Cat();

cat1.sayName();     //"我是cat1"
cat2.sayName();     //"Tom"
cat3.sayName();     //"Tom"
Cat.sayName();      //"我是Cat"

這時候不少人就懵了,爲何cat3說的是"Tom",而不是輸出"我是Cat"。這是由於 Cat.sayName 這個函數是類方法,咱們要注意一點,Cat也是一個函數,函數就是一個對象,能夠爲其添加方法和屬性。因此咱們在實例中調用sayName並非調用該類方法。咱們還須要分清類方法與對象方法的區別。

function Person(){      //經過對象實例調用
    this.say = function(){
        console.log("我是Person對象方法");
    }
}

Person.say = function(){        //只能經過Person調用
    console.log("我是Person類方法");
}

Person.prototype.say = function(){      //經過對象實例調用
    console.log("我是Person對象原型方法");
}

到這裏,也許仍是會有點懵,爲何後面的cat1.sayName(); //"我是cat1"由於對象實例方法會屏蔽掉原型的方法。咱們以前說過,當代碼讀取對象的某個屬性時,它會先從該對象實例開始搜索,若是找不到再往上搜索。因此當你定義了對象實例的方法時,若是跟對象原型中的同名,那麼該對象實例的方法就會屏蔽掉對象原型中的方法。因此cat1第二次輸出的是我是cat1

到這裏,我再總結一下對象原型,對象與對象實例之間的關係。

  • 對象原型內的方法與屬性能夠供全部的對象實例訪問實現共享性
  • 對象的prototype屬性能夠找到對象原型而對象實例的[[proto]]能夠找到對象原型
  • 對象實例能夠重寫對象原型方法使其屏蔽對象原型的方法
  • 對象原型一開始只有constructor屬性該屬性指向該對象
  • 分清對象原型方法對象方法對象實例方法類方法區別類方法不須要經過實例化對象去訪問而其餘的都要對象實例去訪問

那麼到這裏咱們已經弄懂了對象原型,對象與對象實例之間的關係。下面我再介紹一種簡單的原型語法。

function Cat(){
    
}
Cat.prototype = {
    name:"Tom",
    color:"灰白",
    sayName:function(){
        console.log(this.name);
    },
}

這樣我就以字面量的形式建立了新對象,可是有個不同的地方就是constructor屬性不指向Cat,由於咱們建立一個函數就會建立它的原型對象,原型對象裏面自動得到constructor屬性,那麼咱們再這樣的狀況下,重寫了整個原型對象。因此此時的constructor屬性指向了Object。那麼咱們若是非要這個屬性怎麼辦?很好辦,咱們本身給它加上就好。

function Cat(){
    
}
Cat.prototype = {
    constructor:"Cat",
    name:"Tom",
    color:"灰白",
    sayName:function(){
        console.log(this.name);
    },
}

最後咱們講一下原型模式的缺點,原型模式的缺點也很明顯,就是它的共享性。成也共享敗也共享。這讓我忽然想起共享單車。廢話很少說,直接擼碼上來

function Cat(){
    
}

Cat.prototype.name = "Tom";
Cat.prototype.color = "灰白";
Cat.prototype.catchMouse = ["Jerry"];
Cat.prototype.sayName = function(){
    console.log(this.name);
}

let cat1 = new Cat();
let cat2 = new Cat();

cat1.catchMouse.push("Mickey");

console.log(cat1.catchMouse);       //["Jerry", "Mickey"]
console.log(cat2.catchMouse);       //["Jerry", "Mickey"]

由於原型上面的屬性是全部實例均可以訪問的,那麼當添加往catchMouse屬性添加一個值時,全部實例皆能夠訪問到該屬性。這就讓人們很頭疼了,每一個實例的屬性應該都是不同的纔對,起碼正常來講,可是這樣弄得你們都同樣的話,就有問題了。這時候,聰明的人應該均可以想到,將構造函數模式和原型模式組合起來就能夠了。

4.組合構造函數模式和原型模式

將其組合起來,結合他們兩的優勢,是廣泛認同度最高的對象建立模式

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.catchMouse = [];
}

Cat.prototype.sayName = function(){
    console.log(this.name);
}

let cat1 = new Cat("Tom","灰白");
let cat2 = new Cat("HellowKity","粉紅");

cat1.catchMouse.push("Jerry");

cat1.sayName();     //"Tom"
cat2.sayName();     //"HellowKity"
console.log(cat1.catchMouse);       //["Jerry"]
console.log(cat2.catchMouse);       //[]

最後

本篇介紹了對象與對象的建立方法。同時引入並介紹了對象原型的概念。解析了對象原型,對象與對象實例間的關係。咱們在下一篇將會解析原型鏈的概念以及對象的繼承與原型鏈的關係,帶你們敲開原型鏈的奧祕。

原創文章,轉載請註明出處

相關文章
相關標籤/搜索