JS之手写XX

手动实现bind、apply及bind

  • 实现call()
// 实现call方法
Function.prototype.myCall = function(obj) {
    const args = [...arguments].splice(1);   //拿到除了第一个参数外的所有参数,并使其变成数组。
    obj.temp = this;  // 这里的this其实就是调用mycall的函数,我们把这个函数赋值给了obj的一个临时变量。
    obj.temp(...args);  // 这个时候我们执行obj.temp 函数的this就变成了了obj。
    delete obj.temp;    // 销毁函数避免作用域污染
}
  • 实现apply()
// 实现apply方法
Function.prototype.myApply = function(obj,arr) {
    obj.temp = this;
    obj.temp(...arr);
    delete obj.temp;
}
  • 手写bind()
//实现bind方法
// bind有使用有两个方式,实现起来稍微复杂一点,bind的参数可以在使用bind时传,也可以在返回的函数中传.
// 1. 
const action = test.bind(obj, 1)
action()  // 1,'zs'
// 2. 
const action = test.bind(obj)
action(1)  // 1,'zs'

Function.prototype.myBind = function(obj,arr) {
    let args = [...arguments].splice(1)   // 调用myBind时传入参数,状态1;
    const self = this;   // 保存一下这个this,为当前调用函数test;
    return function() {
        const inArgs = [...arguments]    // 返回函数的参数,状态2;
        if(arguments.length > 0) {
            args = [...args,...inArgs];    //把两次传入的值都放在这个数组里;
        }
        self.apply(obj,args);
    }
}
const action = test.myBind(obj, 1)
action()  // 1,'zs'

const action = test.myBind(obj)
action(1)  // 1,'zs'

手动实现new

// 实现new方法
function createNew() {
    var obj = {}  // 1.创建一个空对象
    let constructor = [].shift.call(arguments); // 参数第一个为constructor
    obj.__proto__ = constructor.prototype;    // 2.链接到原型
    let result = constructor.apply(obj,arguments); // 3.绑定this值
    return typeof result === 'object'?result:obj; // 4.返回新对象

}

手动实现浅拷贝、深拷贝

// 浅拷贝
let copy1 = Object.assign({},{x:1});
// 深拷贝
// 缺点:拷贝对象包含 正则表达式,函数,或者undefined等值会失败
let obj = {a:1,b:{x:3}};
let obj1 = JSON.parse(JSON.stringify(obj));
// 使用递归
function deepCopy(obj) {
    let copy = obj instanceof Array ? [] : {};
    for(let i in obj) {
        // JS hasOwnProperty()方法:检测一个属性是否是对象的自有属性
        if(obj.hasOwnProperty(i)) {
            copy[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]; 
        }
    }
    return copy;
}

手动实现节流、防抖

const throttle = function(fn,delay,isBebounce) {
    let timer;
    let lastCall = 0;
    return function(...args) {
        if(isBebounce) {
            //函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时
            if(timer) {
                clearTimeout(timer)
            }
            timer = setTimeout(function(){
                fn.apply(this,...args);
            },delay)
        } else {
            //当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
            var prev = new Date();
            if(prev - lastCall > delay) return
            lastCall = prev;
            fn.apply(this,...args);
        }
    }
}

手动实现instanceof原理

// instanceof原理:右边变量的原型存在于左边的原型链上
function instanceOf(left,right) {
    let leftValue = left.__proto__;
    let rightValue = right.prototype;
    while(true) {
        if(leftValue === null) {
            return false
        }
        if(leftValue === rightValue) {
            return true;
        }
        leftValue = leftValue.__proto__;
    }
} 

手动实现Object.create原理

// Object.create 的基本实现原理
// 思路:将传入的对象作为原型
function create(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

手动实现数据双向绑定

// 实现一个双向数据绑定
let obj ={}
let input = document.getElementById('input');
let span = document.getElementById('span');
// 数据劫持
Object.defineProperty(obj,'text', {
    configurable:true,    //configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。
    enumerable:true,      //是否可通过for-in循环,flase为不可循环,默认值为true
    get() {
        console.log('获取数据了');
    },
    set(newVal) {
        console.log('数据更新了');
        input.value = newVal;
        span.innerHTML = newVal;
        //innerText主要是设置文本的,甚至标签内容,是没有标签的效果的。innerHTML主要的作用是在标签中设置新的heml标签内容,是有标签效果的
    }
})
//输入监听
input.addEventListener('keyup',function(e) {
    obj.text = e.target.value;
})

手动实现ajax

//实现一个ajax
//方法1
var xhr = new XMLHttpRequest()
// 初始化
xhr.open(method,url,async);
//发送请求
xhr.send(data)
//设置状态变化回调处理请求结果
shr.onreadystatechange = () => {
    if(xhr.readyStatus === 4 && xhr.status === 200) {
        console.log(xhr.responseText);
    }
}

// 方法二基于promise实现
function ajax(options) {
    const url = options.url;
    const method = options.methods.toLocaleLowerCase() || 'get';
    const async = options.async;    // 默认异步
    const data = options.data;
    const xhr = new XMLHttpRequest();   //实例化
    if(options.timeout && options.timeout > 0) {
        xhr.timeout = options.timeout;
    }
    //返回一个promise实例
    return new Promise((resolve,reject) => {
        xhr.ontimeout = () => reject && reject('请求超时');
        xhr.onreadystatechange =()=>{
            if(xhr.readyState == 4) {
                // 200-300 之间表示请求成功,304资源未变,取缓存
                if(xhr.status >=200 && xhr.status < 300 || xhr.status === 304) {
                    resolve && resolve(xhr.responseText);
                } else {
                    reject && reject()
                }
            }
        }
        xhr.onerror = err=> reject && reject(err);   // 错误回调
        let paramArr = []
        let encodeData
        // 处理请求参数
        if (data instanceof Object) {
        for (let key in data) {
            // 参数拼接需要通过 encodeURIComponent 进行编码
            paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
        }
        encodeData = paramArr.join('&')
        }
        // get请求拼接参数
        if (method === 'get') {
        // 检测url中是否已存在 ? 及其位置
        const index = url.indexOf('?')
        if (index === -1) url += '?'
        else if (index !== url.length -1) url += '&'
        // 拼接url
        url += encodeData
        }
        // 初始化
        xhr.open(method, url, async)
        // 发送请求
        if (method === 'get') xhr.send(null)
        else {
        // post 方式需要设置请求头
        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
        xhr.send(encodeData)
        }
    })
}

手动实现await sleep函数

//实现一个 await sleep函数延迟n秒后执行后面的语句
//一般情况下,await 后面是一个promise对象,如果不是,会被立即转成一个resolve的promise对象
function sleep(time) {
    return new Promise(resolve=>{
        setTimeout(resolve,time)
    })
}

手动实现如何渲染大数量级的数据的方法

//当面临上千万记录要显示的时候,如何处理呢?
//分页显示(分页查询)
//分批插入
//异步里面调用loop(),开启一个任务线程,避免影响js引擎或渲染线程
setTimeout(()=>{
    // 插入十万条数据
    const total = 100000;
    // 一次插入200条
    const once = 200;
    const loopCount = total / once;
    let countRender = 0;
    let ur = document.querySelector('ul');
    function add() {
        // 优化性能,插入不会引起回流
        // 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。
       const fragment = document.createDocumentFragment(); 
       for(let i =0; i < once; i++) {
           const li = document.createElement('li');
           li.innerText = `这是第${countRender}次的li${li}`
           fragment.appendChild(li);
       }
       ul.appendChild(fragment);
       countRender += 1;
       loop()
    }
    function loop() {
        while(countRender < loopCount) {
            window.requestAnimationFrame(add())
        }
    }
    loop();
},0)

手动实现jsonp

// jsonp解决跨域
// 动态创建<script>标签
// 基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了
function addScriptTag(src) {
    var script = document.createElement('script');
    script.setAttribute("type","text/javascript");
    script.src = src;
    document.body.appendChild(script);
}
window.onload = function() {
    // ?callback=foo 返回一个回调函数
    addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
    console.log('response data:' + JSON.stringify(data));
}

// 服务器端会在返回的参数外面加一个对应的函数包裹层
foo({
  "test": "testData"
});  

手写add(1)(2,3)(5)(柯里化实现)

function add() {
    var args = Array.prototype.slice.call(arguments); 
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);  
        if(arguments.length == 0) {
            var sum = 0;
            for(var i in args) {
                sum += args[i];
            }
            return sum;
        } else {
           var finalArgs = args.concat(innerArgs);    
           return add.apply(this, finalArgs)
        } 
    }
};
console.log(add(1)(2,3)(5)()); // 11

如何柯里化一个函数?

function curry(func) {
    return function curried(...args) {
        // 参数够了,执行本来的func
        if(args.length >= func.length) {
            return func.apply(this,args)
        } else {
            // 参数不够,就‘保存’下参数,返回一个新的function。
            return curried.bind(this,...args)
        }
    }
}

手写Promise.all()

// Promise.all()
// 返回一个Promise,当所有的promise fulfill的时候结果为fulfill;当任意一个promise reject 结果reject。
function myPromiesAll(promises) {
    // 当输入的promises中含有一些非Promise数据时(Promise.all()支持传入非Promise数据),我们需要将非Promise数据转化为Promise数据。
    const _promises = promises.map((item)=>item instanceof Promise?item:Promise.resolve(item));
    if(_promises.length === 0){
        return Promise.resolve([]);
    }
    return new Promise((resolve,reject)=>{
        // fulfill的数据需要放再一个数组里,但是promise的fulfill时机未知,先后顺序不定,所以不能用push存数据,而是利用index来放置数据到正确的位置。
        // 但是这样做过后,存放的数据的数组不能通过length来判断是否所有的promise都已经fulfill了,因此需要一个变量fulfilledCount来计数。
        const result = [];
        let fulfilledCount = 0;
        let isErrored = false;
        _promises.forEach((promise,index)=>{
            promise.then((value) => {
                //
                if(isErrored) return
                result[index] = value;
                fulfilledCount+=1;
                if(fulfilledCount == _promises.length) {
                    resolve(result);
                }
            },(error)=>{     
                // 处理reject的时候直接reject promise即可。单是这里需要注意的是,reject之后,依然可能有promise继续fulfill或者reject,我们需要避免这些情况的发生,因此使用了isErrored;
                if(isErrored) return
                isErrored = true;
                reject(error)
            })
        })
    })
}

学习于奔跑的瓜牛:JS高频手写代码
什么是柯里化?看下这个哦:函数绑定与函数柯里化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值