JS 手写系列

267 阅读8分钟

手写系列

new

思路:最终返回一个对象(或函数)
1、获取函数fn () //var Contructor = [].shift.call(arguments)
2、创建空对象,设置 对象._ proto_ = fn.prototype
3、执行函数,获取返回值
4、判断返回值是否为对象或函数,是则返回,否则返回创建的对象

function myNew(){
    var obj = new Object()
    var Contructor = [].shift.call(arguments)
    if (Object.prototype.toString.call(Contructor) !== '[object Function]'){
        throw new TypeError(Contructor + 'is not a function')
    }
    obj.__proto__ = Contructor.prototype
    var ret = Contructor.apply(obj, arguments)
    return (typeof ret === 'object' || typeof ret === "function")?ret || obj :obj
}

//函数式
function myNew(fn){
    if (Object.prototype.toString.call(fn) !== '[object Function]'){
        throw new TypeError(fn + 'is not a function')
    }
    return function(){
        var obj = {
            __proto__ = fn.prototype
        }
        fn.call(obj,...arguments)
        return obj
    }
}

function Otaku (name,age){
    this.name = name
    this.age = age
    this.habit = "game"
    return "111"
}
Otaku.prototype.p = "p"
Otaku.prototype.say = function(){
    return 'hello'
}


var p = myNew(Otaku,"kin",18)
console.log(p.name)
console.log(p.age)
console.log(p.habit)
console.log(p.p)
console.log(p.say())

bind

思路:最终返回一个函数
1、获取原函数,获得第一个参数(用于bind的obj)
2、在返回函数体获得参数并执行函数,使用call/apply


//无参数情况下
Function.prototype.bind2 = function (context){
    var self = this
    return function (){
        return self.apply(context)
    }
}

//有参数情况下,在bind的时候传入一部分,在调用的时候传入一部分
Function.prototype.bind2 = function (context){
    var self = this
    //获取绑定时的参数
    var args = Array.prototype.slice.call(arguments,1)
    
    return function(){
        //获取调用时传入的参数
        var bindArgs = Array.prototype.slice.call(arguments)
        return self.apply(context, args.concat(bindArgs))
    }
}

//支持new
Function.prototype.bind2 = function(context){
    var self = this
    var args = Array.prototype.slice.call(arguments,1)
    
    var fBound = function(){
        var bindArgs = Array.prototype.slice.call(arguments)
        return self.apply(this instanceof fBound ? this : context,args.concat
            (bindArgs))
    }

    fBound.prototype = this.prototype
    return fBound
}

//支持new 使用Object.create 挂属性(最终版本)
Function.prototype.bind2 = function (context) {
    var self = this
    var args = Array.prototype.slice.call(arguments, 1)

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments)
        return self.apply(this instanceof fBound ? this : context, args.concat
            (bindArgs))
    }
    fBound.prototype = Object.create(this.prototype)
    return fBound
}

var foo = {value:1}
function bar(name,age){
    console.log(this.value) // undefined
    this.name = name
    this.age = age
    console.log(name) //"name"
    console.log(age) //"age"
}
var bin = bar.bind2(foo,"name")
var b = new bin("age")

console.log(b)//function
console.log(b.age) // "age"

call

思路:执行一个函数,this指向传入的第一个参数
1、context 为null 时 ,this指向window
2、有参数情况,获取到函数,挂到对象上,执行,删除对象上的函数属性
3、有返回值情况下,获取返回值返回

// 无参数情况下,获取到函数,挂到对象上,执行,删除对象上的函数属性
Function.prototype.call2 =function(context){
    context.fn = this
    context.fn()
    delete context.fn
}

// 有参数情况下,获取到函数,挂到对象上,执行,删除对象上的函数属性
Function.prototype.call2 = function (context) {
    context.fn = this
    // conetxt.fn()

    //ES6写法
    // var args = []
    // for (var i = 1;i<arguments.length;i++){//此处参数从1开始
    //     args.push('arguments['+i+']')
    // }
    // eval('context.fn('+args+')')

    let args = [...arguments].slice(1)
    context.fn(...args)

    delete context.fn
}


//context 为null 时 ,this指向window
//有参数情况,获取到函数,挂到对象上,执行,删除对象上的函数属性
//有返回值情况下,获取返回值返回
Function.prototype.call2 = function (context) {
    context = context || window // 浏览器下为window,对象为null 时,this为全局
    context.fn = this
    
    let args = [...arguments].slice(1)
    const ret = context.fn(...args)
    
    delete context.fn
    return ret
}


var value = 2

var foo = {
    value: 1
}
function bar(name) {
    console.log(this.value)
    console.log(name)
    return 1
}
console.log(bar.call2(foo,"a"))

apply

思路:与call 相似,只是参数为数组或5无
1、context 为null 时 ,this指向window
2、有参数情况,获取到函数,挂到对象上,执行,删除对象上的函数属性
3、有返回值情况下,获取返回值返回

//ES3写法
Function.prototype.apply2 = function(context,arr){
    context = context || window
    context.fn = this
    var ret;
    if (!arr){
        ret = context.fn() 
    }
    else {
        var args = []
        for(var i = 0;i<arr.length;i++){
            args.push('arr['+i+']')
        }
        ret = eval('context.fn('+args+')')
    }
    delete context.fn
    return ret
}

Function.prototype.apply2 = function (context) {
    context = context || window
    context.fn = this
    let ret 
    if (arguments[1]){
        ret = context.fn(...arguments[1])
    }
    else {
        ret = context.fn()
    }
    delete context.fn
    return ret
}
var value = 2

var foo = {
    value: 1
}
function bar(name,age) {
    console.log(this.value)
    console.log(age)
    console.log(name)
    return 1
}
console.log(bar.apply2(foo, ["a","age"]))

JSON(JSONStringify/JSONParse)

1、JSONStringify

思路 
1、除了对象和数组,其他字符串包装后返回
2、对象和数组,拿到值继续递归
3、返回值判断用[] 还是 {}

function jsonStringify(obj) {
    let type = typeof obj;
    if (type !== "object" || obj === null) {
        // if (/string|undefined|function/.test(type)) {
        //     obj = '"' + obj + '"';
        // }
        return String(obj);
    } else {
        let json = [],
            arr = (obj.constructor === Array);

        for (let k in obj) {
            let v = obj[k];
            let type = typeof v;

            if (/string|undefined|function/.test(type)) {
                v = '"' + v + '"';
            } else if (type === "object") {
                v = myJsonStringify(v);
            }

            json.push((arr ? "" : '"' + k + '":') + String(v));
        }
        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
    }
}
JSON.parse(jsonStringify(["a",undefined]))

2、JSONParse

//有两种
// eval 和 Function

function jsonParse(opt){
    return eval('(' + opt + ')')
}
function jsonParse (opt){
    return (new Function('return '+opt))()
}
var jsonStr = JSON.stringify({a:"a"})
console.log(jsonParse(jsonStr))

deepClone

//循环引用
//解决循环引用问题,我们可以额外开辟一个存储空间,
//来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,
//先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,
//如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题

function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            if (Object.prototype.hasOwnProperty.call(target,key)){
                cloneTarget[key] = clone(target[key], map);
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
};
const obj = {
    test: {
        a: 2
    },
    arr: [],
    fn: function () {
        console.log("clone function");
    }
};
//故意设置循环引用造成Maximum call stack size
obj.obj = obj;
console.log(clone(obj));

instanceof

function new_instanceof (leftValue,rightValue){
    let leftValue = leftValue.__proto__
    let rightValue = rightValue.prototype
    
    while(true){
        if (leftValue === rightValue) return true
        if (leftValue === null) return false //Object.prototype.__proto__ === null
        leftValue = leftValue.__proto__
    }
}

Object.create

// 思路:将传入的对象作为原型

function create(obj) {
    function F() {}
    F.prototype = obj
    return new F()
  }

reduce

Array.prototype.reduce2 = function(){
    var ary = this
    var {length} = ary
    if (arguments.length === 0) {
        throw new TypeError('undefined is not a function')
    }
    if (typeof arguments[0] !== "function") {
        throw new TypeError(arguments[0] + 'is not a function')
    }
    if (length === 0 && arguments.length === 1){
        throw new TypeError('Reduce of empty array with no initial value')
    }

    var callback = arguments[0]
    var startIndex = arguments.length >= 2?0:1
    var value = startIndex === 0?arguments[1]:ary[0]
    for(var i = startIndex;i<length;i++){
        value = callback(value,ary[i],i,ary)
    }
    return value
}

Array.prototype.reduceRight2 = function(){
    var ary = this
    var {length} = ary
    if (arguments.length === 0) {
        throw new TypeError('undefined is not a function')
    }
    if (typeof arguments[0] !== "function") {
        throw new TypeError(arguments[0] + 'is not a function')
    }
    if (length === 0 && arguments.length === 1) {
        throw new TypeError('Reduce of empty array with no initial value')
    }

    var callback = arguments[0]
    var startIndex = arguments.length >= 2? length - 1:length - 2
    var value = startIndex === length - 1 ?arguments[1]: ary[length - 1]
    for (var i = startIndex;i>=0;i--){
        value = callback(value,ary[i],i,ary)
    }
    return value
}

map


Array.prototype.map2 = function(){
    var ary = this
    var res = new Array(ary.length)
    var [fn,self] = Array.prototype.slice.call(arguments)
    if (typeof fn !== "function"){
        throw new TypeError(fn + 'is not a function')
    }

    for (var i = 0;i<ary.length;i++){
        if (i in ary){
            res[i] = fn.call(self,ary[i],i,ary)
        }
    }
    return res
}

function mapFromReduce(fn,self){
    return (ary)=>{
        if (typeof fn !== 'function') {
            throw new TypeError(fn + 'is not a function');
        }
        if (!Array.isArray(ary)) {
            throw new TypeError('list must be a Array');
        }
        if (ary.length === 0) return [];

        var res = new Array(ary.length)
        return ary.reduce((acc,cur,i)=>{
            if (i in ary){
                acc[i] = fn.call(self, cur, i, ary)
            }
            return acc
        }, res)
    }
}

var res = mapFromReduce(x=>x+1)([1,2,3])

// var res = [1,2,3].map(function(item){
//     console.log(this.a)
//     return item
// },{a:"a"})
console.log(res)

节流 throttle

var count = 1
var container = document.getElementById("container")
function getUserAction(e) {
    // console.log(e)
    container.innerHTML = count++
}

container.onmousemove = throttle(getUserAction,1000)

// 时间戳
function throttle(fn,wait){
    var context,args 
    var previous = 0

    return function(){
        var now = +new Date()
        context = this
        args = arguments
        if (now - previous > wait){
            fn.apply(context,args)
            previous = +new Date()
        }
    }
}

// 定时器
function throttle(fn, wait) {
    var context, args
    var timeout 
    return function(){
        if (!timeout){
            context = this
            args = arguments
            timeout = setTimeout(()=>{
                fn.apply(context,args)
                timeout = null
            },wait)
        }
    }
}

//二者结合 马上执行,还有离开后到时间会继续执行一次

function throttle (fn,wait){
    var timeout,context,args
    var previous = 0

    var throttle = function(){
        context = this
        args = arguments

        var now = +new Date()
        var remaining = wait - (now - previous)
        if (remaining<0 || remaining>wait){//立即执行
            if (timeout){
                clearTimeout(timeout)
                timeout = null
            }
            previous = now
            fn.apply(context, args)
        }
        else if(!timeout){//延时执行
            timeout = setTimeout(() => {
                previous = +new Date()
                timeout = null
                fn.apply(context,args)
            }, remaining);
        }
    }

    throttle.cancle = function(){
        clearTimeout(timeout)
        timeout = null
        previous = 0
    }

    return throttle
}

去抖 debounce

// function debounce(func) {
//     var t;
//     return function () {
//         cancelAnimationFrame(t)
//         t = requestAnimationFrame(func);
//     }
// }    

var count = 1
var container = document.getElementById("container")
function getUserAction(e){
    // console.log(e)
    container.innerHTML = count++
}

// container.onmousemove = getUserAction
//初版
function debounce(fn,wait){
    var timeout
    return function(){
        clearTimeout(timeout)
        timeout = setTimeout(fn,wait)
    }
}

// this指向
function debounce(fn, wait) {
    var timeout
    return function () {
        var context = this
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            fn.apply(context)
        }, wait)
    }
}

// this指向、event对象(参数)
function debounce(fn, wait) {
    var timeout
    return function () {
        var context = this
        var args = arguments
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}

//添加是否立即执行,后等待时间(添加返回值)
// 此时注意一点,就是 getUserAction 函数可能是有返回值的,
// 所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,
// 因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,
// 最后再 return 的时候,值将会一直是 undefined,
// 所以我们只在 immediate 为 true 的时候返回函数的执行结果。
function debounce(fn, wait,immediate) {
    var timeout
    var res
    return function () {
        var context = this
        var args = arguments
        timeout && clearTimeout(timeout)
        if (immediate){
            var runNow = !timeout
            timeout = setTimeout(() => {
                timeout = null
            }, wait)
            if (runNow) res = fn.apply(context,args)
        }
        else {
            timeout = setTimeout(() => {
                fn.apply(context, args)
            }, wait)
        }
        return res
    }
}

//立即执行
container.onmousemove = debounce(getUserAction, 500, true)


// 添加cancle方法 函数返回一个方法,方法中带cancle函数
function debounce(fn, wait, immediate) {
    var timeout
    var res
    var debounce = function () {
        var context = this
        var args = arguments
        timeout && clearTimeout(timeout)
        if (immediate) {
            var runNow = !timeout
            timeout = setTimeout(() => {
                timeout = null
            }, wait)
            if (runNow) res = fn.apply(context, args)
        }
        else {
            timeout = setTimeout(() => {
                fn.apply(context, args)
            }, wait)
        }
        return res
    }
    debounce.cancle = function(){
        clearTimeout(timeout)
        timeout = null
    }

    return debounce
}

clone Buffer

from lodash

const allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
function cloneBuffer(buffer, isDeep) {
  if (isDeep) {
    return buffer.slice();
  }
  var length = buffer.length,
    result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);

  buffer.copy(result);
  return result;
}

clone RegExp

from lodash
由于JSON.parse无法深拷贝 正则对象,所以自己实现
核心是lastIndex要跟着改变
如果正则表达式设置了全局标志,test() 的执行会改变正则表达式 lastIndex属性。连续的执行test()方法,后续的执行将会从 lastIndex 处开始匹配字符串,(exec() 同样改变正则本身的 lastIndex属性值).

var regex = /foo/g;

// regex.lastIndex is at 0
const a = regex.test('foo'); // true
console.log(a) //true
// regex.lastIndex is now at 3
const b = regex.test('foo'); // false
console.log(b) //false


深拷贝一份实现与原regex一样的功能

var regex = /foo/g
const a = regex.test('foo'); // true
console.log(a)
var regex2 = cloneRegExp(regex)
const b = regex2.test('foo'); // false
console.log(b) // false

function cloneRegExp(regexp){
  	// const result = new regexp.constructor(regexp.source, regexp.flags)
    const result = new regexp.constructor(regexp.source, /\w*$/.exec(regexp))
    result.lastIndex = regexp.lastIndex 
    return result
}

参考文章

JavaScript深入之new的模拟实现
JavaScript深入之bind的模拟实现
lodash