(七)-前端-原型鏈
7.原型及原型鏈
原型
- 函式都帶有一個prototype 屬性,這是屬性是指向建構函式的原型物件,這個物件包含所有例項共享的屬性和方法。
- 原型物件都有一個constructor 屬性,這個屬性指向所關聯的建構函式。
- 每個物件都有一個__proto__ 屬性[非標準的方法],這個屬性指向建構函式的原型 prototype
原型鏈
- 當訪問例項物件的某個屬性時,會先在這個物件本身的屬性上查詢,如果沒有找到,則會 通過 proto 屬性去原型上查詢,如果還沒有 找到則會在建構函式的原型的__ proto__中查 找, 這樣一層層向上查詢就會形成一個作用域鏈,稱為原型鏈
原型相關習題
![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image006.png)
![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image007.png)
Object.create****的作用:
let obj = {a:123};
let o = Object.create(obj);
//該函式返回了一個新的空物件,但是該空物件的__proto__是指向了obj這個引數
// 手寫Object.create
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
new****的執行過程是怎麼回事?
new操作符做了這些事:
· 它建立了一個全新的物件
· 它會被執行[[Prototype]](也就是__proto__)連結
· 它使this指向新建立的物件
· 通過new建立的每個物件將最終被[[Prototype]]連結到這個函式的prototype物件上
· 如果函式沒有返回物件型別Object(包含Functoin, Array, Date, RegExg, Error),那麼new表示式中的函式呼叫將返回該物件引用
//模擬new
function objectFactory() { const obj = new Object(); const Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; const ret = Constructor.apply(obj, arguments); return typeof ret === "object" ? ret : obj; }
call,apply,bind****三者的區別?
**
** apply() 方法呼叫一個函式, 其具有一個指定的this值,以及作為一個數組(或類似陣列的物件)提供的引數fun.apply(thisArg, [argsArray])
apply 和 call 基本類似,他們的區別只是傳入的引數不同。
apply 和 call 的區別是 call 方法接受的是若干個引數列表,而 apply 接收的是一個包含多個引數的陣列。
bind()方法建立一個新的函式, 當被呼叫時,將其this關鍵字設定為提供的值,在呼叫新函式時,在任何提供之前提供一個給定的引數序列。
call做了什麼:
將函式設為物件的屬性
執行&刪除這個函式
指定this到函式並傳入給定引數執行函式
如果不傳入引數,預設指向為 window
//實現一個call方法:
Function.prototype.myCall = function(context) {
//此處沒有考慮context非object情況
context.fn = this;
let args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
context.fn(...args);
let result = context.fn(...args);
delete context.fn;
return result;
};
/ 模擬 apply
Function.prototype.myapply = function(context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
實現bind要做什麼
返回一個函式,繫結this,傳遞預置引數
bind返回的函式可以作為建構函式使用。故作為建構函式時應使得this失效,但是傳入的引數依然有效
// mdn的實現
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true時,說明返回的fBound被當做new的建構函式呼叫
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 獲取呼叫時(fBound)的傳參.bind 返回的函式入參往往是這麼傳遞的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 維護原型關係
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的程式碼使fBound.prototype是fNOP的例項,因此
// 返回的fBound若作為new的建構函式,new生成的新物件作為this傳入fBound,新物件的__proto__就是fNOP的例項
fBound.prototype = new fNOP();
return fBound;
};
}
// 簡單版
Function.prototype.myBind = function(context,...arg){
// this ---> fn
var _this = this;
return function(...ary){
// _this(...arg)
return _this.apply(context,arg.concat(ary))
}
}
實現類的繼承
類的繼承在幾年前是重點內容,有n種繼承方式各有優劣,es6普及後越來越不重要,那麼多種寫法有點『回字有四樣寫法』的意思,如果還想深入理解的去看紅寶書即可,我們目前只實現一種最理想的繼承方式。
function Parent(name) {
this.parent = name
}
Parent.prototype.say = function() {
console.log(`${this.parent}: 你打籃球的樣子像kunkun`)
}
function Child(name, parent) {
// 將父類的建構函式繫結在子類上
Parent.call(this, parent)
this.child = name
}
/**
1. 這一步不用Child.prototype =Parent.prototype的原因是怕共享記憶體,修改父類原型物件就會影響子類
2. 不用Child.prototype = new Parent()的原因是會呼叫2次父類的構造方法(另一次是call),會存在一份多餘的父類例項屬性
3. Object.create是建立了父類原型的副本,與父類原型完全隔離
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
console.log(`${this.parent}好,我是練習時長兩年半的${this.child}`);
}
// 注意記得把子類的構造指向子類本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打籃球的樣子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是練習時長兩年半的cxk
談談你對this指向的理解
this 的指向,始終堅持一個原理:this 永遠指向最後呼叫它的那個物件
改變 this 的指向我總結有以下幾種方法:
· 使用 ES6 的箭頭函式
· 在函式內部使用 _this = this
· 使用 apply、call、bind
· new 例項化一個物件
全域性作用域下的this指向window
如果給元素的事件行為繫結函式,那麼函式中的this指向當前被繫結的那個元素
函式中的this,要看函式執行前有沒有 . , 有 . 的話,點前面是誰,this就指向誰,如果沒有點,指向window
自執行函式中的this永遠指向window
定時器中函式的this指向window
建構函式中的this指向當前的例項
call、apply、bind可以改變函式的this指向
箭頭函式中沒有this,如果輸出this,就會輸出箭頭函式定義時所在的作用域中的this