JavaScript入門⑤-欲罷不能的物件原型與繼承-全網一般圖文版

安木夕發表於2022-12-05

image.png

JavaScript入門系列目錄

01、Object原型&繼承

JavaScript 中的所有物件本質上都是透過new ()建立出來的,包括字面量的{obj},也是new Object()的語法糖。每一個例項物件都有自己的原型,基於原型建立這個物件,Function本身也是一個物件。

❓那建立物件的原型到底是什麼呢?

1.1、obj.[[Prototype]]原型

JavaScript 常被描述為一種基於原型的語言 (prototype-based language)——每個物件擁有一個原型物件,物件以其原型為模板,從原型繼承方法和屬性。原型物件也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推,這種關係常被稱為原型鏈 (prototype chain)

? obj.[[Prototype]] 原型:每個物件都有這個隱藏(不可訪問)屬性,他就是指向該物件的原型物件引用,也可以說是該物件的父級。

image.png

  • 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

上面示例程式碼可以看到,所有物件都繼承自ObjectObject又繼承自null

image

❗不要輕易更改原型,影響效能。當使用 Object.setPrototypeOfobj.__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()建構函式了。

image

1.3、object萬物之源

JS中基本所有物件都繼承自Object,準確的說是Object.prototypeObject.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

image

?原型共享:(內建)原型也是可以修改的,也可以借用(複製),屬性方法都儲存在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

image.png

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

上面示例程式碼的原型鏈圖:

image

透過示例得到如下結論:

?只能繼承一個:一個物件只能繼承一個原型物件,可以修改原型,會覆蓋+有效能影響,儘量不這樣做。

❓繼承的財產在什麼地方?

  • 對於示例物件,在obj.__proto__訪問器屬性上,實際是在obj.[[Prototype]]屬性。
  • 對於建構函式、內建的原型物件,財產都存在他們的構造器的prototype上,如F.prototype.prototypeArray.prototype

❓繼承了些什麼東西?——共享屬性(使用共享,修改變私有)

  • 方法都繼承了,技能都是靠血脈傳承的,這個好理解。
  • 繼承了所有屬性-共享,但屬性的繼承有一點特別,繼承是單向的,可以用父類的屬性&值,原型屬性值變更後,所有例項物件的該屬性值都跟著變,他們用的是同一個屬性,屬性是共享的。
  • 修改變私有:這個繼承的屬性只能看,不能模。不能透過例項物件修改(重新賦值)原型上的屬性值,如duck2.name = "duck2",當重新賦值時,會建立一個私有的同名屬性,實際上是將屬性新增到自己身上。
  • 屬性值為引用物件:當修改引用物件內部資料時,並不影響共享,如duck2.foods.push("apple"),屬性的引用地址並沒有變更,大家共享了同一個食品庫。
  • 建構函式不是繼承:執行建構函式時,先建立新物件指向this,然後給this新增屬性,都是是私有的,不是繼承。

⁉️ 關於共享屬性

  • 有時共享是需要的, 如統一型號的玩具,其基本屬性如尺寸、顏色外觀都是統一的,所有商品都共用即可,不用單獨建立屬性。
  • 有時不需要,如每一個使用者都有自己的姓名、積分數量。不需要時怎麼辦呢,請看後文的實現繼承的N中姿勢!

image.png

⁉️ 怎麼判斷是不是親生的?

判斷屬性是自己的,還是繼承的。判斷、獲取自己的屬性方法:

  • 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的原型關係,同構造器函式是一樣的,如下圖。

image

簡寫的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繼承自父類 Birdextends 產生了兩方面的原型繼承,主要是多了類本身(建構函式)的繼承

  • 建構函式繼承(獲得靜態屬性):類Duck 繼承自 類Bird,為建構函式之間繼承,這樣就繼承了父類的靜態屬性、方法。
  • 原型繼承Duck.prototype 繼承自 Bird.prototype,這是物件例項繼承的原型鏈。

image

⚠️箭頭函式沒有自己的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多種實現繼承的姿勢,體驗豐富、欲生欲死、欲罷不能!瞭解前三個就基本可以了。

image


實現方式 優缺點
原型繼承 手動設定原型鏈實現繼承:
? 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

©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀

相關文章