前端手撕

前端手撕

new

function _new(fn,...args){
    const obj = Object.create(fn.prototype);
    const res = fn.apply(obj,args)
    return res instanceof Object ? ret: obj;
}
  1. 创建一个空对象
  2. 修改这个对象的原型指向函数的原型
  3. 修改this的指向为该新空对象
  4. 判断构造函数有没有return返回值,有的话直接返回这个返回值,否则返回第一步创建的对象

call

使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

// 用法:function.call(thisArg, arg1, arg2, ...)
Function.prototype.myCall=function(){
    if(typeof this !== 'function'){
        throw new TypeError('this is not a function!');
    }
    let context = arguments[0] || window || global;
    context.fn = this;
    let result = context.fn([...arguments].slice(1));
    delete context.fn;
    return result;
}
  1. 首先先判断this指向的是不是一个函数,不是的话就抛出错误
  2. 通过arguments对象拿取第一个参数,也就是我们需要绑定的this对象,不传参的情况下指向全局对象
  3. 在这个context上下文中新增一个属性fn,它等于我们的this,也就是调用call方法的函数,也就是fn==原函数
  4. 此时的fn虽然函数还是那个函数,但执行context.fn(…arguments)的时候this明显就指向context了
  5. 删除副作用fn,然后返回函数执行结果result

bind

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

简易面试版:

Function.prototype.customBind = function () {
  if (typeof this !== 'function') {
    throw new TypeError('error!')
  }
  const that = this 
  let context = arguments[0] || window
  const args = [...arguments].slice(1)
  return function() {
    return that.apply(context, args.concat([...arguments]))
  }
}
  1. 获取调用bind的函数,用this获取并存放在that
  2. 然后获取bind传参时多传的要绑定的参数args
  3. 返回的是一个函数的引用,因此return 的是一个function
  4. 这个function运行结果就是我们的存放在that中的函数apply,绑定的是我们存放在context中也就是bind传进来的上下文,参数则是,bind已经绑定好的参数加上我们运行这个绑定好的新函数再传入的新参数[...arguments

这个简易版最大的问题就在于不支持使用new调用bind好的构造函数

具体解决可以去看profill

compose

function compose(...args){
    return (subArgs)=>{
        return args.reverse().reduce((acc,func,index)=>{
            return func(acc);
        },subArgs);
    }
}

看一下它的作用

var a=a=>a+1
var b=b=>2+b
var c=c=>c+'5'
compose(a,b,c)()
=>"2undefined51"
compose(a,b,c)(100)
=>"210051"

顾名思义,组合,我们定义三个有返回值的函数,通过compose组合成一个新的函数,然后调用。第一次调用不入参,即a(b(c(undefined))),第二次为a(b(c(100)))

pipe函数管道

类似组合,只是执行顺序有所改变,改变为c(b(a(100)))这样

深拷贝

function deepClone(obj,hash= new WeakMap()){
    let type = Object.prototype.toString.call(obj).slice(8,-1);
    if(type==='RegExp'){
        // regExp.source 正则对象的源模式文本;
        // regExp.flags 正则表达式对象的标志字符串;
        // regExp.lastIndex 下次匹配开始的字符串索引位置
        let temp =  new RegExp(obj.source, obj.flags);
        temp.lastIndex = obj.lastIndex;
        return temp;
    }
    if(hash.has(obj)){return hash.get(obj)}
    let newObj = new obj.constructor();
    hash.set(obj,newObj);
    Reflect.ownKeys(obj).forEach(key=>{
        if(typeof obj[key] === 'object' && obj[key]!==null){
            newObj[key] = deepClone(obj[key],hash)
        }else{
            newObj[key] = obj[key];
        }
    });
    return newObj;
}

EventBus

class EventEmitter{
    constructor(){
        this.cache = {};
    }
    on(name,fn){
        if(this.cache[name]){
            this.cache[name].push(fn);
        }else{
            this.cache[name]=[fn];
        }
    }
    off(name,fn){
        // 一个任务队列
        const tasks=this.cache[name];
        if(tasks){
            const index = task.findIndex(f=>f===fn||f.callback===fn);
            // 找到对应任务并移除
            if(index>=0){
                task.splice(index,1)
            }
        }
    }
    emit(name,once=false){
        if(this.cache[name]){
            const tasks = this.cache[name].slice();
            for(let fn of tasks)fn();
            if(once)delete this.cache[name];
        }
    }
}

事件总线的核心:一个cache队列

API:

on:注册事件,事件对应一个对应事件队列,即一个事件可能会有多个回调函数

off:解除事件,删除对应事件的对应回调函数

emit:提交触发事件,找出对应事件队列,逐个运行,如果设置了once则只能运行一次,运行完毕对应事件将被删除

Class

前提知识:

  1. **Symbol.hasInstance**用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。

    因此在class类中如果用这个属性定义好自定义的实例判断,则转换时需要考虑这个方面的原因

  2. es6 class不能直接调用,只能使用new关键字调用;那怎么判断呢?

    判断this指针!

    • 如果是直接调用class函数的话,那么他的this就是window全局对象(class定义在全局),此时的this instanceOf constructor就会为false
    • 如果是通过new关键字的话,前面我们提到了,这个关键字会创建新对象,修改this指针为构造器,这个就是我们判断的根据点了!!
  3. es6的静态方法和原型共有方法

    static方法是静态的,也就是会直接将方法挂在构造函数上而不是实例或者实例的原型上

  4. 最后用一个IIFE将构造器return出来,来保证这个构造器不会造成变量污染

// es6
class Person {
  	constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    eat() {
        return  'eat'
    }
    static say() {
    	return 'say'
    }
}

// 通过babel转换成的es5语法
"use strict";
// 判断某对象是否为某构造器的实例
function _instanceof(left, right) { 
    if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
        return !!right[Symbol.hasInstance](left); 
    } else { return left instanceof right; }
}

// 检查声明的class类是否通过new的方式调用,否则会报错
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { 
    throw new TypeError("Cannot call a class as a function"); } 
}
/**
 *将方法添加到原型上,如果是静态方法添加到构造函数上,
 **/ 

function _defineProperties(target, props) { 
    // 遍历函数数组,分别声明其描述符 并添加到对应的对象上
    for (var i = 0; i < props.length; i++) { 
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false; // 设置该属性是否能够出现在对象的枚举属性中。默认为 false
        descriptor.configurable = true; // 设置该属性描述符能够被改变,同时该属性也能从对应的对象上被删除。
        if ("value" in descriptor) descriptor.writable = true; // 如果属性中存在value, value设置为可以改变。
        Object.defineProperty(target, descriptor.key, descriptor); // 写入对应的对象上
    }
}

// 收集公有函数和静态方法,将方法添加到构造函数或构造函数的原型中,并返回构造函数。
function _createClass(Constructor, protoProps, staticProps) { 
    if (protoProps) _defineProperties(Constructor.prototype, protoProps); // 共有方法写在property原型上
    if (staticProps) _defineProperties(Constructor, staticProps); // 静态方法写到构造函数上 
    return Constructor; 
}

var Person = function () {
    function Person(name, age) {
        _classCallCheck(this, Person);

        this.name = name;
        this.age = age;
    }

    _createClass(Person, [{
        key: "eat",
        value: function eat() {
            return 'eat';
        }
    }], [{
        key: "say",
        value: function say() {
            return 'say';
        }
    }]);

    return Person;
}();

promise跟proxy后面再单独开一个详细点写,毕竟自己也还不太熟。

参考文章:

JS的call,apply与bind详解,及其模拟实现

MDN bind

Raynos

前端面试-手撕代码篇

前端面试常见的手写功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值