面向?qū)ο笾R點(diǎn)總結(jié)

構(gòu)造函數(shù)和原型

  • 能夠使用構(gòu)造函數(shù)創(chuàng)建對象
  • 能夠說出原型的作用
  • 能夠說出訪問對象成員的規(guī)則
  • 能夠使用es5新增的一些方法

構(gòu)造函數(shù)(模擬類的實(shí)現(xiàn))

  • 在典型的oop語言中(如java)刑峡,都存在類的概念,類就是對象的模板羽利,對象就是類的實(shí)例这弧,但是在ES6之前Js中并沒有引入類的概念
  • 在ES6之前匾浪,對象不是基于類創(chuàng)建的蛋辈,而是用一種稱為構(gòu)造函數(shù)的特殊函數(shù)來定義對象和他們的特征
  • 創(chuàng)建對象的三種方式
    1.對象字面量
    2.new Object()
    3.構(gòu)造函數(shù)
    // 1. 利用new Object() 創(chuàng)建對象
    var obj1 = new Object();
    // 2. 字面量創(chuàng)建對象
    var obj2 = {};
    // 3. 構(gòu)造函數(shù)創(chuàng)建對象
    function Star(uname, age) {
      this.uname = uname;
      this.age = age;
      this.sing = function() {
        console.log('111');
      }
    }
    var ldh = new Star('趙四', 18);
// 構(gòu)造函數(shù)要和new一起使用才會有意義
// 1. 實(shí)例成員
// 實(shí)例成員就是構(gòu)造函數(shù)內(nèi)部通過this添加的成員 uname age sing 就是實(shí)例成員
// 實(shí)例成員只能通過實(shí)例化的對象來訪問
// console.log(Star.uname);不可以通過這種構(gòu)造函數(shù)的方式來訪問實(shí)例成員

// 2. 靜態(tài)成員: 在構(gòu)造函數(shù)本身上添加的成員
Star.sex = '男'; // sex就是靜態(tài)成員
// 靜態(tài)成員只能通過構(gòu)造函數(shù)來訪問,不能通過對象來訪問逞频。

靜態(tài)方法和實(shí)例方法最主要的區(qū)別就是實(shí)例方法可以訪問到實(shí)例虏劲,可以對實(shí)例進(jìn)行操作柒巫,而靜態(tài)方法一般用于跟實(shí)例無關(guān)的操作堡掏。這兩種方法在 jQuery 中有大量應(yīng)用泉唁,在 jQuery 中$(selector)其實(shí)拿到的就是實(shí)例對象,通過$(selector)進(jìn)行操作的方法就是實(shí)例方法拴鸵。比如$(selector).append()劲藐,這會往這個(gè)實(shí)例DOM添加新元素聘芜,他需要這個(gè) DOM 實(shí)例才知道怎么操作汰现,將append作為一個(gè)實(shí)例方法服鹅,他里面的this就會指向這個(gè)實(shí)例庐扫,就可以通過this操作 DOM 實(shí)例形庭。

那什么方法適合作為靜態(tài)方法呢萨醒?比如$.ajax富纸,這里的ajax跟 DOM 實(shí)例沒關(guān)系晓褪,不需要這個(gè)this涣仿,可以直接掛載在$上作為靜態(tài)方法好港。

  • new 在執(zhí)行時(shí)會做四件事:
    (1)在內(nèi)存中創(chuàng)建一個(gè)空對象钧汹;
    (2)讓this指向這個(gè)空對象
    (3)執(zhí)行構(gòu)造函數(shù)里面的代碼类嗤,給這個(gè)新對象添加屬性和方法
    (4)返回這個(gè)新對象(所以構(gòu)造函數(shù)里面不需要return)
  • 構(gòu)造函數(shù)很好用,但是存在浪費(fèi)內(nèi)存的問題
  • 我們希望所有的對象都是用同一個(gè)函數(shù)嗤形,這時(shí)候我們就用到了原型對象的概念

構(gòu)造函數(shù)原型prototype

  • Js規(guī)定赋兵,每一個(gè)構(gòu)造函數(shù)里面都有prototype屬性,它指向一個(gè)對象历造,那么這個(gè)prototype就是一個(gè)對象吭产,我們稱之為原型對象臣淤,這個(gè)對象的所有屬性和方法就會被構(gòu)造函數(shù)所擁有邑蒋。
  • 我們可以把一些不變的方法,直接定義在prototype對象上遮咖,這樣所有對象的實(shí)例就可以共享這些方法御吞。
    function Star(uname, age) {
      this.uname = uname;
      this.age = age;
    }
    var ldh = new Star('趙四', 18);

    Star.prototype.sing = function() {
      console.log('111');
    }
    console.log(ldh.sing());
    // 一般情況下陶珠,我們的公共屬性定義到構(gòu)造函數(shù)里面诀蓉,公共的方法我們放到原型對象身上
  • 原型的作用就是共享方法
  • 但是這個(gè)時(shí)候大家可能有個(gè)疑惑渠啤,在ldh對象身上并沒有sing這個(gè)方法呀沥曹,是定義到了Star原型對象身上了,為什么ldh還能使用sing這個(gè)方法壶栋?
  • 這是因?yàn)楣笫裕瑢ο蠖紩幸粋€(gè)屬性__proto__指向構(gòu)造函數(shù)的prototype原型對象锡移,之所以我們對象可以使用構(gòu)造函數(shù)prototype原型對象的屬性和方法,就是因?yàn)閷ο笥?code>__proto__原型的存在
//  驗(yàn)證一下
console.log(ldh.__proto__ === Star.prototype); // true

constructor 構(gòu)造函數(shù)

  • 對象原型(__proto__)和構(gòu)造函數(shù)(prototype)原型對象里面都有一個(gè)屬性constructor屬性施符,constructor我們稱之為構(gòu)造函數(shù)戳吝,因?yàn)樗富貥?gòu)造函數(shù)本身。
// 我們打印一下可以驗(yàn)證
console.log(Star.prototype);
console.log(ldh.__proto__);
  • 很多情況下我們需要手動的利用constructor這個(gè)屬性指回原來的構(gòu)造函數(shù)陆盘,例如:
// 我們把Star中的方法全部拿出來寫成統(tǒng)一的對象的方式
//Star.prototype.sing = function(){
//  console.log('我會唱歌');
//}
//Star.prototype.movie= function(){
//  console.log('我會演電影')酸员;
//}
// 如果利用下面這種寫法幔嗦,看上去也沒啥問題邀泉,只不過把方法以對象的方式寫入原型對象
// 但是這時(shí)候我們會發(fā)現(xiàn):console.log(Star.prototype.constructor);   console.log(sing.__proto__.constructor);
// 他們打印的結(jié)果不再是原型對象本身,因?yàn)樾露x的下面的對象把Star本身帶的constructor對象覆蓋掉了屁置。
// 這時(shí)候我們就需要在下面的寫法中重新手動的利用constructor這個(gè)屬性指回原來的構(gòu)造函數(shù)
Star.prototype = {
  // 這是上面這一大堆話的關(guān)鍵
  constructor: Star, 
  sing: () => {
    console.log('我會唱歌');
  },
  movie: () => {
    console.log('我會演電影');
  }
}
// 此時(shí)再打印
console.log(Star.prototype.constructor === ldh.__proto__.constructor); // true
// 如果沒有指回原來的構(gòu)造函數(shù),那么上面這個(gè)等式就不成立
  • 對上面這個(gè)例子進(jìn)行總結(jié):如果我們修改了原來的原型對象使鹅,給原型對象賦值的是一個(gè)對象患朱,則必須手動的利用constructor指回原來的構(gòu)造函數(shù)炊苫。

構(gòu)造函數(shù)裁厅,原型,實(shí)例的關(guān)系

  • 原型鏈:首先我們有一個(gè)構(gòu)造函數(shù)侨艾,每個(gè)構(gòu)造函數(shù)中都有一個(gè)prototype屬性(這個(gè)屬性是一個(gè)指針执虹,指向一個(gè)對象,這個(gè)對象包含了通過調(diào)用該構(gòu)造函數(shù)所創(chuàng)建的對象共享的屬性和方法唠梨。其實(shí)我們平常的叫法就是指:prototype就是通過該構(gòu)造函數(shù)創(chuàng)建的某個(gè)實(shí)例的原型對象),prototype又指向這個(gè)原型對象当叭。同樣的原型對象里面有個(gè)屬性叫constructor茬故,它又指回了構(gòu)造函數(shù)。而我們又知道蚁鳖,我們可以通過構(gòu)造函數(shù)來new一個(gè)實(shí)例對象均牢,在這個(gè)實(shí)例對象中也有一個(gè)原型叫__proto__,這個(gè)__proto__是指向prototype這個(gè)原型對象的才睹。如圖:
    構(gòu)造函數(shù)徘跪、實(shí)例對象甘邀、原型對象三者關(guān)系

原型鏈

  • 只要是對象都會有一個(gè)__proto__原型
  • 在prototype原型對象中也有一個(gè)__proto__原型,它指向的是Object.prototype
  • Object.prototype這個(gè)原型對象上面也有__proto__這個(gè)原型垮庐,它最終指向的是null空松邪。
    原型鏈?zhǔn)疽鈭D

原型對象this指向

    function Star(uname, age) {
      this.uname = uname;
      this.age = age;
    }
    var ldh = new Star('趙四', 18);
    // 在構(gòu)造函數(shù)中,里面的this指向的是實(shí)例對象哨查,這里就是ldh逗抑;
    var that = this;
    Star.prototype.sing = function() {
      console.log('111');
      that = this;
    }
    
    ldh.sing();
    console.log(ldh === that);// true
    //  上面的例子說明,原型對象函數(shù)里面的this寒亥,指向的也是對象實(shí)例ldh邮府;
    

擴(kuò)展內(nèi)置對象

  • 可以通過原型對象,對原來的內(nèi)置對象進(jìn)行擴(kuò)展自定義方法溉奕,比如給數(shù)組增加自定義求偶數(shù)和的功能褂傀。
// 我們先來打印一下數(shù)組的原型對象,發(fā)現(xiàn)原型對象上面沒有偶數(shù)和這個(gè)方法
    console.log(Array.prototype);
    Array.prototype.sum = function() {
      for (let i = 0; i < this.length; i++) {
        sum += this[i];
      }
      return sum;
    }

    var arr = [1, 2, 3];
    console.log(arr.sum());

擴(kuò)展call方法

call()加勤,兩個(gè)作用:

  • 1.調(diào)用這個(gè)函數(shù)
  • 2.修改函數(shù)運(yùn)行時(shí)的this指向

fun.call(thisArg, arg1, arg2, ...)

thisArg: 當(dāng)前調(diào)用函數(shù)this的指向?qū)ο?br> arg1仙辟,arg2: 傳遞的其他參數(shù)

    function fn(x, y) {
      console.log('111');
      console.log(this); // 正常來說這里打印的this的指向應(yīng)該是window
      console.log(x + y); // 3
    }
    var obj = {
      name: 'li',
    }
    // 1. 調(diào)用函數(shù)
    fn.call();
    // 2. 改變函數(shù)內(nèi)部this的指向
    fn.call(obj, 1, 2); // 通過call方法,我們可以實(shí)現(xiàn)把fn函數(shù)內(nèi)部的this指向obj這個(gè)對象鳄梅,這就是call的強(qiáng)大之處

繼承

  • Es6之前是沒有類這個(gè)概念的叠国,也就沒有extends給我們提供繼承。我們可以通過構(gòu)造函數(shù)+原型對象模擬實(shí)現(xiàn)繼承戴尸,被稱為組合繼承粟焊。
  • 核心原理:通過call()把父類型的this指向子類型的this,這樣就可以實(shí)現(xiàn)子類型繼承父類型的屬性孙蒙。
1. 借用父構(gòu)造函數(shù)繼承屬性
    // 1. 父構(gòu)造函數(shù)
    function Father(name, age) {
      // this指向父構(gòu)造函數(shù)的對象實(shí)例
      this.name = name;
      this.age = age;
    }
    // 1. 子構(gòu)造函數(shù)
    function Son(name, age) {
      // this指向子構(gòu)造函數(shù)的對象實(shí)例
      Father.call(this, name, age)
      // 這里調(diào)用父構(gòu)造函數(shù)吆玖,利用call方法,可以把子構(gòu)造函數(shù)的this作為第一個(gè)參數(shù)马篮。
      // 這樣就把父構(gòu)造函數(shù)的this指向了子構(gòu)造函數(shù)沾乘。
    }

    var son = new Son('劉德華', 18);
    // 這時(shí)候在子構(gòu)造函數(shù)中就能夠使用父構(gòu)造函數(shù)中的屬性
2. 借用原型對象繼承方法
// 1. 父構(gòu)造函數(shù)
    function Father(name, age) {
      // this指向父構(gòu)造函數(shù)的對象實(shí)例
      this.name = name;
      this.age = age;
    }
    Father.prototype.money = function() {
      console.log('我賺了這么多錢');
    }
    // 1. 子構(gòu)造函數(shù)
    function Son(name, age) {
      // this指向子構(gòu)造函數(shù)的對象實(shí)例
      Father.call(this, name, age);
    }

    Son.prototype = new Father();
    // 將Father的實(shí)例對象,賦值給Son構(gòu)造函數(shù)浑测,根據(jù)原型鏈的原理翅阵,F(xiàn)ather實(shí)例對象就可以獲取到money方法
    // 那同樣的Son構(gòu)造函數(shù)也就可以使用money這個(gè)方法了
    // 但是這里,我們將Son的構(gòu)造函數(shù)指向了Father的實(shí)例對象迁央,那么Son原來的constructor指向就有問題了掷匠,指向了Father。
    console.log(Son.prototype.constructor);// --> Father
    // 如果利用了對象的形式修改了原型對象岖圈,別忘了利用constructor指回原來的構(gòu)造函數(shù)
    Son.prototype.constructor = Son;
    

    var son = new Son('劉德華', 18);

    console.log(son.money());

Es5新增方法

Es5給我們新增了一些方法讹语,可以很方便的操作數(shù)組或者字符串

1. 數(shù)組方法
  • 迭代(遍歷)方法:forEach()、map()蜂科、filter()顽决、some()短条、every()
forEach()
    // forEach方法
    var Arr = [1, 2, 3];
    Arr.forEach(function(value, index, array)) {
      console.log('每個(gè)數(shù)組元素' + value);
      console.log('每個(gè)數(shù)組元素索引號' + index);
      console.log('數(shù)組元素本身' + array);
    }
filter()

array.filter(function(currentValue, index, arr))

  • filter方法創(chuàng)建一個(gè)新數(shù)組,新數(shù)組中的元素是通過檢查指定數(shù)組中符合條件的所有元素才菠,主要用于篩選數(shù)組茸时。
  • 注意它直接返回一個(gè)新數(shù)組
  • currentValue :數(shù)組當(dāng)前項(xiàng)的值
  • index:數(shù)組當(dāng)前項(xiàng)的索引值
  • arr:數(shù)組對象本身
    // filter方法
    var Arr = [12, 66, 4, 88];
    // 選取大于20的選項(xiàng)
    var newArray = Arr.filter(function(value, index) {
      return value >= 20;
    });

    console.log(newArray);
some()

array.some(function(currentValue, index, arr))

  • some()方法用于檢測數(shù)組中的元素是否滿足指定條件,通俗點(diǎn)查找數(shù)組中是否有滿足條件的元素
  • 注意它返回值是布爾值赋访,如果查找到這個(gè)元素就返回true可都,如果找不到就返回false
  • 如果找到第一個(gè)滿足條件的元素,則終止循環(huán)蚓耽,不再繼續(xù)查找
  • currentValue :數(shù)組當(dāng)前項(xiàng)的值
  • index:數(shù)組當(dāng)前項(xiàng)的索引值
  • arr:數(shù)組對象本身
    // some方法
    var Arr = [10, 30, 4];
    var flag = Arr.some(function(value) {
      // 尋找是否存在大于20的元素
      return value >= 20;
    })

    console.log(flag);// true
map()

array.map(function(currentValue, index, arr))

  • map() 方法返回一個(gè)新數(shù)組渠牲,數(shù)組中的元素為原始數(shù)組元素調(diào)用函數(shù)處理后的值。
  • map() 方法按照原始數(shù)組元素順序依次處理元素步悠。和forEach有點(diǎn)相似签杈。
  • 注意: map() 不會對空數(shù)組進(jìn)行檢測。
  • 注意: map() 不會改變原始數(shù)組贤徒。
// 數(shù)組中的每個(gè)元素乘于輸入框指定的值,并返回新數(shù)組:
var numbers = [65, 44, 12, 4];
function multiplyArrayElement(num) {
    return num * document.getElementById("multiplyWith").value;
}
function myFunction() {
    document.getElementById("demo").innerHTML = numbers.map(multiplyArrayElement);
}
every()

array.every(function(currentValue, index, arr))

  • every() 方法用于檢測數(shù)組所有元素是否都符合指定條件(通過函數(shù)提供)汇四。和some類似
  • every() 方法使用指定函數(shù)檢測數(shù)組中的所有元素:
  • 如果數(shù)組中檢測到有一個(gè)元素不滿足接奈,則整個(gè)表達(dá)式返回 false ,且剩余的元素不會再進(jìn)行檢測通孽。
    如果所有元素都滿足條件序宦,則返回 true。
  • 注意: every() 不會對空數(shù)組進(jìn)行檢測背苦。
  • 注意: every() 不會改變原始數(shù)組互捌。
// 檢測數(shù)組 ages 的所有元素是否都大于等于 18 :
var ages = [32, 33, 16, 40];

function checkAdult(age) {
    return age >= 18;
}

function myFunction() {
    document.getElementById("demo").innerHTML = ages.every(checkAdult);
}
2. 字符串方法
trim()

str.trim()

  • trim()方法會從一個(gè)字符串的兩端刪除空白字符
  • trim方法并不影響原來的字符串本身,它會返回一個(gè)新的字符串行剂。
3. 對象方法
Object.keys()
  • Object.keys()用于獲取對象自身所有的屬性秕噪。

Object.keys(obj)

  • 效果類似for...in
  • 返回一個(gè)由屬性名構(gòu)成的數(shù)組
    var obj = {
      name: '張三',
      age: 18,
      sex: '男'
    }
    Object.keys(obj);
    console.log(Object.keys(obj));
    // ["name", "age", "sex"]
Object.defineProperty()

Object.defineProperty(obj, prop, descriptor)

  • Object.defineProperty()作用是定義新屬性,或者是修改原有的屬性厚宰。
  • Object.defineProperty()第三個(gè)參數(shù)descriptor說明:以對象的形式書寫{}
    1.value:設(shè)置屬性的值腌巾,默認(rèn)為undefined
    2.writable:值是否可以重寫。默認(rèn)為false
    3.enumerable:目標(biāo)屬性是否可以被枚舉铲觉。默認(rèn)為false
    4.configrable:目標(biāo)屬性是否可以被刪除或者是否可以再次修改特性澈蝙。默認(rèn)為false
    var obj = {
      name: '張三',
      age: 18,
      sex: '男'
    }

    //obj.num = 1000;// 以前我們用這種方式增加或修改對象屬性
    Object.defineProperty(obj, 'age', {
      value: 19,
    });

    Object.defineProperty(obj, 'age', {
      // 不允許修改這個(gè)屬性值
      writable: false,
    });

    Object.defineProperty(obj, 'address', {
      value: '中國山東',
      // 如果值為false,則不允許遍歷此項(xiàng)撵幽,默認(rèn)為false
      enumerable: false,
      // 如果值為false灯荧,則不允許刪除這個(gè)屬性,而且接下來也不允許修改第三個(gè)參數(shù)里面的特性盐杂,默認(rèn)false逗载,
      configurable: false,
    })
    delete obj.address;// 試圖刪除address屬性

ES6:Class 的繼承

Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承哆窿,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多撕贞。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調(diào)用父類的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 調(diào)用父類的toString()
  }
}

let cp = new ColorPoint(25, 8, 'green');

cp instanceof ColorPoint // true
cp instanceof Point // true

上面代碼中更耻,constructor方法和toString方法之中,都出現(xiàn)了super關(guān)鍵字捏膨,它在這里表示父類的構(gòu)造函數(shù)秧均,用來新建父類的this對象。

子類必須在constructor方法中調(diào)用super方法号涯,否則新建實(shí)例時(shí)會報(bào)錯(cuò)目胡。這是因?yàn)樽宇愖约旱膖his對象,必須先通過父類的構(gòu)造函數(shù)完成塑造链快,得到與父類同樣的實(shí)例屬性和方法誉己,然后再對其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法域蜗。如果不調(diào)用super方法巨双,子類就得不到this對象。

實(shí)例對象cp同時(shí)是ColorPoint和Point兩個(gè)類的實(shí)例霉祸,這與 ES5 的行為完全一致筑累。

最后,父類的靜態(tài)方法丝蹭,也會被子類繼承慢宗。

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用來從子類上獲取父類。

Object.getPrototypeOf(ColorPoint) === Point
// true

因此奔穿,可以使用這個(gè)方法判斷镜沽,一個(gè)類是否繼承了另一個(gè)類。

super關(guān)鍵字

super 這個(gè)關(guān)鍵字贱田,既可以當(dāng)作函數(shù)使用缅茉,也可以當(dāng)作對象使用。在這兩種情況下男摧,它的用法完全不同宾舅。

第一種情況,super 作為函數(shù)調(diào)用時(shí)彩倚,代表父類的構(gòu)造函數(shù)筹我。ES6 要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super 函數(shù)帆离。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

上面代碼中蔬蕊,子類B的構(gòu)造函數(shù)之中的super(),代表調(diào)用父類的構(gòu)造函數(shù)。這是必須的岸夯,否則 JavaScript 引擎會報(bào)錯(cuò)麻献。

注意,super雖然代表了父類A的構(gòu)造函數(shù)猜扮,但是返回的是子類B的實(shí)例勉吻,即super內(nèi)部的this指的是B的實(shí)例,因此super()在這里相當(dāng)于A.prototype.constructor.call(this)旅赢。

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

上面代碼中齿桃,new.target指向當(dāng)前正在執(zhí)行的函數(shù)≈笈危可以看到短纵,在super()執(zhí)行時(shí),它指向的是子類B的構(gòu)造函數(shù)僵控,而不是父類A的構(gòu)造函數(shù)香到。也就是說,super()內(nèi)部的this指向的是B报破。

作為函數(shù)時(shí)悠就,super()只能用在子類的構(gòu)造函數(shù)之中,用在其他地方就會報(bào)錯(cuò)充易。

class A {}

class B extends A {
  m() {
    super(); // 報(bào)錯(cuò)
  }
}

上面代碼中梗脾,super()用在B類的m方法之中,就會造成語法錯(cuò)誤蔽氨。

第二種情況藐唠,super作為對象時(shí)帆疟,在普通方法中鹉究,指向父類的原型對象;在靜態(tài)方法中踪宠,指向父類自赔。

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

上面代碼中,子類B當(dāng)中的super.p()柳琢,就是將super當(dāng)作一個(gè)對象使用绍妨。這時(shí),super在普通方法之中柬脸,指向A.prototype他去,所以super.p()就相當(dāng)于A.prototype.p()

這里需要注意倒堕,由于super指向父類的原型對象灾测,所以定義在父類實(shí)例上的方法或?qū)傩裕菬o法通過super調(diào)用的垦巴。

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

上面代碼中媳搪,p是父類A實(shí)例的屬性铭段,super.p就引用不到它。

如果屬性定義在父類的原型對象上秦爆,super就可以取到序愚。

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();

上面代碼中,屬性x是定義在A.prototype上面的等限,所以super.x可以取到它的值爸吮。

ES6 規(guī)定,在子類普通方法中通過super調(diào)用父類的方法時(shí)精刷,方法內(nèi)部的this指向當(dāng)前的子類實(shí)例拗胜。

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m() // 2

上面代碼中,super.print()雖然調(diào)用的是A.prototype.print()怒允,但是A.prototype.print()內(nèi)部的this指向子類B的實(shí)例埂软,導(dǎo)致輸出的是2,而不是1纫事。也就是說勘畔,實(shí)際上執(zhí)行的是super.print.call(this)

由于this指向子類實(shí)例丽惶,所以如果通過super對某個(gè)屬性賦值炫七,這時(shí)super就是this,賦值的屬性會變成子類實(shí)例的屬性钾唬。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();

上面代碼中万哪,super.x賦值為3,這時(shí)等同于對this.x賦值為3抡秆。而當(dāng)讀取super.x的時(shí)候奕巍,讀的是A.prototype.x,所以返回undefined儒士。

如果super作為對象的止,用在靜態(tài)方法之中,這時(shí)super將指向父類着撩,而不是父類的原型對象诅福。

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2

上面代碼中,super在靜態(tài)方法之中指向父類拖叙,在普通方法之中指向父類的原型對象氓润。

另外,在子類的靜態(tài)方法中通過super調(diào)用父類的方法時(shí)薯鳍,方法內(nèi)部的this指向當(dāng)前的子類咖气,而不是子類的實(shí)例。

class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3;
B.m() // 3

上面代碼中,靜態(tài)方法B.m里面采章,super.print指向父類的靜態(tài)方法运嗜。這個(gè)方法里面的this指向的是B,而不是B的實(shí)例悯舟。

注意担租,使用super的時(shí)候,必須顯式指定是作為函數(shù)抵怎、還是作為對象使用奋救,否則會報(bào)錯(cuò)。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super); // 報(bào)錯(cuò)
  }
}

上面代碼中反惕,console.log(super)當(dāng)中的super尝艘,無法看出是作為函數(shù)使用,還是作為對象使用姿染,所以 JavaScript 引擎解析代碼的時(shí)候就會報(bào)錯(cuò)背亥。這時(shí),如果能清晰地表明super的數(shù)據(jù)類型悬赏,就不會報(bào)錯(cuò)狡汉。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super.valueOf() instanceof B); // true
  }
}

let b = new B();

上面代碼中,super.valueOf()表明super是一個(gè)對象闽颇,因此就不會報(bào)錯(cuò)盾戴。同時(shí),由于super使得this指向B的實(shí)例兵多,所以super.valueOf()返回的是一個(gè)B的實(shí)例尖啡。

最后,由于對象總是繼承其他對象的剩膘,所以可以在任意一個(gè)對象中衅斩,使用super關(guān)鍵字。

var obj = {
  toString() {
    return "MyObject: " + super.toString();
  }
};

obj.toString(); // MyObject: [object Object]

總結(jié)

  • JS中的函數(shù)可以作為函數(shù)使用援雇,也可以作為類使用

  • 作為類使用的函數(shù)實(shí)例化時(shí)需要使用new

  • 為了讓函數(shù)具有類的功能矛渴,函數(shù)都具有prototype屬性椎扬。

  • 為了讓實(shí)例化出來的對象能夠訪問到prototype上的屬性和方法惫搏,實(shí)例對象的__proto__指向了類的prototype。所以prototype是函數(shù)的屬性蚕涤,不是對象的筐赔。對象擁有的是__proto__,是用來查找prototype的揖铜。

  • prototype.constructor指向的是構(gòu)造函數(shù)茴丰,也就是類函數(shù)本身。改變這個(gè)指針并不能改變構(gòu)造函數(shù)。

  • 對象本身并沒有constructor屬性贿肩,你訪問到的是原型鏈上的prototype.constructor峦椰。

  • 函數(shù)本身也是對象,也具有__proto__汰规,他指向的是JS內(nèi)置對象Function的原型Function.prototype汤功。所以你才能調(diào)用func.call,func.apply這些方法,你調(diào)用的其實(shí)是Function.prototype.callFunction.prototype.apply溜哮。

  • prototype本身也是對象滔金,所以他也有__proto__,指向了他父級的prototype茂嗓。__proto__prototype的這種鏈?zhǔn)街赶驑?gòu)成了JS的原型鏈餐茵。原型鏈的最終指向是Object的原型。Object上面原型鏈?zhǔn)莕ull述吸,即Object.prototype.__proto__ === null忿族。

  • 有朋友提到:Function.__proto__ === Function.prototype。這是因?yàn)镴S中所有函數(shù)的原型都是Function.prototype蝌矛,也就是說所有函數(shù)都是Function的實(shí)例肠阱。Function本身也是可以作為函數(shù)使用的----Function(),所以他也是Function的一個(gè)實(shí)例朴读。類似的還有Object屹徘,Array等,他們也可以作為函數(shù)使用:Object(), Array()衅金。所以他們本身的原型也是Function.prototype噪伊,即Object.__proto__ === Function.prototype。換句話說氮唯,這些可以new的內(nèi)置對象其實(shí)都是一個(gè)類鉴吹,就像我們的Puppy類一樣。

  • ES6的class其實(shí)是函數(shù)類的一種語法糖惩琉,書寫起來更清晰豆励,但原理是一樣的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞒渠,一起剝皮案震驚了整個(gè)濱河市良蒸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伍玖,老刑警劉巖嫩痰,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窍箍,居然都是意外死亡串纺,警方通過查閱死者的電腦和手機(jī)丽旅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纺棺,“玉大人榄笙,你說我怎么就攤上這事〉或颍” “怎么了办斑?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杆逗。 經(jīng)常有香客問我乡翅,道長,這世上最難降的妖魔是什么罪郊? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任蠕蚜,我火速辦了婚禮,結(jié)果婚禮上悔橄,老公的妹妹穿的比我還像新娘靶累。我一直安慰自己,他們只是感情好癣疟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布挣柬。 她就那樣靜靜地躺著,像睡著了一般睛挚。 火紅的嫁衣襯著肌膚如雪邪蛔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天扎狱,我揣著相機(jī)與錄音侧到,去河邊找鬼。 笑死淤击,一個(gè)胖子當(dāng)著我的面吹牛匠抗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播污抬,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汞贸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了印机?” 一聲冷哼從身側(cè)響起矢腻,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耳贬,沒想到半個(gè)月后踏堡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猎唁,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咒劲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年顷蟆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腐魂。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帐偎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛔屹,到底是詐尸還是另有隱情削樊,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布兔毒,位于F島的核電站漫贞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏育叁。R本人自食惡果不足惜迅脐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望豪嗽。 院中可真熱鬧谴蔑,春花似錦、人聲如沸龟梦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽计贰。三九已至钦睡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躁倒,已是汗流浹背赎婚。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留樱溉,地道東北人挣输。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像福贞,于是被迫代替她去往敵國和親撩嚼。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容