前端手写代码

下面介绍一些常用的源码实现
实现一个深拷贝
实现 new 操作符
实现instanceof
防抖
节流
函数柯里化
实现 call,apply
实现 bind
实现Ajax
Promise.all

实现一个深拷贝

深拷贝为对象创建一个相同的副本,两者的引用地址不同。
一般方法:JSON的序列化和反序列化(JSON.Parse(JSON.stringify(obj))),但是这种方式有两个不足:
1.undefined, null, symbol 类型的值会被删除。
2.碰见循环引用的时候回报错
循环引用解决方案:可以使用一个WeakMap结构存储已经被拷贝的对象,每一次进行拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回.

下面我们简单实现一个深拷贝

function isObj(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj, hash = new WeakMap()) {
    if(hash.has(obj)) return hash.get(obj)
    let cloneObj = Array.isArray(obj) ? [] : {}
    hash.set(obj, cloneObj)
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}
// 测试
let o1 = {
    a: 1,
    b: null,
    c: undefined,
    d: "hello",
    e: [111,222,333,444],
    f: true,
    s: Symbol('ww'),
}
o1.oo = o1   // 循环引用
let o2 = deepCopy(o1)
console.log(o2)


实现 new 操作符

大体可以分为3步
1.创建一个新的对象
2.将新对象的的 _ proto _ 指向 构造函数的 prototype 对象
3.执行构造函数,并将 this 指向新的对象

function myNew(fn, ...args) {
    if(typeof fn !== 'function') {
        throw fn + 'is not a constructor'
    }
    let obj = {};
    obj.__proto__ = fn.prototype;
    // let obj = Object.create(null)  也使用 ES5 的 Object.create() 来代替上面两行代码
    let res = fn.apply(obj, args);
    return res instanceof Object ? res : obj;  
    // return 的时候需要对返回的东西进行判断,若是对象则返回,如果不是对象,则返回新创建的对象。
}
// 测试
function Person(name, age) {
    this.name = name;
    this.age = age
}
console.log(myNew(Person, "dingding", 100))


实现instanceof

a instanceof b 用于判断 构造函数 b 的 prototype 是否存在于 a 的原型链上
常用于判断引用类型

function myInstanceof(left, right) {
    left = left.__proto__;
    
    while(left !== right.prototype) {
        left = left.__proto__
        if(left === null) 
        return false
    }
    return true;
}


// 测试
var a = []
var b = {}
 
function Foo(){}
var c = new Foo()
 
function child(){}
function father(){}
child.prototype = new father() 
var d = new child()
 
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // true


防抖

在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时

function ajax() {
    console.log("我是 ajax")
}
function debounce(cb, delay) {
    let timer = null;
    return function(args) {
        let that = this;   // 获得函数的作用域
                let args = arguments;
        clearTimeout(timer);  // 每次事件被触发,都会清除当前的timeer,然后重写设置超时调用
        timer = setTimeout(function(){
            cb.apply(that, args);
        }, delay)
    }
}

// 测试,模拟一个在 2100毫秒内每隔半秒就重复触发的事件
var rsu = debounce(ajax, 1000,);
let itv = setInterval(()=> {
    rsu(888)
}, 500)
setTimeout(()=> {
    clearInterval(itv)
}, 2100)


节流

使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数

function jl(fn, time, ...args) {
    let lock = false;
    return function() {
        console.log(1111)
        if(lock) return;
        setTimeout(() => {
            fn.apply(this, ...args);
            lock = true;
        }, time)
    }
}
function ajax() {
    console.log(999999999)
}
// 测试
var fun = jl(ajax, 2000);
let st = setInterval(()=> {
    fun()
}, 600)
setTimeout(()=> {
    clearInterval(st)
}, 2000)

函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

函数柯里化

实现 sum(11, 22, 33) => sum(11, 22)(33)() = 66 的效果
传入参数时,不执行,而是先记忆起来,延迟计算,什么时候想要计算,直接 sum() 就行

var currying = function (fn) {
    var args = []
    return function() {
        if(arguments.length === 0) {
            return fn.apply(this, args)
        }
        // [].slice.call(arguments) 将函数的实际参数转化成数组
        Array.prototype.push.apply(args, [].slice.call(arguments))
        // arguments.callee 返回当前匿名函数
        // rguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,
        // 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文,这有利于匿名函数的递归或者保证函数的封装性
        return arguments.callee
    }
}
var tempFun = function() {
    var sum = 0; 
    for(var i = 0; i < arguments.length; i++) {
        sum += arguments[i]
    }
    return sum
}
// 测试
var sum = currying(tempFun);
sum(11,22)
sum(33)
sum()


call 和 apply

用法:https://www.jianshu.com/p/aa2eeecd8b4f

原本操作

var o1 = {
 name: 'dingding'
}
function say() {
    console.log(222, this.name)
}
o1.fun = say;
o1.fun()     // 222 dingding

如果不使用 o1.fun = say 进项绑定
手写apply

Function.prototype.apply = function (context, args) {
    context = context ? context : window
    let result
    context['fn'] = this // this是test函数
    result = context['fn'](...args)
    delete context['fn']
    return result
}
// 用法
var obj = {name:333}
function test(flag){
    console.log(this.name, flag)
}
test.apply(obj, [10])

手写call

Function.prototype.call = function (context, ...args) {
    context = context ? context : window
//     console.log(this, context) // this 就是test函数,想想
    context['fn'] = this // 把函数作为对象的某个成员值
    let result = context['fn'](...args)  // 把函数执行,此时函数中的this就是
    delete context['fn'] // 设置完成员属性后,删除
    return result
}
// 用法
var obj = {name:333}
function test(flag){
    console.log(this.name, flag)
}
test.call(obj, 10)

// 相当于变成这样
obj = {
    name:333,
  fn: test
}


bind

bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值,
例如,f.bind(obj),实际上可以理解为obj.f(),这时,f函数体内的this自然指向的是obj
bind 基础用法:https://www.jianshu.com/p/25a855c01896

下面实现myBind 方法可以绑定对象,可以传参, 还要注意函数柯里化的情况

Function.prototype.myBind = function(context) {
    const self = this;
    let args = [...arguments].slice(1);   // args: [7, 8]
    
    return function() {
        // 考虑函数柯里化的情况
        let newArgs = [...arguments];     //newArgs: [9]
        return self.apply(context, newArgs.concat(args))
    }
}

// 测试
function a(m, n, o){
    return this.name + ' ' + m + ' ' + n + ' ' + o;
}
var b = {name : 'kong'};
console.log(a.myBind(b, 7, 8)(9)); 


实现Ajax

实现一个ajax其实主要是一个XMLHttpRequest对象以及其API方法的一个使用的问题。而在这里我建议尽量封装成promise的形式,方便使用。

function ajax({url, methods, body, headers}) {
    return new Promise((resolve, reject) => {
        let request = new XMLHttpRequest();
        request.open(url, methods);
        for(let key in headers) {
            let value = headers[key]
            request.setRequestHeader(key, value);
        }
        request.send(body)
        request.onreadystatechange = () => {
            if(request.readyState === 4) {
                if(request.status >= '200' && request.status < 300) {
                    resolve(request.responeText);
                } else {
                    reject(request)
                }
            }
        }
       
    })
}

Promise.all

// 假设一下Promise其他所有函数都正常工作,但Promise.all功能失效了,我们现在就要为程序重写一个Promise.all
Promise.all = function(promises) {
    let results = [];
    let promiseCount = 0;
    return new Promise((resolve, reject) => { 
        for(let i = 0; i < promises.length; i++) {            // 使用let保证promise顺序执行
            Promise.resolve(promises[i]).then(res => {    // 传入的 promises 元素可能不是 Promise 类型的,使用 Promise.resolve(arr[i]) 转换。
                promiseCount++;
                results[i] = res;
                if(promiseCount === promises.length) {        // 当所有函数都正确执行了,resolve输出所有返回结果。
                    resolve(results);
                }
            }, err => {
                reject(err);
            })
        }
    })
}

let p1 = new Promise((resolve) => {
    setTimeout(()=> {
        console.log("p1 resolve");
        resolve(111);
    }, 1000)
})
let p2 = new Promise((resolve) => {
    console.log('p2 resolve');
    resolve(222);
})
let p3 = new Promise((resolve) => {
    console.log('p3 resolve');
    resolve(333);
})

var p = Promise.all([p1, p2, p3]);
console.log(1212, p)
p.then(e => {
    console.log(e)
});



若有错误,欢迎留言~~

image.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351