一、什麼是原型
在JavaScript中,每個對象都有一個指向另一個對象的引用,叫做原型。原型是JavaScript中一個比較重要的概念。
通過使用構造函數創建的對象,會自動擁有一個原型對象。原型的作用就是用來繼承屬性和方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
var person1 = new Person("Alice", 18);
var person2 = new Person("Bob", 20);
person1.sayHello(); // "Hello, Alice"
person2.sayHello(); // "Hello, Bob"
在這個例子中,我們定義了一個Person對象,通過Person的原型來增加了一個sayHello方法,然後我們根據這個構造函數來創建兩個不同的對象person1和person2,通過調用sayHello方法,我們可以看到兩個對象都可以正確的繼承到sayHello方法。
二、原型鏈的作用
原型是可以被繼承的,由此形成的繼承關係被稱為原型鏈。當對象調用方法或屬性時,如果本身找不到,就會去原型對象中尋找,如果還是找不到,則會繼續去原型對象的原型對象中查找,構成一個鏈式結構,直到最後查找完整個鏈才會返回undefined。
function Animal() {
this.name = "Animal";
}
Animal.prototype.eat = function() {
console.log(this.name + " is eating");
};
function Cat() {
this.name = "Cat";
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat();
cat1.eat(); // "Cat is eating"
在這個例子中,我們定義了一個Animal構造函數,然後在它的原型上添加一個eat方法。然後我們又定義了一個Cat構造函數,由於我們希望Cat繼承Animal的屬性和方法,所以我們將Cat的原型指向了Animal的實例,這樣一來Cat就可以調用到Animal的屬性和方法了。
當我們調用cat1的eat方法時,首先在cat1對象上尋找是否有eat屬性或方法,沒有找到,於是它會去cat1的原型對象Cat.prototype中查找,還是沒有找到,於是它又去Cat.prototype的原型對象Animal.prototype中查找,最終在Animal.prototype中找到了eat方法,然後執行。這就是原型鏈的查找過程。
三、原型鏈的細節
在JavaScript中,有些方法是自身屬性,有些是繼承屬性;也有一些屬性是自身的,有一些是繼承來的。由於原型鏈的繼承關係,有些情況下會出現一些意料之外的結果,下面舉幾個例子說明一下:
var str = "hello"; console.log(str.toString()); // "hello" var arr = [1,2,3]; console.log(arr.toString()); // "1,2,3"
在這兩個例子中,我們在字元串和數組對象上調用了toString方法,然而這兩個對象並沒有自己的toString方法,是從它們的構造函數Object中繼承了toString方法。這也是為什麼其他對象也可以使用toString方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
var person1 = new Person("Alice", 18);
var person2 = new Person("Bob", 20);
console.log("name" in person1); // true
console.log(person1.hasOwnProperty("name")); // true
console.log(person1.hasOwnProperty("sayHello")); // false
console.log("sayHello" in person1); // true
console.log(person1.__proto__.hasOwnProperty("sayHello")); // true
console.log(person1.__proto__.hasOwnProperty("name")); // false
在這個例子中,我們通過person1對象來演示hasOwnProperty方法和in方法的區別。hasOwnProperty是檢測對象自身是否擁有某個屬性,而in方法是檢測對象是否擁有某個屬性,不管是自身還是繼承而來的。因為person1對象自己擁有name屬性,所以hasOwnProperty返回true,而因為sayHello方法是從它的原型對象Person.prototype上繼承而來的,所以hasOwnProperty返回false,in方法卻返回true。
四、如何組合使用原型與構造函數
在實際編程中,常常需要使用原型和構造函數來一起工作。比如,希望繼承某個對象,並在繼承的同時傳遞一些參數;或者希望封裝私有變數,但又能讓實例對象共享某些公共的屬性和方法等等。以下給出一些例子:
// 1、通過原型繼承屬性和方法,通過構造函數來傳遞參數
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
function Student(name, age, school) {
Person.call(this, name, age);
this.school = school;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.sayHello = function() {
console.log("Hello, I'm a student, my name is " + this.name);
};
var student1 = new Student("Alice", 18, "Harvard");
student1.sayHello(); // "Hello, I'm a student, my name is Alice"
// 2、使用原型來封裝私有變數
function Counter() {
var count = 0;
this.getCount = function() {
return count;
};
}
Counter.prototype.increment = function() {
var count = this.getCount();
count++;
this.getCount = function() {
return count;
};
};
var counter1 = new Counter();
counter1.increment();
console.log(counter1.getCount()); // 1
var counter2 = new Counter();
counter2.increment();
console.log(counter2.getCount()); // 1,而不是2
以上這兩個例子都非常常見。第一個例子通過Student構造函數來傳遞參數,同時繼承了Person的屬性和方法;第二個例子封裝了私有變數,並通過原型來共享increment方法。
五、總結
原型和原型鏈是JavaScript中比較重要的概念,通過靈活運用原型和構造函數,我們可以很方便地實現繼承、私有變數等功能。同時,需要注意的是,由於原型鏈的存在,有些方法可能並不是對象自身的,而是繼承而來的,因此在編程時需要特別注意。
原創文章,作者:GNIGG,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/360768.html
微信掃一掃
支付寶掃一掃