1. 程式人生 > 實用技巧 >486 原型及原型鏈模式:3個重要知識點,3個重要知識點,從面向物件角度來講解內建類,hasOwnProperty,原型鏈方法中的THIS問題,基於內建類的原型擴充套件方法

486 原型及原型鏈模式:3個重要知識點,3個重要知識點,從面向物件角度來講解內建類,hasOwnProperty,原型鏈方法中的THIS問題,基於內建類的原型擴充套件方法

3個重要知識點

【建構函式是函式型別,例項物件是物件。】

  1. 每一個函式資料型別的值,都有一個天生自帶的屬性:prototype(原型),這個屬性的屬性值是一個物件(“用來儲存例項公用屬性和方法”)

    • 普通的函式
    • 類(自定義類和內建類)
  2. 在prototype這個物件中,有一個天生自帶的屬性:constructor,這個屬性儲存的是當前函式本身

    Fn.prototype.constructor === Fn // true
    
  3. 每一個物件資料型別的值,也有一個天生自帶的屬性:__proto__,這個屬性指向“所屬類的原型prototype”

    • 普通物件、陣列、正則、Math、日期、類陣列等等
    • 例項也是物件資料型別的值
    • 函式的原型prototype屬性的值也是物件型別的
    • 函式也是物件資料型別的值
/*
 * 類:函式資料型別
 * 例項:物件資料型別的
 */
function Fn() {
	/*
	 * NEW執行也會把類當做普通函式執行(當然也有類執行的一面)
	 *   1.建立一個私有的棧記憶體
	 *   2.形參賦值 & 變數提升
	 *   3.瀏覽器建立一個物件出來(這個物件就是當前類的一個新例項),並且讓函式中的THIS指向這個例項物件  => “建構函式模式中,方法中的THIS是當前類的例項”
	 *   4.程式碼執行
	 *   5.在我們不設定RETURN的情況下,瀏覽器會把建立的例項物件預設返回 
	 */
    this.x = 100;
    this.y = 200;
    this.say = function () { }
}

Fn.prototype.eat = function () {
    console.log('吃飯睡覺打豆豆');
}

Fn.prototype.say = function () { }

var f1 = new Fn();
var f2 = new Fn();


原型鏈查詢機制

1.先找自己私有的屬性,有則調取使用,沒有繼續找
2.基於__proto__找所屬類原型上的方法(Fn.prototype),如果還沒有則繼續基於__proto__往上找...一直找到Object.prototype為止


從面向物件角度來講解內建類


hasOwnProperty

檢測某一個屬性名是否為當前物件的私有屬性

“in” :檢測這個屬性是否屬於某個物件(不管是私有屬性還是公有屬性,只要是它的屬性,結果就為TRUE)

// 自己堆中有的就是私有屬性,需要基於__proto__查詢的就是公有屬性(__proto__在IE瀏覽器中(EDGE除外)給保護起來了,不讓我們在程式碼中操作它)
let ary = [10, 20, 30];
console.log('0' in ary);  // => TRUE
console.log('push' in ary); // => TRUE
console.log(ary.hasOwnProperty('0'));  // => TRUE
console.log(ary.hasOwnProperty('push')); // => FALSE,"push"是它公有的屬性,不是私有的

// => TRUE,是公有屬性,還是私有屬性,需要看相對誰來說的
console.log(Array.prototype.hasOwnProperty('push')); 
console.log(Array.prototype.hasOwnProperty('hasOwnProperty')); // => FALSE
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); // => TRUE   

檢測某個屬性是否為物件的公有屬性:hasPubProperty

方法:是它的屬性,但是不是私有的

// 基於內建類原型擴充套件方法
Object.prototype.hasPubProperty = function (property) {
    // => 驗證傳遞的屬性名合法性(一般只能是數字或字串等基本值)
    let x = ["string", "number", "boolean"],
        y = typeof property;
    if (!x.includes(y)) return false;
    // => 開始校驗是否為公有的屬性(方法中的THIS就是要校驗的物件)
    let n = property in this,
        m = this.hasOwnProperty(property);
    return n && !m;
}
console.log(Array.prototype.hasPubProperty('push')); // => FALSE
console.log([].hasPubProperty('push')); // => TRUEa

原型鏈方法中的THIS問題

/*
 * 面向物件中有關私有/公有方法中的THIS問題
 *   1.方法執行,看前面是否有點,點前面是誰THIS就是誰 【確定this指向】
 *   2.把方法總的THIS進行替換 
 *   3.再基於原型鏈查詢的方法確定結果即可
 */

function Fn() {
	// => this:f1這個例項
	this.x = 100;
    this.y = 200;
    // 建構函式中有自己的屬性、方法,則用建構函式中的,不用原型物件上的
	this.say = function () {
        // 普通函式的this,看誰呼叫的
		console.log(this.x);
	}
}

Fn.prototype.say = function () {
	console.log(this.y);
}

Fn.prototype.eat = function () {
	console.log(this.x + this.y);
}

Fn.prototype.write = function () {
	this.z = 1000;
}

let f1 = new Fn;
f1.say(); // => this: f1  => console.log(f1.x)   => 100
f1.eat(); // => this: f1  => console.log(f1.x + f1.y)   => 300

// => this: f1.__proto__   => console.log(f1.__proto__.y),不找私有屬性,在原型上找,原型上沒有y屬性,Object的原型上也沒有   => undefined
f1.__proto__.say(); 

// => this: Fn.prototype   => console.log(Fn.prototype.x + Fn.prototype.y) ,undefined + undefined =>  NaN
Fn.prototype.eat(); 

// => this: f1   => f1.z=1000   => 給f1設定一個私有的屬性z=1000
f1.write(); 

// => this: Fn.prototype   => Fn.prototype.z=1000   => 給原型上設定一個屬性z=1000(屬性是例項的公有屬性)
Fn.prototype.write();
console.log(f1.z) // 1000



基於內建類的原型擴充套件方法

/*
 * 基於內建類的原型擴充套件方法 
 *   在內建類原型上的方法,類所對應的例項可以直接調取使用,例如:例項.方法()  ary.push()
 *   如果我們也把自己寫的方法放到原型上,那麼當前類的例項也可以直接這樣調取使用了,很方便
 * 
 * 但是也有需要注意的地方
 * 	 1.自己擴充套件的方法不能影響原有內建的方法(我們自己設定的方法最好加字首: 如my)
 *   2.擴充套件方法中的THIS一般都是當前類的例項(也就是要操作的值):例項.方法()
 */

// 補充
let obj = { aa: 11, bb: 22 }
console.log(obj.cc) // undefined

~ function () {
	/*
	 * myUnique : 實現陣列去重
	 *   @params
	 *   @return
	 *      [Array] 去重後的陣列
	 * by 666 on 20190805
	 */
    function myUnique() {
        // 此時沒有傳遞要操作的ARY進來,但是方法中的THIS是當前要操作的陣列,因為是該陣列呼叫該方法:ARY.MYUNIQUE()
        let obj = {};
        for (let i = 0; i < this.length; i++) {
            let item = this[i];
            // 如果obj中沒有item這一項,就是undefined,不等於undefined,說明有了
            if (typeof obj[item] !== 'undefined') {
                // (1)刪除重複項,會把這一項後面的所有項都往前提一位,效能差;
                // (2)下一輪迴圈,i++,就會空出一位,防止出現塌陷問題,i--;
                // (3)最後一項拿過來,替換當前項,當前項就不能用了,然後把最後一項刪除
                this[i] = this[this.length - 1];
                this.length--;
                i--;
                continue; // 存在了,就不往裡存了
            }
            obj[item] = item;
        }
        obj = null;
        // 保證當前方法執行完返回的結果依然是ARRAY類的一個例項
        return this;
    }
    // => 擴充套件到內建類的原型上
    Array.prototype.myUnique = myUnique;
}();

let ary = [12, 23, 13, 12, 23, 24, 34, 13, 23];
// ary.myUnique(); 返回去重後的陣列(也是ARRAY類的例項)
// ary.sort((a, b)  =>  a - b); 返回排序後的陣列
// => 鏈式寫法(保證返回值依然是當前類的例項 一般都會RETURN THIS)
// ary.myUnique().sort((a, b)  =>  a - b).reverse().slice(2).push('珠峰').concat(12);// => Uncaught TypeError: ary.myUnique(...).sort(...).reverse(...).slice(...).push(...).concat is not a function  執行完push返回的是一個數字(新增後陣列的長度),不是陣列了,不能在繼續使用陣列的方法
ary.myUnique().sort((a, b)  =>  a - b).reverse();
console.log(ary);


/* Array.prototype.push = function () {
	console.log("哈哈哈");
}
let ary = [1, 2, 3];
ary.push(100); // => "哈哈哈"
console.log(ary); // => 陣列沒變*/


// --------------------------


// 補充:我之前去重的老寫法
function myUnique(arr) {
    let obj = {}
    arr.forEach((item, index) => {
        obj[item] = item
    })
    // {12: 12, 13: 13, 23: 23, 24: 24, 34: 34, 嘻: "嘻", 好: "好", 哈: "哈", 呵: "呵"}
    console.log(obj) 
    arr = []
    for(let k in obj) {
        arr.push(obj[k])
    }
    return arr
}

let arr = ['嘻', '好', 12, 23, 13, 12, 23, 24, 34, 13, 23, '哈', '呵', '嘻', '好', '哈', '呵'];
let res = myUnique(arr)
console.log(res) // [12, 13, 23, 24, 34, "嘻", "好", "哈", "呵"]