iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
自我挑戰組

Be friend with JavaScript系列 第 20

Day 20 - Constructor Function & Prototype

Constructor Function

  • 用來製作大量相似的 object,通常用大寫字母做開頭
  • 搭配 new 來使用(功能為創造一個新的空的 object)
function Person(name,sex,age){
  console.log(this); // {}
  this.name = name;
  this.sex = sex;
  this.age = age;
  this.sayHi= function(){
    console.log(this.name+" says Hi");
  }
}
let Helen = new Person("Helen","Female",24);
console.log(Helen); // {name:"Helen",sex:"Female",age:24}

let Daniel = new Person("Daniel","Male",23);
console.log(Daniel); // {name:"Daniel",sex:"Male",age:23}

Helen.sayHi(); // Helen says Hi
Daniel.sayHi(); // Daniel says Hi

console.log(Helen.sayHi == Daniel.sayHi); // false

Prototype

假如今天有 100 個物件,使用前面介紹的 Constructor Function,就會產生 100 個 sayHi function,造成記憶體的負擔,更好的做法是讓這 100 個物件只執行一個 sayHi function,讓這 100 個 object 共同使用,這個概念就是 Prototype。

function Person(name,sex,age){
  console.log(this); // {}
  this.name = name;
  this.sex = sex;
  this.age = age;
}

Person.prototype.sayHi = function(){
    console.log(this.name+" says Hi");
}

let Helen = new Person("Helen","Female",24);
console.log(Helen); // {name:"Helen",sex:"Female",age:24}

let Daniel = new Person("Daniel","Male",23);
console.log(Daniel); // {name:"Daniel",sex:"Male",age:23}

Helen.sayHi(); // Helen says Hi
Daniel.sayHi(); // Daniel says Hi

console.log(Helen.sayHi == Daniel.sayHi) // true

使用 prototype 後,可以看到不管是誰執行 sayHi 這個 function,都是指向同一個,所以 console.log(Helen.sayHi == Daniel.sayHi) 會是 true

當我們 console.log Daniel 或是 Helen 時都只能看到姓名性別和年齡,那麼為什麼 Helen.sayHi() 可以看到 Helen says Hi 呢?

事實上就是從 prototye 去繼承而來的,在 Helen 這個 object 裡面會有 prototype,而裡面有 constructor 和 sayHi 這個 function

Prototype Inheritance

下面這個例子因為 name,sex,age 都是一樣的參數,所以在 Student() 裡面 call 了 Person 的 property 來用,但要特別注意的是,這樣的作法,使 Student() 從頭到尾都和 Person.prototype 裡面的東西毫無關聯,所以當我們執行 Daniel.sayHi() 的時候,會出現 Uncaught TypeError: Daniel.sayHi is not a function,是因為 Daniel 根本沒有繼承 Person.prototype 裡面的 function

function Person(name,sex,age){
    console.log(this); // {}
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  
  Person.prototype.sayHi= function(){
      console.log(this.name+" says Hi");
  }
  function Student(name,sex,age,height,weight){
    Person.call(this,name,sex,age);
    this.height;
    this.weight;
  }
  let Daniel = new Student("Daniel","Male",23,172,56);
  console.log(Daniel);
  Daniel.sayHi(); // Uncaught TypeError: Daniel.sayHi is not a function

如下圖可以看到 prototype 裡根本沒有 sayHi() 這個 function

要解決上述的問題,讓 Daniel 繼承 prototype 裡面的 function 有兩種做法。

  1. 直接複製 prototype.function
Student.prototype.sayHi= function(){
    console.log(this.name+" says Hi");
}


此時可以看見 prototype 裡有 sayHi() 這個 function,但是這做法並不妥,如果程式碼很多的時候要一個一個改,不易維護。
2. Object.create()
使用此方式是較正確的做法
創造一個新的物件來當作 Student 的 prototype,而我們要創造的物件就是 Person.prototype
Student.prototype = Object.create(Person.prototype)

function Person(name,sex,age){
    console.log(this); // {}
    this.name = name;
    this.sex = sex;
    this.age = age;
}

Person.prototype.sayHi= function(){
    console.log(this.name+" says Hi");
}

function Student(name,sex,age,height,weight){
    Person.call(this,name,sex,age);
    this.height;
    this.weight;
}
Student.prototype = Object.create(Person.prototype);

let Daniel = new Student("Daniel","Male",23,172,56);
console.log(Daniel);
Daniel.sayHi(); // Daniel says Hi

此時 Daniel 就已經繼承了 Person.prototype 來當作自己的prototype,因此執行 Daniel.sayHi() 可以看到 Daniel says Hi
要注意的是,Daniel 這個物件本身是不含有 sayHi 這個 function 的,在執行 Daniel.sayHi() 的時候因為找不到,就會去 prototype 裡面找,如果找到了這個 function 就去執行,如果沒有找到,又繼續往下面的 prototype 裡面找

當然也可以替 Student 做自己的 prototype 和 method

Student.prototype = Object.create(Person.prototype);
Student.prototype.study = function(){
    console.log("I am studying")
}
let Daniel = new Student("Daniel","Male",23,172,56);
console.log(Daniel);
Daniel.sayHi(); // Daniel says Hi
Daniel.study(); // I am studying

let Helen = new Person("Helen","Female",24,160,56);
Helen.sayHi(); // Helen says Hi
Helen.study(); // Uncaught TypeError: Helen.study is not a function
console.log(Helen);

因為 Person 的 proptotype function 只有 sayHi(),study() 是 Student 的 prototype 裡面的,所以執行 Helen.study(),會得到 Uncaught TypeError

let Helen = new Student("Helen","Female",24,160,56);
Helen.sayHi(); // Helen says Hi
Helen.study(); // I am studying
console.log(Helen);

如果是 new Student,Helen 這個 object 裡面 prototype 才會有 study(),並且也有 sayHi()

補充:

let array = [1,2,3,4];
array.push(5);
console.log(array)

array 可以用 push() 這個 function 是因為我們在創造 array 的時候,他也從 prototype 裡面去繼承了很多 function,例如 : forEach(),indexOf(),map(),reduce(),slice(),some()....等

而前面我們也有提過 coercion 的概念

let myName = "Helen";
console.log(myName.toUpperCase());

可以直接用 string.toUpperCase() 是因為其實程式碼在執行時,默默的執行了 myName = new String("Helen") 從 prototype 繼承了 method 來用,而在執行完後又馬上恢復原狀,讓 myName 變回 string


上一篇
Day 19 - Execution Context
下一篇
Day 21 - Class
系列文
Be friend with JavaScript39

尚未有邦友留言

立即登入留言