iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0
Modern Web

前端蛇行撞牆記系列 第 13

Day13 前端蛇行撞牆記 - Contructor function 建構式函式

  • 分享至 

  • xImage
  •  

Constructor function

建構式函式跟一般的function不同,不需要return,一定要搭配 new 運算子才可以建立一個新的物件。

  • 建構式函式長這樣:
function Person(name, age, city) {
    this.name = name;
    this.age = age;
    this.city = city;
}

我們在建構式函式裡面訂定物件要的 property (key, value),
再把 key值 放在參數上,讓要建立物件的引數可以對應key ,就可以把引數 放進key 的 value裡面,產出的物件就會直接都有key, value 了!

而這個新產生的物件是建構式函式的 實例(instance)

那要怎麼建立一個建構式函式呢?

  • 建構式函式的名稱約定俗成都是寫大寫,才能讓人一眼就看出。
  • 裡面用this綁定要建立的property。
  • 參數也要都跟property一樣,才能對到this所綁定的property。
  • 最後新建一個變數的時候一定要 new 這個建構式函式,並且帶入參數。
function Person(name) {
  this.name = name
}

const jade = new Person("Jade");

console.log(jade);
// Person { name: 'Jade' }
console.log(jade.name);
// Jade

每個經由建構式函式建立的實例(instance)的前面都有來自哪個建構式函式的名稱
還可以藉由.constructor來知道這個實例的建構式函式是誰

但是其實new出來的物件其實前面都會有建構式函式的名字

console.log(jade.constructor);
// [Function: Person]

console.log(jade.constructor === Person);
// true

如果一次要建立好幾個同樣property的物件的話,
就可以利用建構式函式去創造好幾個實例了!

  • 例如現在想要一次創造班級的人,把每個人都做成物件,以後才好管理:
function Person(name,age,school) {
  this.name = name,
  this.age = age,
  this.school = school
}

const jade = new Person("Jade",20,"TNUA");
const ann = new Person("Ann",18,"NTUA");
const april = new Person("April",19,"NTU");


console.log(jade);
console.log(ann);
console.log(april);

// Person { name: 'Jade', age: 20, school: 'TNUA' }
// Person { name: 'Ann', age: 18, school: 'NTUA' }
// Person { name: 'April', age: 19, school: 'NTU' }

Prototype

前面講到可以用建構式函式創建好幾個property一樣的實例,
但這些實例其實還可以使用建構式函式的原型喔。

  • 我們在Person的prototype建立一個方法叫做sayHello()
function Person(name,age,school) {
  this.name = name,
  this.age = age,
  this.school = school
}

Person.prototype.sayHello = function(){
    console.log(`Hello, my name is ${this.name}, and I am ${this.age} years old`);
  }

const jade = new Person("Jade",20,"TNUA");
const ann = new Person("Ann",18,"NTUA");
const april = new Person("April",19,"NTU");

console.log(jade);
console.log(ann);
console.log(april);

// Person { name: 'Jade', age: 20, school: 'TNUA' }
// Person { name: 'Ann', age: 18, school: 'NTUA' }
// Person { name: 'April', age: 19, school: 'NTU' }
  • 結果這些實例也同樣可以使用這個方法:
console.log(jade.sayHello());
console.log(ann.sayHello());
console.log(april.sayHello());

// Hello, my name is Jade, and I am 20 years old
// undefined
// Hello, my name is Ann, and I am 18 years old
// undefined
// Hello, my name is April, and I am 19 years old
// undefined

就算sayHello()這些實例沒有,可是創造他們的建構式函式有這個prototype,他們就可以使用!

可以想像這些實例是Person 建構式函式的小孩,只要Person有的,實例就可以有。

只要你爸有錢,你就可以有錢(誤)

New

現在我們知道建構式函式跟prototype之後,來聊聊new運算子,到底為什麼這些實例可以使用建構式的prototype呢?

先來看看MDN上的解釋:

The new operator lets developers create an instance of a user-defined object type or of one of the built-in object types that has a constructor function.

創建的過程簡單來說大概是:

  1. 一旦使用了new這個運算子,就會立即創造一個空物件,這個先叫做新實例(newInstance)。
  2. 綁定新實例的this給建構式函式。
  3. 再來就會把新實例的__proto__指向建構式函式prototype,讓兩個連在一起。
  4. 然後就創建好新的object了。

現在來模擬一下new到底做了什麼事:

把原本的var jade = new Person("jade", 20)模擬成var jade = newPerson("jade", 20)
來把new Person()變成一個function newPerson()來看看。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ", age:" + this.age);
};


// 假裝把new變成一個function表示
// 模擬:var jade = new Person("jade", 20);
var jade = newPerson("jade", 20);

function newPerson(name, age) {
  let obj = {}; // 創造一個空物件
  Person.call(obj, name, age); //綁定新實例的this給建構式函式。
  obj.__proto__ = Person.prototype;// 新實例的__proto__指向建構式函式函式的prototype,讓兩個連在一起。
  return obj;
}

console.log(jade);
// Person { name: 'jade', age: 20 }

2022/10/27 更新

Chirs 提供的寫法,結合Object.create()

function newFn (constructor, name, age) {
  // 創造一個空物件,並把obj.__proto__ = constructor.prototype
  const obj = Object.create(constructor.prototype)

   // 綁定this給obj,帶入name, age
  obj.constructor(name, age);

  return obj
}

const jade = newFn(Person, 'jade', 18);

Speaking Javascript 的寫法:
會再多判斷是否是物件的條件。
p. 268

function newOperator(Constr, args) {
    var thisValue = Object.create(Constr.prototype);
    var result = Constr.apply(thisValue, args)
        if(typeof result === 'object' && result !== null){
        return result 
        }
 return thisValue
}

總結

  • 建構式函式的約定俗成是大寫來命名
  • 要使用 new 建立自己的instance
  • new 會讓instance的__proto__連結建構式函式的prototype

之後會來介紹__proto__,今天就這樣囉!

明天見!


參考資料:【新手大哉問】property、key、value、reference,到底是什麼啦?
[筆記] 談談 JavaScript 中的 function constructor 和關鍵字 new
胡立 JS201


上一篇
Day12 前端蛇行撞牆記 - 屬性描述器的定義方式
下一篇
Day14 前端蛇行撞牆記 - 由__proto__串起的原型鏈
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Chris
iT邦新手 4 級 ‧ 2022-10-15 09:43:40

這段 重新實作 new 的實作

function newPerson(name, age) {
  // 創造一個空物件
  let obj = {}; 
  Person.call(obj, name, age); //綁定新實例的this給建構式函式。
  obj.__proto__ = Person.prototype;// 新實例的__proto__指向建構式函式函式的prototype,讓兩個連在一起。
  return obj;
}

應該可以這樣寫,再將 MDN 的步驟合在一起

function newFn (constructor, name, age) {
  // 立即創造一個空物件,這個先叫做新實例(newInstance)
  const obj = {};
  // 綁定新實例的this給建構式函式。
  obj.constructor = constructor;
  // 執行建構式
  obj.constructor(name, age);
  // 再來就會把新實例的__proto__指向建構式函式prototype,讓兩個連在一起。
  return Object.create(obj.prototype);  // 這一步可以簡化成這樣
  // 也可以寫這樣 
  // obj.__proto__ = Person.prototype;
  // return obj;
}

newFn(Person, 'jade', 18);

以上,是我覺得的理解方式

jadddxx iT邦新手 5 級 ‧ 2022-10-27 11:28:11 檢舉
function newFn (constructor, name, age) {
  // 創造一個空物件,並把obj.__proto__ = constructor.prototype
  const obj = Object.create(constructor.prototype)

   // 綁定this給obj,帶入name, age
  obj.constructor(name, age);

  return obj
}

const jade = newFn(Person, 'jade', 18);

Speaking Javascript 的寫法:
p. 268

function newOperator(Constr, args) {
    var thisValue = Object.create(Constr.prototype);
    var result = Constr.apply(thisValue, args)
    if(typeof result === 'object' && result !== null){
        return result;
    }
 return thisValue;
}

我要留言

立即登入留言