JavaScript入門系列目錄
- JavaScript入門①-基礎知識築基
- JavaScript入門②-函式(1)基礎{淺出}
- JavaScript入門③-函式(2)原理{深入}執行上下文
- JavaScript入門④-萬物皆物件:Object
- JavaScript入門⑤-欲罷不能的物件、原型與繼承
- JavaScript入門⑥-WEB瀏覽器API
- JavaScript入門⑦-DOM操作大全
- JavaScript入門⑧-事件總結大全
- JavaScript入門⑨-非同步程式設計●異世界之旅
- JavaScript入門⑩-ES6歸納總結
01、Object原型&繼承
JavaScript 中的所有物件本質上都是透過new ()
建立出來的,包括字面量的{obj}
,也是new Object()
的語法糖。每一個例項物件都有自己的原型,基於原型建立這個物件,Function
本身也是一個物件。
❓那建立物件的原型到底是什麼呢?
1.1、obj.[[Prototype]]原型
JavaScript 常被描述為一種基於原型的語言 (prototype-based language)——每個物件擁有一個原型物件,物件以其原型為模板,從原型繼承方法和屬性。原型物件也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推,這種關係常被稱為原型鏈 (prototype chain)。
? obj.[[Prototype]] 原型:每個物件都有這個隱藏(不可訪問)屬性,他就是指向該物件的原型物件的引用,也可以說是該物件的父級。
- obj.proto(前後雙下劃線):設定、獲取物件的原型。
__proto__
是[[Prototype]]
的getter/setter
訪問器屬性,是歷史遺留下來的訪問方式,不過還挺好用。
let bird = {
name: "bird",
sayHi() { console.log(this.name + " hi!") },
}
let duck = {
__proto__: bird, //設定原型__proto__
}
duck.__proto__ = bird; //效果同上,設定原型__proto__
console.log(bird.name, duck.name) //bird bird
bird.name='bird2';
console.log(bird.name, duck.name) //bird2 bird2 //共享原型(父)的屬性
//父新增一個方法
bird.fly = function () { console.log(this.name + " fly!") }
duck.name = "duck";
duck.fly(); //duck fly! //新鮮出爐的方法也被繼承了
- Object.getPrototypeOf(obj)、Object.setPrototypeOf(obj,proto),是新加入的替代
__proto__
,用於獲取、設定物件原型的方法。
const arr=[1,2];
const t1=Object.getPrototypeOf(arr); //Array []
const t2=Object.getPrototypeOf(t1); //Object { … }
const t3=Object.getPrototypeOf(t2); //null
console.log(t1,t2,t3); //Array [] Object { … } null
//獲取物件的原型鏈
function getPrototype(obj,arr=[]){
if(obj===null){
return arr;
}
const t=Object.getPrototypeOf(obj);
arr.push(t);
return getPrototype(t,arr);
}
console.log(getPrototype(1)); //Number Object { … } null
console.log(getPrototype(true));//Boolean Object { … } null
console.log(getPrototype("a")); //String Object { … } null
上面示例程式碼可以看到,所有物件都繼承自Object
,Object
又繼承自null
。
❗不要輕易更改原型,影響效能。當使用
Object.setPrototypeOf
或obj.__proto__
“即時”更改原型是一個非常緩慢的操作,因為它破壞了物件屬性訪問操作的內部最佳化。
1.2、F.prototype繼承
F.prototype 指的是建構函式F
的一個名為 "prototype" 的常規屬性,指向一個原型物件——預設只有constructor
(構造器)屬性的物件,構造器constructor
指向函式自身F
。
用建構函式F()
建立新的物件時,① 建構函式里的屬性、方法每次都會重新建立並賦值給this
,② 然後新物件會繼承F.prototype
,獲得他的屬性、方法財產。F.prototype
可以被重寫,可以修改(增、刪除屬性方法)。
?建構函式:就是一個函式,不過是為了建立物件用的。必須是
function
宣告建立的函式:function FuncName(){ }
- 所有屬性、方法都賦值給
this
,沒有return
語句。- 約定大駝峰命名,用來區分普通函式。
- 使用
new F()
來建立物件。這裡new關鍵字的步驟:① 建立一個空物件;② 賦值this
;③ 執行建構函式中的程式碼,給this
新增屬性方法;④ 返回新物件。
function Duck(name) {
this.name = name;
this.cry = function () { console.log(this.name + " cry!") };
}
Duck.prototype.place = 'china'; //原型上新增的屬性會被繼承(共享)
let duck = new Duck("duck"); //1、執行建構函式,初始化this屬性、方法;2、this原型=Duck.prototype,完成繼承儀式
console.log(Duck.prototype.constructor == Duck); //true
console.log(duck.__proto__ == Duck.prototype); //true
console.log(duck.constructor == Duck); //true
console.log(duck.__proto__.constructor == Duck); //true
?obj.constructor:物件構造器,就是建構函式
- 可以用物件的原型構造器
constructor
來建立一個和該物件類似的新物件:new duck.constructor("kfc")
,等效new Duck()
。F.prototype.constructor == F
:函式的prototype
的屬性constructor
等於他自己。
?new F():用建構函式F()
建立物件,分配F.prototype
到新物件的原型[[Prototype]]
F.prototype
只在new F()
建立新物件是使用,設定為新物件的[[Prototype]]
原型。F.prototype
只支援物件、null,其他值會被忽略。- 如果
F.prototype
後面變更了,前後物件不影響,新的繼承新的,舊的物件還是原有的。
function Duck(name) {
this.name = name;
this.cry = function () { console.log(this.name + " cry!") };
}
let duck0 = new Duck('duck');
let cbird = {
place: 'china',
}
let jbird = {
place: 'jepan',
}
//修改建構函式Duck的原型物件為cbird
Duck.prototype = cbird;
let cduck = new Duck('cduck');
//再次修改建構函式Duck的原型物件為jbird
Duck.prototype = jbird;
let jduck = new Duck('jduck');
//兩個物件的原型各不相同,互不影響
console.log(cduck.place); //china
console.log(jduck.place) //jepan
console.log(duck0.__proto__,cduck.__proto__,jduck.__proto__); //{constructor: ƒ} {place: 'china'} {place: 'jepan'}
?再來一個建構函式+原型繼承的示例:
let bird = {
name: "bird",
fly: function () { console.log(this.name + " fly!") },
}
function Duck(name) {
this.name = name;
this.cry = function () { console.log(this.name + " cry!") };
}
Duck.prototype = bird; //原型繼承,讓new Duck()建立的例項物件都繼承自bird,bird作為原型就是共享的
Duck.prototype.type = "bird"; //增加原型屬性,也就是給bird物件新增屬性
let duck = new Duck("duck");
duck.fly(); //duck fly!
duck.cry(); //duck cry!
console.log(Duck.prototype == bird); //true
console.log(duck.__proto__ == bird); //true
console.log(duck.constructor == Duck); //false 因為建構函式的預設原型被更改了,duck就沒有構造器了
該示例的圖形化分析如下圖,bird
實際上是由new Object()建立的,bird
的建構函式就是Object()
建構函式了。
1.3、object萬物之源
JS中基本所有物件都繼承自Object
,準確的說是Object.prototype
,Object.prototype
的原型是null
,算是繼承的盡頭。所謂“道生一,一生二,二生三,三生萬物”。還有很多內建物件Array、Function等,每一個原型物件都內建了很多屬性、方法。當我們建立這些物件時,就繼承了他們的豐富財富。
? 對於基本值型別稍有不同:
- 值包裝器:值型別String、NUmber、Boolean,只有資料值,不是物件,因此本身並沒有什麼屬性、方法。當我們訪問其屬性、方法(如
str.length
)時,會產生一個臨時的物件包裝器物件,這個包裝器物件就是基於其對應的String()
、Number()
、Boolean()
構造器建立的。 - null、undefined 沒有物件包裝器,也就麼有任何屬性、方法。
[1, 2, 3].__proto__ == Array.prototype; //true
(() => { }).__proto__ == Function.prototype; //true
(5).__proto__ == Number.prototype; //true
let bird = { name: "bird" };
let duck = { color: "red" };
duck.__proto__ = bird; //繼承bird
console.log(duck.__proto__ == bird);//true
console.log(duck.__proto__.__proto__ == bird.__proto__);//true
console.log(duck.__proto__.__proto__ == Object.prototype);//true
?原型共享:(內建)原型也是可以修改的,也可以借用(複製),屬性方法都儲存在
prototype
中(Array.prototype、Object.prototype)。原型prototype
是全域性共享的,需要注意!
//給string擴充套件一個全域性方法: 轉換資料為整數
if (!String.prototype.toInt) {
String.prototype.toInt = function (defaultValue = 0) {
const num = parseInt(this);
return num ? num : defaultValue;
}
}
//借給(複製)給其他原型
Number.prototype.toInt = String.prototype.toInt;
"123a".toInt(); //123
123.11.toInt(); //123
//擴充套件一個函式包裝器defer,讓任何函式延遲執行
Function.prototype.defer = function (ms) {
let f = this;
return function (...args) {
setTimeout(() => {
f.apply(this, args);
}, ms);
}
}
//延遲3s執行方法
console.log.defer(3000)("123a");
alert.defer(3000)("Hi!");
1.4、到底繼承了些什麼東西?-原型鏈
繼承是一層一層的,逐級往上,直到Oject
(Object.prototype),形成了一個原型鏈。被繼承的財富就藏在每一層原型上,當訪問屬性、方法時,先在自己內部查詢,自己沒有的屬性/方法,會在原型鏈上向上查詢,直到宇宙盡頭null
,都沒找到就返回undefined
。
function Bird() {
this.name = "bird";
this.foods = []; //注意這個陣列——共享財產
this.eat = function (food) { this.foods.push(food) };
}
function Duck(name) {
this.color = "white";
}
Duck.prototype = new Bird(); //修改原型物件,繼承自Bird例項物件
//修正constructor,不修正也沒啥,就是別人用new duck.constructor("gaga")建立物件時不對
Duck.prototype.contructor = Duck;
Duck.prototype.fly = function () { console.log(this.name + " fly!") }
let duck1 = new Duck();
let duck2 = new Duck();
console.log(duck1.__proto__.__proto__.__proto__ == Object.prototype);//true
duck1.eat("rose");
console.log(duck1.foods, duck2.foods); //['rose'] ['rose'] //共享屬性foods,這不是我們想要的!
Duck.prototype.name = "duck"; //在原型上修改值
duck1.__proto__.name = "duck"; //效果同上
console.log(duck1.name, duck2.name); //duck duck //都會生效,共享屬性name
duck2.name = "duck2"; //重新賦值屬性值,不會影響原型
console.log(duck1.name, duck2.name); //duck duck2 //duck2有自己的屬性name值了
duck2.foods.push("apple");
console.log(duck1.foods, duck2.foods); //['rose', 'apple'] ['rose', 'apple'] //共享屬性foods
duck2.foods = ["私有food"];
console.log(duck1.foods, duck2.foods); //['rose', 'apple'] ['私有food'] //duck2的私有foods
上面示例程式碼的原型鏈圖:
透過示例得到如下結論:
?只能繼承一個:一個物件只能繼承一個原型物件,可以修改原型,會覆蓋+有效能影響,儘量不這樣做。
❓繼承的財產在什麼地方?
- 對於示例物件,在
obj.__proto__
訪問器屬性上,實際是在obj.[[Prototype]]
屬性。 - 對於建構函式、內建的原型物件,財產都存在他們的構造器的
prototype
上,如F.prototype
、.prototype
、Array.prototype
。
❓繼承了些什麼東西?——共享屬性(使用共享,修改變私有)
- 方法都繼承了,技能都是靠血脈傳承的,這個好理解。
- 繼承了所有屬性-共享,但屬性的繼承有一點特別,繼承是單向的,可以用父類的屬性&值,原型屬性值變更後,所有例項物件的該屬性值都跟著變,他們用的是同一個屬性,屬性是共享的。
- 修改變私有:這個繼承的屬性只能看,不能模。不能透過例項物件修改(重新賦值)原型上的屬性值,如
duck2.name = "duck2"
,當重新賦值時,會建立一個私有的同名屬性,實際上是將屬性新增到自己身上。 - 屬性值為引用物件:當修改引用物件內部資料時,並不影響共享,如
duck2.foods.push("apple")
,屬性的引用地址並沒有變更,大家共享了同一個食品庫。 - 建構函式不是繼承:執行建構函式時,先建立新物件指向
this
,然後給this
新增屬性,都是是私有的,不是繼承。
⁉️ 關於共享屬性
- 有時共享是需要的, 如統一型號的玩具,其基本屬性如尺寸、顏色外觀都是統一的,所有商品都共用即可,不用單獨建立屬性。
- 有時不需要,如每一個使用者都有自己的姓名、積分數量。不需要時怎麼辦呢,請看後文的實現繼承的N中姿勢!
⁉️ 怎麼判斷是不是親生的?
判斷屬性是自己的,還是繼承的。判斷、獲取自己的屬性方法:
- obj.hasOwnProperty(propName):判斷是否自己的親生的屬性,返回bool值。
- Object.keys(obj),獲取obj自己的可列舉屬性陣列,不包含原型(父級)的屬性。
for(in)
會迴圈所有的可列舉屬性,包括原型鏈上的。
02、class類
JS終於有點像樣的東西了——類Class。看完後之後:也就那樣,坑也不少啊。
class 定義一個類,可以更好的物件導向程式設計。class
的本質上是函式,像建構函式的“語法糖”,構造器、原型繼承基本都一樣。不過他不是一般的語法糖,是JS內建的、有特殊標誌的建構函式。
function 建構函式 | class 類 | |
---|---|---|
列舉屬性 | 屬性方法都可列舉 | 類方法不可列舉,預設enumerable = false,屬性可以 |
嚴格模式 | 預設模式 | 自動嚴格模式,use strict |
提升 | 有提升效果,可先使用、後定義 | 不會提升 |
使用方式 | 可以當普通函式使用 | 不能直接呼叫,只能new 建立物件 |
建構函式 | 就是函式本身 | 類中的constructor() 函式,沒有也會自動生成一個 |
命名方式 | 推薦大駝峰 | 大駝峰 |
語法 | - | 方法申明不需要function 關鍵字:method(){} |
繼承方式 | 設定__proto__ |
extends |
原型鏈 | F.prototype |
和function 相同,多了類本身之間的繼承(實現靜態繼承) |
2.1、class基本語法
class ParentClass { }
class MyClass extends ParentClass {
#name; //私有屬性,#開頭
size = 100; //正常屬性
// 構造器
constructor(type) {
super();
this.type = type; //傳統欄位申明
}
// 方法
method1() { }
#method2() { } //私有方法
//...
//getter/setter
get name() { }
set name(value) { }
}
//使用new建立例項物件
let obj = new MyClass(); //自動呼叫構造器方法建立物件
class
申明一個類,類名 建議大駝峰命名,首字母大寫。constructor
定義構造器函式,建立物件時預設呼叫constructor
,可以沒有(會自動建立)- 方法申明,
method(para){}
,和函式申明略有不同。 - 訪問器屬性
getters/setters
,同物件中的申明方式。 extends
繼承另一個類,可以繼承自定義的類,也可以繼承JS的原生類,如Array、Map,然後實現更多擴充套件。(不過原生類的靜態屬性方法不會被繼承)supper
呼叫父級,在繼承的內部可以透過supper
呼叫父類的建構函式、屬性方法。static
申明靜態的屬性、方法。#
私有屬性、方法,#
開頭命名的欄位、方法為私有的,不可外部訪問,不可繼承,肥水不流外人田。在這之前,大家都是約定下劃線_開頭命名,表示私有,哎,可憐的程式設計師!
? 注意:方法間沒有逗號
,
、冒號;
,其他語句同函式。方法預設是不可列舉的,預設enumerable = false
,屬性可以。
?一個簡單的示例:
class User {
constructor(name) { this.name = name; }
sayHi() { console.log(this.name); }
}
let user = new User("sam");
console.log(typeof User); // function
console.log(user.__proto__ === User.prototype); // true
console.log(user.constructor === User.prototype.constructor); // true
console.log(user.constructor === User); // true
console.log(User === User.prototype.constructor); // true
上面是一個非常簡單的類,例項物件user
和類User
的原型關係,同構造器函式是一樣的,如下圖。
簡寫的class類表示式:同函式表示式寫法。
let Bird = class { name = "sam" };
let Duck = class MyDuck { name = "sam" }
2.2、extends繼承
類的繼承同樣遵從原型鏈的規則,都繼承了Object原型,繼承的子類可以重寫父類的屬性方法。
- ?
constructor
重寫,必須先呼叫父類的建構函式supper()
。- 如不重寫建構函式,會自動生成並呼叫
supper()
。 - 為什麼必須呼叫父類建構函式?子類是基於父類建立的,必須先構造父類,獲得
this
物件,完成繼承,再執行子類的建構函式,最終完成this
的建立。
- 如不重寫建構函式,會自動生成並呼叫
- 方法重寫,同名的方法會覆蓋父類的方法,可以透過
super.method()
來呼叫 父類的方法。 - 欄位重寫,同方法,不過欄位(屬性)的重寫很怪異的一點,?,父類的建構函式總數呼叫自己的欄位,而不是被重寫的。
- ❓為什麼會這樣?是由於奇特的執行順序:先初始化父類欄位 >>
supper()
執行父類建構函式 >> 初始化自己的欄位 >> 執行自己的建構函式。 - so,執行父類建構函式時,他還不知道自己的欄位被綠了。解決方式就是在子類構造函重新賦值。
- ❓為什麼會這樣?是由於奇特的執行順序:先初始化父類欄位 >>
- 單一繼承/Mixin 模式:extends只能繼承一個類。如果希望獲得多個類的屬性、方法,需要配合其他方式,如複製
Object.assign()
。
?一個繼承的示例:
class Bird {
#name;
colors = ["red"];
static type = 'bird'; //靜態屬性,透過Bird.type訪問
constructor(name) {
this.name = name;
}
cry() { console.log(this.name + " cry!") }
get name() {
return this.#name;
}
set name(value) { this.#name = value }
}
class Duck extends Bird {
weight;
constructor(name, weight) {
super(name);
this.weight = weight;
}
}
let duck = new Duck("gaga", 10);
console.log(Duck.__proto__ == Bird); //true 類本身的繼承
console.log(duck.__proto__ == Duck.prototype); //true
console.log(duck.__proto__.__proto__ == Bird.prototype); //true
console.log(duck.constructor == Duck); //true
console.log(duck.__proto__.__proto__.constructor == Bird); //true
duck.colors.push("yellow");
console.log(duck.colors, new Duck().colors); //['red', 'yellow'] ['red'] colors屬性是私有的
上面的程式碼中,類Duck
繼承自父類 Bird
,extends
產生了兩方面的原型繼承,主要是多了類本身(建構函式)的繼承。
- 建構函式繼承(獲得靜態屬性):類
Duck
繼承自 類Bird
,為建構函式之間繼承,這樣就繼承了父類的靜態屬性、方法。 - 原型繼承:
Duck.prototype
繼承自Bird.prototype
,這是物件例項繼承的原型鏈。
⚠️箭頭函式沒有自己的this、supper:注意this、supper的丟失,例如透過
setTimeout
在另一個上下文環境中執行,可用箭頭函式;或複製有supper
程式碼的方法。
2.3、static靜態屬性方法
static 靜態定義的屬性、方法屬於這個類本身,不屬於其任何例項,靜態方法中的this
也是指向的是類本身。透過類名進行呼叫,可以被類繼承,就像我們常用的Object.keys(obj)
。
- 內部定義,static申明。
- 外部賦值,透過類申明,與物件原型類似。
class User {
static Type = "VIP"; //內部用static申明定義靜態屬性、方法
static showType() { console.log(this.Type); }
}
//外部定義靜態屬性、函式
User.Level = 99;
//繼承
class SupperUser extends User {
static showType = function () { console.log(this.Type + 2); }
}
User.showType(); //VIP
SupperUser.showType(); //VIP2
console.log(SupperUser.Level, SupperUser.Level); //99 99
03、實現繼承的幾種姿勢
貼心的JavaScript為我們準備了N多種實現繼承的姿勢,體驗豐富、欲生欲死、欲罷不能!瞭解前三個就基本可以了。
實現方式 | 優缺點 | |
---|---|---|
原型繼承 | 手動設定原型鏈實現繼承: ? subObj.__proto__ == parentObj ? Object.setPrototypeOf(obj , parentObj) ? SubFunc.prototype = parentObj ? Object.create(proto, propertiesObject) |
?原型__proto__ 物件是共享的,大家共享原型上的屬性值(特別是值為引用)?無法向父類傳遞引數 |
借用建構函式 | 呼叫建構函式,借用其this 的屬性、方法,本質是複製,沒有“繼承”關係。parentFunc.call(this) |
?避免了屬性共享,可以傳遞引數 ?方法無法複用,每個例項物件都重新建立方法 |
組合繼承 | 上面兩種的組合: ? 借用建構函式:實現屬性”繼承“ ? 原型繼承:實現方法繼承、複用 |
?實現了方法的重用,解決了屬性共享 ?至少呼叫兩次父級建構函式?好像也不是什麼大事 |
寄生組合 | 組合繼承的改進版,新增了用一個空建構函式包裝父級原型 | ?在組合繼承基礎上,減少了一次父類建構函式的呼叫。 ?子級的原型 prototype 被覆蓋了 |
增強寄生組合 | 寄生組合的改進版,把子類原型中的屬性手動加回來 | ?解決了上面的問題 |
class類繼承 | extends,屬性並沒有在class.prototype 裡 |
?屬性是私有的,方法是共享的,支援傳參 |
- 借用建構函式:
parentFunc.call(this)
,借雞生蛋!
function Bird() {
this.type = "sam";
this.hi = function () { console.log("hi") };
}
function Duck() {
Bird.call(this); //強制修改Bird()的this,借雞下蛋
}
let duck = new Duck();
console.log(duck instanceof Bird); //false 和Bird沒繼承關係
console.log(duck instanceof Duck); //true
- 組合繼承:借用建構函式 + 原型繼承
function Bird(name) {
this.name = name;
this.colors = ["red"];
}
Bird.prototype.fly = function () { console.log(this.name + " fly!") };
Bird.prototype.type = "鳥類"; //需要共享的屬性
function Duck(name) {
Bird.call(this, name); //借用建構函式:實現屬性”繼承“。呼叫一次Bird建構函式
this.price = 100;
}
Duck.prototype = new Bird(); //原型繼承:實現方法繼承、複用。呼叫一次Bird建構函式
//修正constructor,不修正也沒啥,就是別人用new duck.constructor("gaga")建立物件時不對
Duck.prototype.constructor = Duck;
let duck = new Duck("sam");
console.log(duck instanceof Bird); //true
console.log(duck instanceof Duck); //true
console.log(duck.fly == (new Duck()).fly); //true
duck.colors.push("green");
console.log(duck.colors, new Duck("ww").colors); // ['red', 'green'] ['red'] //沒有共享
- 寄生組合式繼承:基本思路同組合繼承,算是組合繼承的改進版,直接設定子級的原型
F.prototype
,減少一次父級建構函式的呼叫。
function inherit(parentFunc, childFunc) {
let SuperF = function () { }; //用一個空建構函式封裝父級
SuperF.prototype = parentFunc.prototype;
childFunc.prototype = new SuperF(); //new 這個空建構函式,不用呼叫父級建構函式了。
childFunc.constructor = childFunc;
}
//更粗暴的做法
function inherit2(parentFunc, childFunc) {
childFunc.prototype = parentFunc.prototype; //修改prototype
childFunc.constructor = childFunc;
}
//父級
function Bird(name) {
this.name = name;
this.colors = ["red"];
}
Bird.prototype.fly = function () { console.log(this.name + " fly!") };
//子類
function Duck(name) {
Bird.call(this, name);
this.price = 100;
}
Duck.prototype.cry = function () { console.log(this.name + " cry!") }; //Duck.prototype原有的屬性被後面覆蓋了
Duck.prototype = Bird.prototype; //修改prototype
Duck.constructor = Duck;
// inherit2(Bird, Duck); //同上
let duck = new Duck("sam");
console.log(duck instanceof Bird); //true
console.log(duck instanceof Duck); //true
console.log(duck.fly == (new Duck()).fly); //true
duck.colors.push("green");
console.log(duck.colors, new Duck("ww").colors); // ['red', 'green'] ['red'] //沒有共享
- 增強寄生組合:寄生組合的繼續改進版,把子類原型中的屬性手動加回來
function inherit(parentFunc, childFunc) {
let proto = parentFunc.prototype;
//把子類原型的所有屬性複製到一起
Object.keys(childFunc.prototype).forEach(key =>
Object.defineProperty(proto, key, { value: childFunc.prototype[key] }))
childFunc.prototype = proto;
childFunc.constructor = childFunc;
}
//父級
function Bird(name) {
this.name = name;
this.colors = ["red"];
}
Bird.prototype.fly = function () { console.log(this.name + " fly!") };
//子類
function Duck(name) {
Bird.call(this, name);
this.price = 100;
}
Duck.prototype.cry = function () { console.log(this.name + " cry!") };
inherit(Bird, Duck);
let duck = new Duck("sam");
duck.cry(); //sam cry! //沒有丟失
console.log(duck instanceof Bird); //true
console.log(duck instanceof Duck); //true
console.log(duck.fly == (new Duck()).fly); //true
duck.colors.push("green");
console.log(duck.colors, new Duck("ww").colors); // ['red', 'green'] ['red'] //沒有共享
04、補充
判斷資料型別方法
描述 | 返回值 | |
---|---|---|
typeof | 原始資料型別 | string |
{}.toString | 原始資料型別、內建物件,包含 Symbol.toStringTag 屬性的物件 |
string |
instanceof | 物件,會檢測其原型鏈,只要在原型鏈上都返回true |
true/false |
class Bird {
[Symbol.toStringTag] = "Bird"; //內建特殊屬性[Symbol.toStringTag],自定義toString方法的值。
}
let bird = new Bird();
console.log(bird.toString()); //[object Bird]
console.log(typeof 1); //number
console.log(typeof "1"); //string
let ftype = Object.prototype.toString; //用最原始的toString方法
ftype = {}.toString; //或者這樣
console.log(ftype.call(1)); //[object Number]
console.log(ftype.call("1")); //[object String]
console.log(ftype.call(ftype)); //[object Function]
console.log(ftype.call({})); //[object Object]
console.log(ftype.call([1, 2])); //[object Array]
console.log(ftype.call(bird)); //[object Bird]
console.log(bird instanceof Bird); //true
console.log(bird instanceof Object); //true
©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀