486 原型及原型鏈模式:3個重要知識點,3個重要知識點,從面向物件角度來講解內建類,hasOwnProperty,原型鏈方法中的THIS問題,基於內建類的原型擴充套件方法
阿新 • • 發佈:2020-06-28
3個重要知識點
【建構函式是函式型別,例項物件是物件。】
-
每一個
函式資料型別
的值,都有一個天生自帶的屬性:prototype
(原型),這個屬性的屬性值是一個物件(“用來儲存例項公用屬性和方法”)- 普通的函式
- 類(自定義類和內建類)
-
在prototype這個物件中,有一個天生自帶的屬性:
constructor
,這個屬性儲存的是當前函式本身Fn.prototype.constructor === Fn // true
-
每一個
物件資料型別
的值,也有一個天生自帶的屬性:__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, "嘻", "好", "哈", "呵"]