new, call, apply, bind模拟实现

一:模拟new的实现

我们首先看一下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 一个对象之后,返回的是什么,以及它的内部做了哪些操作?

  • 一个继承aninmal的对象cat被创建
  • cat.__proto__ === aninmal.prototype
  • 需要执行构造函数aninmal,并将this 指向新创建的对象实例cat
  • 返回被一个新对象
    • 如果构造函数没有显示返回,怎返回this
    • 如果构造函数显式的返回值,返回的是基本类型, 如 number,string,boolean, 则返回的还是this, 注意返回null,也是返回this
    • 如果构造函数显式返回的是对象,如 {name: 1}, 则返回这个对象{name: 1}

通过以上分析,我们来实现一个MNew来模拟以上实现

    function isObject (obj) {
        return (typeof obj === 'object' && typeof obj !== null) || typeof obj === 'function'
    }
    function MNew () {
        // this
        let o = new Object()
        // 获取构造函数
        let FunctionConstructor = Array.prototype.shift.call(arguments)
        // this对象指向构造函数的对象
        o.__proto__ = FunctionConstructor.prototype
        // 需要执行构造函数aninmal,并将this 指向新创建的对象实例cat
        let obj = FunctionConstructor.apply(o, arguments)
        return isObject(obj) ? obj : o
    }

我们来一个简单的例子测试一下MNew

function parent (name, gender) {
    this.name = name
    this.gender = gender
    // return null
    // function _dd () {
    //     console.log('hellow')
    // }
    // return _dd
    // 默认return this
}

let aa = mNew(parent, 'rose', 'female')
console.log(aa)

let cc = new parent('jack', 'male')
console.log(cc) 

二: 模拟call实现

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 -MDN

function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
    // this是当前 Food 的实例,继承于Product
  Product.call(this, name, price);
  this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// expected output: "cheese"

我们分析下,如何实现一个call

  • Product函数执行了
  • Product函数里的this被绑定了call的第一个参数
  • Product的入参是call第一个之后的参数
// 有一点值得我们思考: 在没有依赖call,apply,bind的情况下,如果修改this指向
// 这里利用对象方法的形式: 把 Product 作为 call 第一个参数this的属性,如果通过 this.Product的方法调用,那么 Product里this就被绑定了call的this
Function.prototype.call = function (context, ...args) {
    let context = context || window
    // context 是 被绑定的this
    // this 是被call 调用的方法 
    let context.fn = this
    // 被绑定的this
    let res = context.fn(...args)
    delete context.fn
    return res
}

我们写一个简单的例子测试下:

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 模拟的实现

我们既然实现了call的实现,那么 apply实现起来就简单多了

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。- MDN

通过MDN的定义,我们发现,call和 apply 唯一的区别就是,第二个参数的形式不同

fn.call(obj, p1, p2, p3)
fn.apply(obj, [p1, p2, p3])

同样,我们分析,下如何实现一个apply,还是以Product这个例子来说明

  • Product函数执行了
  • Product函数里的this被绑定了apply的第一个参数
  • Product的入参是apply第一个之后的参数的数组,内部会把数组拆开
Function.prototype.apply = function (this, args) {
    let context = this || window
    context.fn = this
    let res = context.fn(...args)
    delete context.fn
    return res
}

同样,拿刚才的例子测试下

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}

四:接下来,我们来实现bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 -MDN

先来2个简单的例子

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)
// 值得注意的是: 通过构造函数返回,this仍然是rose的实例
// name==> undefined: rose 当前 name没有赋值
// 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()
// 输出: 参数被带入了被bind的新函数
// rose 成功被绑定为obj
// gender===> male

通过以上的例子,我们来分析一下如何模拟一个bind,我们结合rose,jack例子来说明

  • rose.bind(jack, b, c) 返回一个新的函数extendRose,函数的this指向 jack, 内置参数是 b, c
  • extendRose 如果是通过new来调用的,则实例对象仍是rose的实例, this指向rose的实例。

接下来,我们来实现一个bind

Function.prototype.bind = function (context, ...bindArgs) {
    // 不是函数绑定直接报错
    if (typeof this !== 'function') {
        throw new Error('this must be a function')
    }
    // 调用函数
    let fn = this
    let fbound = function(...args) {
        args = [...bindArgs, ...args]
        // 看下面的关键点,来理解这一句代码
        return fn.apply(this instanceof fn ? this : context, args)
    }
    // this 是当前被绑定的函数
    // 关键点:this 有原型对象,返回的函数不能丢失this原型链对象上的属性
    if (this.prototype) {
        fbound.prototype = Object.create(this.prototype)
    }
    return fbound
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容