一、什麼是原型
在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