【轉】JS 的 new 到底是乾什麼的?

来源:https://www.cnblogs.com/PeunZhang/archive/2020/02/12/12298391.html
-Advertisement-
Play Games

原文:https://zhuanlan.zhihu.com/p/23987456?refer=study-fe 大部分講 new 的文章會從面向對象的思路講起,但是我始終認為,在解釋一個事物的時候,不應該引入另一個更複雜的事物。 今天我從「省代碼」的角度來講 new。 想象我們在製作一個策略類戰爭游 ...


原文:https://zhuanlan.zhihu.com/p/23987456?refer=study-fe

大部分講 new 的文章會從面向對象的思路講起,但是我始終認為,在解釋一個事物的時候,不應該引入另一個更複雜的事物。

今天我從「省代碼」的角度來講 new。

---------------------------

想象我們在製作一個策略類戰爭游戲,玩家可以操作一堆士兵攻擊敵方。

我們著重來研究一下這個游戲裡面的「製造士兵」環節。

一個士兵的在電腦里就是一堆屬性,如下圖:

 

我們只需要這樣就可以製造一個士兵:

var 士兵 = {
  ID: 1, // 用於區分每個士兵
  兵種:"美國大兵",
  攻擊力:5,
  生命值:42, 
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防禦:function(){ /*護臉*/       }
}

兵營.製造(士兵)

 

製造一百個士兵

如果需要製造 100 個士兵怎麼辦呢?

迴圈 100 次吧:

var 士兵們 = []
var 士兵
for(var i=0; i<100; i++){
  士兵 = {
    ID: i, // ID 不能重覆
    兵種:"美國大兵",
    攻擊力:5,
    生命值:42, 
    行走:function(){ /*走倆步的代碼*/},
    奔跑:function(){ /*狂奔的代碼*/  },
    死亡:function(){ /*Go die*/    },
    攻擊:function(){ /*糊他熊臉*/   },
    防禦:function(){ /*護臉*/       }
  }
  士兵們.push(士兵)
}

兵營.批量製造(士兵們)

 

哎呀好簡單。

質疑

上面的代碼存在一個問題:浪費了很多記憶體。

  1. 行走、奔跑、死亡、攻擊、防禦這五個動作對於每個士兵其實是一樣的,只需要各自引用同一個函數就可以了,沒必要重覆創建 100 個行走、100個奔跑……
  2. 這些士兵的兵種和攻擊力都是一樣的,沒必要創建 100 次。
  3. 只有 ID 和生命值需要創建 100 次,因為每個士兵有自己的 ID 和生命值。

 

改進

看過我們的專欄以前文章(JS 原型鏈)的同學肯定知道,用原型鏈可以解決重覆創建的問題:我們先創建一個「士兵原型」,然後讓「士兵」的 __proto__ 指向「士兵原型」

var 士兵原型 = {
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防禦:function(){ /*護臉*/       }
}
var 士兵們 = []
var 士兵
for(var i=0; i<100; i++){
  士兵 = {
    ID: i, // ID 不能重覆
    生命值:42
  }

  /*實際工作中不要這樣寫,因為 __proto__ 不是標準屬性*/
  士兵.__proto__ = 士兵原型 

  士兵們.push(士兵)
}

兵營.批量製造(士兵們)

 

優雅?

有人指出創建一個士兵的代碼分散在兩個地方很不優雅,於是我們用一個函數把這兩部分聯繫起來:

function 士兵(ID){
  var 臨時對象 = {}

  臨時對象.__proto__ = 士兵.原型

  臨時對象.ID = ID
  臨時對象.生命值 = 42
  
  return 臨時對象
}

士兵.原型 = {
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防禦:function(){ /*護臉*/       }
}

// 保存為文件:士兵.js

 

然後就可以愉快地引用「士兵」來創建士兵了:

var 士兵們 = []
for(var i=0; i<100; i++){
  士兵們.push(士兵(i))
}

兵營.批量製造(士兵們)

JS 之父的關懷

JS 之父創建了 new 關鍵字,可以讓我們少寫幾行代碼:

 

只要你在士兵前面使用 new 關鍵字,那麼可以少做四件事情:

  1. 不用創建臨時對象,因為 new 會幫你做(你使用「this」就可以訪問到臨時對象);
  2. 不用綁定原型,因為 new 會幫你做(new 為了知道原型在哪,所以指定原型的名字為 prototype);
  3. 不用 return 臨時對象,因為 new 會幫你做;
  4. 不要給原型想名字了,因為 new 指定名字為 prototype。

 

這一次我們用 new 來寫

function 士兵(ID){
  this.ID = ID
  this.生命值 = 42
}

士兵.prototype = {
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防禦:function(){ /*護臉*/       }
}

// 保存為文件:士兵.js

 

然後是創建士兵(加了一個 new 關鍵字):

var 士兵們 = []
for(var i=0; i<100; i++){
  士兵們.push(new 士兵(i))
}

兵營.批量製造(士兵們)

new 的作用,就是省那麼幾行代碼。(也就是所謂的語法糖)

註意 constructor 屬性

new 操作為了記錄「臨時對象是由哪個函數創建的」,所以預先給「士兵.prototype」加了一個 constructor 屬性:

士兵.prototype = {
  constructor: 士兵
}

 

如果你重新對「士兵.prototype」賦值,那麼這個 constructor 屬性就沒了,所以你應該這麼寫:

士兵.prototype.兵種 = "美國大兵"
士兵.prototype.攻擊力 = 5
士兵.prototype.行走 = function(){ /*走倆步的代碼*/}
士兵.prototype.奔跑 = function(){ /*狂奔的代碼*/  }
士兵.prototype.死亡 = function(){ /*Go die*/    }
士兵.prototype.攻擊 = function(){ /*糊他熊臉*/   }
士兵.prototype.防禦 = function(){ /*護臉*/       }

 

或者你也可以自己給 constructor 重新賦值:

 

士兵.prototype = {
  constructor: 士兵,
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防禦:function(){ /*護臉*/       }
}

完。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 面向對象: 對代碼的一種抽象,對外統一提供調用介面的編程思想 對象的屬性:事物自身擁有的東西 對象的方法:事物的功能 對象:事物的一個實例 對象的原型:.prototype -> 記憶體地址 -> 存儲了一個對象 function fn(){ return 1; } alert(fn.prototyp ...
  • 用JS實現簡易計算器 首先創建結構和樣式 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1 ...
  • 1. Ajax是什麼? 全稱是 asynchronous javascript and xml,是已有技術的組合,主要用來實現客戶端與伺服器端的非同步通信效果(無需重新載入整個網頁的情況下),實現頁面的局部刷新。 通過在後臺與伺服器進行少量數據交換,AJAX 可以使網頁實現非同步更新,用於創建快速動態網 ...
  • 表單是什麼?表單是前端和伺服器做交互的一種機制,表單收集用戶輸入信息,之後發送或者提交給伺服器。用戶在輸入的信息稱之為內容,內容的文本分為普通和密碼型,用戶通過單選框、覆選框、下拉框(也就是下拉菜單)完成內容信息輸入,最後通過提交按鈕發送給伺服器!這裡要講到瀏覽器怎麼發送給伺服器?涉及到http協議... ...
  • 要用面向對象的方式去編程,而不要用面向過程的方式去編程 對象是各種類型的數據的集合,可以是數字、字元串、數組、函數、對象…… 對象中的內容以鍵值對方式進行存儲 對象要賦值給一個變數 var cat={ "name":"喵1", "age":4, "family":["喵爸","喵媽"], "spea ...
  • SPA單頁面應用容器 開源地址: https://github.com/yuzd/Spa 功能介紹 前端應用開發完後打包後自助上傳部署發佈 配合服務端腳本(javascript)實現服務端業務邏輯編寫渲染SSR功能 可以快速回滾到上一個版本 可以設置環境變數供SSR功能使用 服務端腳本提供執行日誌 ...
  • 最近自學到了JS運動部分,自己整理了一些js模板,望採納。 1.支持鏈式運動的模板: 先解釋一下函數中的幾個參數含義: 1)obj: 要操作的對象 2)target: 屬性要到達的目標值 3)attr: 要操作的屬性值 4)callback: 回調函數 註:調用函數時,回調函數可不寫 2.支持完美運 ...
  • 在vue、react等框架大量應用之前,我們需要使用jQuery或者原生js來操作dom寫代碼,在用原生js進行事件綁定時,我們可以應用DOM2級綁定事件的方法,即:元素.addEventListener(),因為相容性,還有: 元素.attachEvent()。所以我們需要封裝成一個方法: fun ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...