Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试之手撕手写实现 #24

Open
wweggplant opened this issue Sep 25, 2023 · 0 comments
Open

面试之手撕手写实现 #24

wweggplant opened this issue Sep 25, 2023 · 0 comments

Comments

@wweggplant
Copy link
Owner

现在面试经常遇到需要手写javascript原生方法的要求。最常见的如,手写bindapplycall方法。这些题目的目的在于考察你对于javascript的理解能力。

call

实现callapply类似,有几个要点:

  1. 如果实现改变this上下文执行环境
  2. 参数如何处理
  3. 传入this的类型不同如何处理

入股偶改变this

我们知道函数执行中,this的指向有三种情况,1. 使用new构造一个实例 2. 使用构造一个属性为函数的对象,通过.预算符调用函数 3. 出以上两种外,默认this是全局对象,如window.那么分析可知只有在1和2里做选择了.构造函数方法没有普遍性,所以只能通过方法2了. 我们从参数中得到上下文环境context和要调用的函数fn,吧fn作为context中的属性,调用结束后删除调这个属性就可以了.

有个问题是,如果`context`中已经存在了`fn`属性的话,会把原本的属性覆盖掉.可以额外再加一个属性的检测.

有了上下文和函数后, 之后就是如何调用了.使用eval函数,拼接一个函数的字符串传入进入就可以了.

// text.call2(this, ....)
Function.prototype.call2 = function () {
    var args = []
    var context
    // 如果是基础类型的话,自动封一个包装类
    if (context == null) {
        context = window
    } else {
        context = typeof context != 'object' ?  Object(arguments[0]) : context
    }
    context.fn = this
    for(var i = 1;i < arguments.length;i++)
        args.push('arguments[' + i + ']')
    var result = eval('context.fn('+args+')')
    delete context.fn
    return result
}

参数处理与传入

通过对arguments的浅复制, 有同学可能要问, 可以直接用Array.prototype.slice.call(arguments, 1)得到参数的列表吗?题要求是实现自己的call,然后在里面又用了call是不是很怪.所以就用嘴简单的办法遍历arguments复制到一个新的数组.得到了参数列表后,我们要在上面的eval中拼接参数, context.fn('+args+')里,数组会自动调用toString()方法,转换成带有,的字符串.

传入this的类型不同如何处理

以上已经完成了90%的工作了,最后就剩下this类型处理的分析.如果thisnull或者undefinedthiswindow.如果this是Object类型的话,以上的逻辑的没有问题的,如果是基础类型的怎么办呀?基础类型是不可以添加属性的,所以我们要加一个判断,如果是对象类型的话,就用这个对象,否则就把这个对象转换成对象类型.

apply

apply和call类似

// text.call2(this, ....)
Function.prototype.call2 = function (context, arr) {
    var args = []
    // 如果是基础类型的话,自动封一个包装类
    if (context == null) {
        context = window
    } else {
        context = typeof context != 'object' ?  Object(arguments[0]) : context
    }
    context.fn = this
    for(var i = 1;i < arr.length;i++)
        args.push('arr[' + i + ']')
    var result = eval('context.fn('+args+')')
    delete context.fn
    return result
}

bind

// var fn = test.bind(this, 1,2,3)
Function.prototype.bind2 = function () {
    var fn = this
    var context = arguments[0], preArgs = []
    var args = arguments
    if (context == null) {
        context = window
    } else {
        context = typeof context != 'object' ?  Object(arguments[0]) : context
    }
    for(var i = 1;i< args.length;i++) {
        preArgs.push('args[' + i + ']')
    }
    function fBound () {
        context.fn = fn
        var arr  = preArgs
        for(var i = 0;i < arguments.length;i++) {
            arr.push('arguments[' + i + ']')
        }
        var result = eval('context.fn('+arr+')')
        delete context.fn
        return result
    }
    // 新生成的函数要继承之前函数
    var noop = function() {}
    noop.prototype = fn.prototype
    fBound.prototype = new noop();
    return fBound
}

new操作符

new是一个关键字,我们没有办法创建一个关键字,所以只能创建一个工厂函数,要注意的是下面几点:

  1. 传入的构造函数的原型要指向返回对象的__prop__
  2. 构造函数也有一个执行的结果,如果这个结果是对象的话就返回它
function objectFactory() {
    var obj = new Object()
    var Cst = [].shift.call(arguments);
    obj.__prop__ = Cst.prototype
    // 初始化
    var ret = Constructor.apply(obj, arguments);
    return typeof ret === 'object' ? ret : obj;
}

手写curry

可以有以下的使用方法

var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

实现如下:

function curry(fn, args) {
    var length = fn.length
    var args = args || []
    return function() {
        for(var i = 0;i< arguments.length;i++) {
            _args.push(arguments[i])
        }
        if (_args.length < length) {
            return curry.call(this, fn, _args)
        } else {
            return fn.apply(this, _args)
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant