一:模擬new的實(shí)現(xiàn)
我們首先看一下new的使用
function aninmal (name, actions) {
this.name = name
this.actions = actions
this.sayName = function () {
console.log('myname :>> ', this.name)
}
}
let cat = new aninmal('cat', ['eat', 'cat-walk'])
我們分析一下,new 一個(gè)對(duì)象之后,返回的是什么畸陡,以及它的內(nèi)部做了哪些操作?
- 一個(gè)繼承aninmal的對(duì)象cat被創(chuàng)建
cat.__proto__ === aninmal.prototype
- 需要執(zhí)行構(gòu)造函數(shù)aninmal虽填,并將this 指向新創(chuàng)建的對(duì)象實(shí)例cat
- 返回被一個(gè)新對(duì)象
- 如果構(gòu)造函數(shù)沒(méi)有顯示返回丁恭,怎返回this
- 如果構(gòu)造函數(shù)顯式的返回值,返回的是基本類型斋日, 如 number牲览,string,boolean, 則返回的還是this, 注意返回null,也是返回this
- 如果構(gòu)造函數(shù)顯式返回的是對(duì)象恶守,如 {name: 1}, 則返回這個(gè)對(duì)象{name: 1}
通過(guò)以上分析第献,我們來(lái)實(shí)現(xiàn)一個(gè)MNew來(lái)模擬以上實(shí)現(xiàn)
function isObject (obj) {
return (typeof obj === 'object' && typeof obj !== null) || typeof obj === 'function'
}
function MNew () {
// this
let o = new Object()
// 獲取構(gòu)造函數(shù)
let FunctionConstructor = Array.prototype.shift.call(arguments)
// this對(duì)象指向構(gòu)造函數(shù)的對(duì)象
o.__proto__ = FunctionConstructor.prototype
// 需要執(zhí)行構(gòu)造函數(shù)aninmal,并將this 指向新創(chuàng)建的對(duì)象實(shí)例cat
let obj = FunctionConstructor.apply(o, arguments)
return isObject(obj) ? obj : o
}
我們來(lái)一個(gè)簡(jiǎn)單的例子測(cè)試一下MNew
function parent (name, gender) {
this.name = name
this.gender = gender
// return null
// function _dd () {
// console.log('hellow')
// }
// return _dd
// 默認(rèn)return this
}
let aa = mNew(parent, 'rose', 'female')
console.log(aa)
let cc = new parent('jack', 'male')
console.log(cc)
二: 模擬call實(shí)現(xiàn)
call() 方法使用一個(gè)指定的 this 值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來(lái)調(diào)用一個(gè)函數(shù)兔港。 -MDN
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
// this是當(dāng)前 Food 的實(shí)例庸毫,繼承于Product
Product.call(this, name, price);
this.category = 'food';
}
console.log(new Food('cheese', 5).name);
// expected output: "cheese"
我們分析下,如何實(shí)現(xiàn)一個(gè)call
- Product函數(shù)執(zhí)行了
- Product函數(shù)里的this被綁定了call的第一個(gè)參數(shù)
- Product的入?yún)⑹莄all第一個(gè)之后的參數(shù)
// 有一點(diǎn)值得我們思考: 在沒(méi)有依賴call,apply,bind的情況下衫樊,如果修改this指向
// 這里利用對(duì)象方法的形式: 把 Product 作為 call 第一個(gè)參數(shù)this的屬性飒赃,如果通過(guò) this.Product的方法調(diào)用利花,那么 Product里this就被綁定了call的this
Function.prototype.call = function (context, ...args) {
let context = context || window
// context 是 被綁定的this
// this 是被call 調(diào)用的方法
let context.fn = this
// 被綁定的this
let res = context.fn(...args)
delete context.fn
return res
}
我們寫(xiě)一個(gè)簡(jiǎn)單的例子測(cè)試下:
function bb(params) {
this.bb = 123
console.log('params==>', params) // 456
return this
}
let obj = {aa: 456}
let cc = bb.call(obj, 456) // {aa: 345, bb: 123}
三:apply 模擬的實(shí)現(xiàn)
我們既然實(shí)現(xiàn)了call的實(shí)現(xiàn),那么 apply實(shí)現(xiàn)起來(lái)就簡(jiǎn)單多了
apply() 方法調(diào)用一個(gè)具有給定this值的函數(shù)载佳,以及以一個(gè)數(shù)組(或類數(shù)組對(duì)象)的形式提供的參數(shù)炒事。- MDN
通過(guò)MDN的定義,我們發(fā)現(xiàn)蔫慧,call和 apply 唯一的區(qū)別就是挠乳,第二個(gè)參數(shù)的形式不同
fn.call(obj, p1, p2, p3)
fn.apply(obj, [p1, p2, p3])
同樣,我們分析藕漱,下如何實(shí)現(xiàn)一個(gè)apply欲侮,還是以Product這個(gè)例子來(lái)說(shuō)明
- Product函數(shù)執(zhí)行了
- Product函數(shù)里的this被綁定了apply的第一個(gè)參數(shù)
- Product的入?yún)⑹莂pply第一個(gè)之后的參數(shù)的數(shù)組,內(nèi)部會(huì)把數(shù)組拆開(kāi)
Function.prototype.apply = function (this, args) {
let context = this || window
context.fn = this
let res = context.fn(...args)
delete context.fn
return res
}
同樣肋联,拿剛才的例子測(cè)試下
function bb(a, b, c) {
this.bb = 123
console.log('params==>', a, b, c) // 3,4,5
return this
}
let obj = {aa: 456}
let cc = bb.apply(obj, [3,4,5]) // {aa: 345, bb: 123}
四:接下來(lái)威蕉,我們來(lái)實(shí)現(xiàn)bind
bind() 方法創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí)橄仍,這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù)韧涨,而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用侮繁。 -MDN
先來(lái)2個(gè)簡(jiǎn)單的例子
function rose (gender, hobby) {
this.gender = gender
this.hobby = hobby
console.log('name==>', this.name)
return this
}
let jack = {
name : 'jack'
}
let extendRose = rose.bind(jack, 'female', ['swimming', 'football'])
console.log(extendRose())
// { name: 'jack', gender: 'female', hobby: [ 'swimming', 'football' ] }
let extendRose = rose.bind(jack, 'female', ['swimming', 'football'])
let person = new extendRose()
console.log(person)
// 值得注意的是: 通過(guò)構(gòu)造函數(shù)返回虑粥,this仍然是rose的實(shí)例
// name==> undefined: rose 當(dāng)前 name沒(méi)有賦值
// rose { gender: 'female', hobby: [ 'swimming', 'football' ] }
let name = 'jack'
function getPerson (gender) {
console.log(this.name)
console.log('gender===>', gender)
}
let obj = {
name: 'rose'
}
let person = getPerson.bind(obj, 'male')
person()
// 輸出: 參數(shù)被帶入了被bind的新函數(shù)
// rose 成功被綁定為obj
// gender===> male
通過(guò)以上的例子,我們來(lái)分析一下如何模擬一個(gè)bind宪哩,我們結(jié)合rose,jack例子來(lái)說(shuō)明
- rose.bind(jack, b, c) 返回一個(gè)新的函數(shù)extendRose娩贷,函數(shù)的this指向 jack, 內(nèi)置參數(shù)是 b, c
- extendRose 如果是通過(guò)new來(lái)調(diào)用的,則實(shí)例對(duì)象仍是rose的實(shí)例锁孟, this指向rose的實(shí)例彬祖。
接下來(lái),我們來(lái)實(shí)現(xiàn)一個(gè)bind
Function.prototype.bind = function (context, ...bindArgs) {
// 不是函數(shù)綁定直接報(bào)錯(cuò)
if (typeof this !== 'function') {
throw new Error('this must be a function')
}
// 調(diào)用函數(shù)
let fn = this
let fbound = function(...args) {
args = [...bindArgs, ...args]
// 看下面的關(guān)鍵點(diǎn)品抽,來(lái)理解這一句代碼
return fn.apply(this instanceof fn ? this : context, args)
}
// this 是當(dāng)前被綁定的函數(shù)
// 關(guān)鍵點(diǎn):this 有原型對(duì)象储笑,返回的函數(shù)不能丟失this原型鏈對(duì)象上的屬性
if (this.prototype) {
fbound.prototype = Object.create(this.prototype)
}
return fbound
}