一文搞懂JavaScript原型鏈(看完絕對懂)
- 原型
- 原型鏈
- 原型指向改變後是如何新增方法和屬性
- 原型指向改變後的原型鏈
- 例項物件的屬性和原型物件的屬性重名
- 通過原型繼承
- 組合繼承
- 拷貝繼承
一,原型
問題: 請看以下程式碼,如果我們要建立100個物件,應該怎麼建立?
function Person(name, sex) { this.name = name; this.sex = sex; this.drink () { console.log('我想喝手磨咖啡!!') } } for (let i = 0; i < 100; i++) { var per = new Person('蘇大強', '男'); per.drink(); } 複製程式碼
從上面的程式碼可以看出,如果我們要建立100個Person物件,這樣要開一百個記憶體空間,每次都要呼叫drink()函式,由於drink()函式都是一樣的,每個記憶體空間裡都有它太過於浪費空間,那我們怎樣才能避免這種情況,減少記憶體呢?我們接下來引入原型prototype
function Person(name, sex) { this.name = name; this.sex = sex; } //為原型新增方法 Person.prototype.drink = function () { console.log('我想喝手磨咖啡!!') } //例項化物件 let per = new Person('蘇大強', '男'); per.drink(); 複製程式碼
我們運用了原型prototype,可以共享資料,減少記憶體空間。
二,原型鏈
我們既然清楚了原型,那我們再來看看原型鏈。首先我們列印一下建構函式Person和例項物件per。
console.dir(Person);//建構函式 console.dir(per);//例項物件 複製程式碼

從圖上看,建構函式中的prototype中的屬行和例項物件per中的__proto__中的屬性一模一樣,那我們想想它們相等嗎?我們可以驗證一下。
console.log(per.__proto__ === Person.prototype); 複製程式碼

由此我們可以判斷出,建構函式Person中的prototype原型和例項物件per中的__proto__原型指向是相同的,我們一般是先有建構函式再有例項物件,例項物件由建構函式建立,所以說例項物件中的__proto__原型指向的是建構函式中的原型prototype
例項物件中__proto__是原型,瀏覽器使用的。建構函式中的prototype是原型,程式設計師使用的
那接下來我們看一幅圖來看看原型鏈到底是什麼?

我們來分析分析整張圖
- 首先,建構函式中的prototype屬性指向自己的原型物件
- 然後,原型物件中的構造器指向的是,原型物件所在的建構函式
- 再然後,例項物件中的__proto__指向的是,它所在建構函式中prototype屬性所指向的原型物件
所以從上圖我們可以得到以下幾點:
- 例項物件的原型指向了建構函式中prototype屬性所指向的原型物件,所以例項物件和原型物件之間有關係,它和建構函式是一個間接的關係。
- 我們從程式碼中也可以得出,例項物件可以直接訪問原型物件中的屬性或方法。
- 例項物件和原型物件之間有關係,它們的關係是通過原型__proto__來連線的。
最終我們可以得出,原型鏈:它是一種關係,例項物件和原型物件之間的關係,關係是通過原型__proto__來聯絡的
三,原型指向改變後是如何新增方法和屬性
原型改變新增方法也無非就是兩種:1.在原型改變前新增加方法。2.在原型改變以後新增方法。
首先,我們來看第一種:
function Person(name, sex) { this.name = name; this.sex = sex; } Person.prototype.drink = function () { console.log('我想喝水!!') } function Student(name, sex) { this.name = name; this.sex = sex; } Student.prototype.eat = function () { console.log('我想吃東西!!') } //改變原型指向 Student.prototype = new Person('人', '男'); let stu = new Student('學生', '女'); stu.drink(); stu.eat(); 複製程式碼
我們來執行以下:

我們可以看到圖中的資訊,stu.eat()不是一個函式,剛才我們明明將eat()新增到了Student的原型上,怎麼現在報錯了?
原因是:由於Student的原型指向改變了,它指向了new Person('人', '男'),並且Person的原型上並沒有eat(),所以報錯,那麼第一種情況在原型改變之前新增是錯誤的!
我們再來看第二種情況:在原型改變之後新增方法。
function Person(name, sex) { this.name = name; this.sex = sex; } Person.prototype.drink = function () { console.log('我想喝水!!') } function Student(name, sex) { this.name = name; this.sex = sex; } //改變原型指向 Student.prototype = new Person('人', '男'); //為原型新增方法 Student.prototype.eat = function () { console.log('我想吃東西!!') } let stu = new Student('學生', '女'); stu.drink(); stu.eat(); 複製程式碼
我們來執行以下:

四,原型指向改變後的原型鏈
那麼,當原型指向改變之後,原型鏈會發生怎樣的改變呢?
那我們來們分析以下:
- 原型指向改變之前
- 原型指向改變之後
我們先來分析原型指向改變之前:
//人的建構函式 function Person(name) { this.name = name; } //為原型新增方法 Person.prototype.drink = function () { console.log('我想喝水!!') } //學生的建構函式 function Student(name) { this.name = name; } //為原型新增方法 Student.prototype.eat = function () { console.log('我想吃東西!!') } //例項物件 let per = new Person('老師'); let stu = new Student('學生'); console.dir(Person);//建構函式 console.dir(per);//例項物件 console.dir(Student);//建構函式 console.dir(stu);//例項物件 複製程式碼
我們執行以下這段程式碼:

請看每個prototype和__proto__,我們可以得到它們的原型鏈圖:

我們再來看看原型指向改變之後:
//人的建構函式 function Person(name) { this.name = name; } //為原型新增方法 Person.prototype.drink = function () { console.log('我想喝水!!') } //學生的建構函式 function Student(name) { this.name = name; } //為原型新增方法 Student.prototype.eat = function () { console.log('我想吃東西!!') } //改變學生的原型指向 Student.prototype = new Person('老師'); //例項物件 let stu = new Student('學生'); console.dir(Person);//建構函式 console.dir(new Person('老師'))//例項物件 console.dir(Student.prototype)//Student的原型物件 console.dir(Student);//建構函式 console.dir(stu);//例項物件 複製程式碼
我們來看看執行結果:

我們來分析分析:

這裡的序號沒有任何意義,相當於起的名字!!!
還沒有完,我們再來看圖:

原型鏈改變完畢!
五,例項物件的屬性和原型物件的屬性重名
當例項物件中的屬性和原型物件中的屬性重名時應該先訪問那個?
我們來看一看程式碼:
//人的建構函式 function Person(age, sex) { this.age = age; this.sex = sex; } //為原型新增屬性 Person.prototype.sex = "女"; //例項化物件 var per = new Person(10,"男"); console.log(per.sex); 複製程式碼
看圖:

如果在例項物件中找不到呢?我們來看程式碼:
function Person(age) { this.age = age; } //為原型新增屬性 Person.prototype.sex = "女"; //例項化物件 var per = new Person(10); console.log(per.sex); 複製程式碼
我們來看執行結果:

六,通過原型繼承
//js中通過原型來實現繼承 //人的建構函式 function Person(name, age, sex) { this.name = name; this.sex = sex; this.age = age; } //為原型新增方法 Person.prototype.eat = function () { console.log("人吃東西"); }; Person.prototype.sleep = function () { console.log("人在睡覺"); }; Person.prototype.play = function () { console.log("生活就是編程式碼!"); }; //學生的建構函式 function Student(score) { this.score = score; } //改變學生的原型的指向即可==========>學生和人已經發生關係 Student.prototype = new Person("小明", 10, "男"); //為原型新增方法 Student.prototype.study = function () { console.log("學習很累很累的哦."); }; var stu = new Student(100); console.log(stu.name); console.log(stu.age); console.log(stu.sex); stu.eat(); stu.play(); stu.sleep(); console.log("下面的是學生物件中自己有的"); console.log(stu.score); stu.study(); 複製程式碼
看執行結果:

七,組合繼承
//組合繼承:原型繼承+借用建構函式繼承 //人的建構函式 function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } Person.prototype.sayHi=function () { console.log("你好嗎?"); }; function Student(name, age, sex, score) { //借用建構函式:屬性值重複的問題 Person.call(this, name, age, sex); this.score = score; } //改變原型指向----繼承 Student.prototype = new Person();//不傳值 Student.prototype.eat = function () { console.log("吃東西"); }; //例項物件 var stu = new Student("金仔", 20, "男", "100分"); console.log(stu.name, stu.age, stu.sex, stu.score); stu.sayHi(); stu.eat(); var stu2=new Student("含仔", 20, "女", "100分"); console.log(stu2.name, stu2.age, stu2.sex, stu2.score); stu2.sayHi(); stu2.eat(); 複製程式碼
看執行結果:

八,拷貝繼承
function Person() {}; Person.prototype.age = 10; Person.prototype.sex = "男"; Person.prototype.height = 100; Person.prototype.play = function () { console.log("玩耍!"); }; var Student = {}; //Person的構造中有原型prototype,prototype就是一個物件,那麼裡面,age,sex,height,play都是該物件中的屬性或者方法 for (let key in Person.prototype) { Student[key] = Person.prototype[key]; } console.dir(Student); Student.play(); 複製程式碼
請看執行結果:

至此,本片文章的全部內容完畢!本人是一個前端新人,本片文章若哪裡有不正確的地方,請各位前端大佬不吝斧正!咱們攜手共同進步!再次感謝!