JavaScript常规手写的复习

282 阅读6分钟

前言

这篇主要是一些js手写的实现汇总,忘了写,写了忘,主要是复习一下吧。

new的实现

假如我们new一个构造函数Fn。(看下红宝书怎么说的吧)

  • 在内存中创建一个新对象
  • 这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性
  • 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
  • 执行构造函数内部的代码(给新对象添加属性)
  • 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
function newFn(fn) {
    let obj = {}
    obj.__proto__ = fn.prototype 
    //let obj = Object.create(fn.prototype) 前面两步,可以直接用这个替代
    let res = fn.call(obj)
    return typeof res === 'object' && res !== null ? res : obj
}

Object.create实现

  • 传入一个对象obj,在内部创建一个新的对象obj2,obj2的原型指向对象obj
function create(obj) {
    function F() {}
    F.prototype = obj
    return new F()
}

instanceof的实现

  • 概念:a instanceof B,a为实例,B为构造函数,一般是检查a实例是否被B构造函数所创建
  • 实现:获取a的隐式原型,判断是否与B的显式原型是同一个,不是的话继续在a的原型链上找到下一个原型与B的原型比较,如果遍历完所有原型链还没找到相同,返回false
// right.prototype 在 left.__proto__ 原型链上
function instanceof(left, right) {
    if(typeof left !== 'object' || left === null) return false
    let proto = Object.getPrototypeOf(left)
     while(proto) {
         if(right.prototype === proto) {
             return true 
         }
         proto = Object.getPrototypeOf(left)
     }
     return false
}

call、apply、bind的实现

call、apply的实现

call 和apply的API唯一的区别是,apply第2个参数只能是一个数组,数组里面接收函数的所有参数,call使用普通方式传参

  • call应该由函数调用,否则抛出错误
  • 当传入的需要绑定的参数是一个null或者undefined值,会默认绑定window;传入的是一个基本类型的值,会通过Object处理一下
Function.prototype._call = function(context, ...args) {
    if(typeof this !== 'function') {
        throw new TypeError('this is not function')
    }
    context = typeof context == null ? window : Object(context)
    let caller = Symbol('caller')
    context[caller] = this
    let res = context[caller](...args)
    delete context[caller]
    return res
}

Function.prototype._apply = function(context, arr) {
    if(typeof this !== 'function') {
        throw new TypeError('this is not function')
    }
    context = typeof context == null ? window : Object(context)
    let caller = Symbol('caller')
    context[caller] = this
    let res = context[caller](...arr)
    delete context[caller]
    return res
}

bind实现

  • 绑定传入的参数作为this,返回一个未执行的函数
  • 当返回的函数作为构造函数来使用,传入的参数作为this将失效
Function.prototype._bind = function(context, ...args1) {
    if(typeof this !== 'function') {
        throw new TypeError('this is not function')
    }
   let fn = this
   let boundFunc = function(...args2) {
       if(new.target) {
           let res = fn.call(this, ...args1, ...args2)
           return typeof res === 'object' && res !== null ? res : this
       } else {
           let res = fn.call(context, ...args1, ...args2)
           return res
       }
   }
   if(fn.prototype) {
       boundFunc.prototype = Object.create(fn.prototype)
   }
   return boundFunc
}

原型继承的6种方式

原型链继承

  • 实现:将父类的实例赋值给子类的原型上
  • 缺点:1.父类实例上的属性将会被多个实例共享2.不能实现传参给父类
function Super() {
    this.colors = ['red', 'blue', 'green']
}
function Sub() {}
Sub.prototype = new Super()
let instance1 = new Sub()
instance1.colors.push('black')

let instance2 = new Sub()
console.log(instance2.colors) // 'red', 'blue', 'green', 'black'

借用构造函数继承

  • 实现:在子类里面,通过call调用父类方法
  • 优点:解决了向父类传参的问题
  • 缺点:父类原型上的方法和属性不能继承
// 父类
function Super(name) {
    this.name = name;
    this.color = ['red', 'blue', 'green']
}
Super.prototype.sayName = function(){
    console.log('say name')
}
// 子类
function Sub(name) {
    Super.call(this, name)
}
let instance1 = new Sub('jack')
instance1.sayName // undefined

组合继承

  • 实现:综合了原型链继承和借用构造函数继承;先在子类里面call调用父类方法,再将父类的实例赋值给子类构造函数的显示原型上
  • 缺点:父类构造函数被执行了2次(父类的属性,会同时存在子类实例的属性和原型上)
// 父类
function Super(name) {
    this.name = name;
    this.color = ['red', 'blue', 'green']
}
Super.prototype.sayName = function(){
    console.log('say name')
}
// 子类
function Sub(name) {
    // 第2次调用父类的构造函数
    Super.call(this, name)
}
// 第1次调用父类构造函数
Sub.prototype = new Super()

let instance1 = new Sub('Jack')

原型式继承

  • 实现:主要实现继承对象的属性和方法,将该对象赋值给空对象的原型
  • 缺点:多个实例会共享父类引用类型的属性,无法传参
let person = {
    name: 'jack',
    friends: ['Van']
}
let son1 = Object.create(person);
son1.friends.push('Rob')

let son2 = Object.create(person);
son2.friends.push('Rob1')
console.log(son1.friends) // 'Van', 'Rob', 'Rob1'

寄生式继承

和原型式继承差不多,缺点也是一样,在对象增加了属性和方法

   let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };

  function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
      return this.friends;
    };
    return clone;
  }

  let person5 = clone(parent5);

组合寄生式继承

目前是引用类型继承的最佳模式

  • 实现:综合了借用构造函数继承和寄生式继承
// 父类
function Super(name) {
    this.name = name;
    this.color = ['red', 'blue', 'green']
}
Super.prototype.sayName = function(){
    console.log('say name')
}

// 子类
function Sub(name) {
    // 借用构造函数
    Super.call(this, name)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub

let instance = new Sub('jack')

Promise实现

基本版promise实现

原理也是基于发布订阅模式,先将回调函数push到一个数组中,然后在异步触发的时候,遍历执行回调函数

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
    constructor(exector) {
        this.value = undefinde;
        this.reason = undefinde;
        this.status = PENDING;
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        let resolve = function(value) {
            if(this.status === PENDING) {
                this.value = value
                this.status = FULFILLED
                this.onFulfilledCallbacks.forEach(fn => fn())
            }
        }
        let reject = function(value) {
            if(this.status === PENDING) {
                this.reason = reason
                this.status = REJECTED
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }
        try {
            exector(resolve, reject)
        } catch(e) {
            reject(e)
        }
    }
    
    then(onFulfilled, onRejected) {
        if(this.status === PENDING) {
            this.onFulfilledCallbacks.push(()=>{
                setTimeout(()=>{
                    onFulfilled(this.value)
                }, 0)
            })
            this.onRejectedCallbacks.push(()=>{
                setTimeout(()=>{
                    onRejected(this.reason)
                })
            })
        }
        if(this.status === FULFILLED) {
            setTimeout(()=>{
                onFulfilled(this.value)
            }, 0)
        }
        if(this.status === REJECTED) {
            setTimeout(()=>{
                onRejected(this.reason)
            })
            
        }
    }
}

Promise.all实现

  • 接收一个数组,数组里面可以传入值或者promise实例,所有实例成功后,将返回结果,有一个失败就返回失败
  • 实现:通过计数来计算所有异步是否请求成功,判断是否返回结果
Promise.all = function(arr) {
    if(!Array.isArray(arr)) {
        throw new TypeError('... is not iterable')
    }
    const result = []
    let count = 0
    return new Promise((resolve, reject)=>{
        let processData = (value, i) => {
            count++;
            result[i] = value;
            if(count === arr.length) {
                resolve(result)
            }
        }
        for(let i = 0; i < arr.length; i++) {
            let value = arr[i]
            if(value && typeof value.then === 'function') {
                value.then((val)=>{
                    processData(val, i)
                }, reject)
            } else {
                processData(value, i)
            }
        }
    })
    
}

Promise.prototype.finally实现

  • 不管promise返回的是成功的结果,还是失败的结果,都会执行传入的回调函数
  • 最后再将成功的结果或者失败的结果返回,可以继续调用then方法
Promise.prototype.finally = function(onFinshed) {
    return this.then((res)=>{
        onFinshed()
        return res
    })
    .catch((err)=>{
        onFinshed()
        return err
    })
}

深拷贝

  • 传入的对象做遍历,判断对象的类型,从而创建一个空对象,对其赋值
function deepClone(obj, map = new WeakMap()) {
    // 不是对象直接返回
    if(typeof obj !== 'object' || obj === null) {
        return obj;
    }
    // 避免循环引用
    if(map.has(obj)) {
        return map.get(obj)
    }
    // 如果是这几种类型,可以直接创建对象返回
    if([RegExp, Date, Map, Set, WeakMap, WeakSet].includes(obj.constructor)) {
        return new obj.constructor(obj)
    }
    const result = Array.isArray(obj) ? [] : {};
    map.set(result, true)
    Object.keys(obj).forEach(key=>{
        const value = obj[key]
        if(typeof value === 'object' && value !== null) {
            result[key] = deepClone(value, map);
        } else {
            result[key] = value
        }
    })
    return result
}

防抖

function debounce(func, time = 17 , options = { leading: true, context: null }) {
    let timer = null;
    const _debounce = (...args) => {
        if(timer) {
            clearTimeout(timer)
            timer = null
        }
        // 第一次触发会执行
        if(options.leading && !timer) {
            timer = setTimeout(null, time)
            func.apply(options.context, args)
        } else {
            timer = setTimeout(()=>{
                func.apply(options.context, args)
                timer = null
            }, time)
        }
        
    }
    return _debounce
}

节流

// trailing 表示最后一次执行
function throttle(func, time = 17, options = {context: null, trailing: false }) {
    let timer = null;
    let prev = 0;
    const _throttle = (...args) => {
        let now = Date.now()
        if(now - prev > time) {
            func.apply(options.context, args)
            prev = now
        } else if(options.trailing) {
            clearTimeout(timer)
            timer = setTimeout(()=>{
                func.apply(options.context, args)
                timer = null
            }, time)
        }
    }
    return _throttle;
}

最后

js的一些基础知识还是很简单的,只是工作重复性的工作导致我们忘记,需要偶尔的复习一下。这些很多红宝书都会讲的很详细,大家有时间可以多翻翻。