当前位置:首页 » 《关注互联网》 » 正文

JavaScript进阶:手写代码挑战(一)

1 人参与  2024年10月28日 16:00  分类 : 《关注互联网》  评论

点击全文阅读


​?个人主页:前端青山
?系列专栏:JavaScript篇
?人终将被年少不可得之物困其一生

依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript手写代码篇

#1024程序员节|征文#

在现代Web开发中,JavaScript 是不可或缺的编程语言。掌握其核心功能和原理对于开发者至关重要。本文通过手写实现JavaScript的一些关键功能和算法,帮助读者深入理解其工作原理,提升编程技能。无论你是初学者还是有经验的开发者,都能从中受益。

目录

1、手写一个Promise

2、手写call,apply,bind实现详解

4、手写一个防抖的实现

5、手写一个节流的实现

6、手写ajax  

7、手写JSONP的原理和实现

8、手写深拷贝

9、bind

10 call

11 apply


1、手写一个Promise

/** * 自定义Promise函数模块 */(function () {​    const PENDING = 'pending'    const RESOLVED = 'resolved'    const REJECTED = 'rejected'​​    /**     * Promise构造函数     * @param {*} executor 执行器函数     */    function Promise(executor) {        const self = this        // 给promise对象指定status属性,初始值为pending        self.status = PENDING        // 给promise对象指定一个用于存储结果数据的属性        self.data = undefined        // 每个元素的结构 { onResolved(){}, onRejected() }        self.callbacks = []​​        function resolve(value) {            // 此处做判断,使得promise的状态只能修改一次            if (self.status === PENDING) {                // 将状态改为 resolved                self.status = RESOLVED                // 保存value数据                self.data = value​                // 如果有待执行的callback函数,立即异步执行回调                if (self.callbacks.length > 0) {                    setTimeout(() => { // 表示在异步队列中执行                        self.callbacks.forEach(callbacksObj => {                            callbacksObj.onResolved(value)                        })                    }, 0);                }            }        }        function reject(reason) {            // 此处做判断,使得promise的状态只能修改一次            if (self.status === PENDING) {                // 将状态改为 resolved                self.status = REJECTED                // 保存value数据                self.data = reason​                // 如果有待执行的callback函数,立即异步执行回调                if (self.callbacks.length > 0) {                    setTimeout(() => { // 表示在异步队列中执行                        self.callbacks.forEach(callbacksObj => {                            callbacksObj.onRejected(reason)                        })                    }, 0);                }            }        }​        // 立即执行器        try {            executor(resolve, reject);        } catch (error) { // 如果执行器抛出异常,promise状态变为rejected状态            reject(error)        }    }​    /**     * Promise原型对象的then方法     */    Promise.prototype.then = function (onResolved, onRejected) {        const self = this        onResolved = typeof onResolved === 'function' ? onResolved : value => value        // 指定默认的失败的回调(实现错误/异常穿透的关键点)        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }​​        return new Promise((resolve, reject) => {​            /**             * 处理onResolve和onRejected函数             * @param {*} callback              */            function resolvePromise(callback) {                try {                    const result = callback(self.data)                    if (result instanceof Promise) {                        result.then(resolve, reject)                    } else if (result !== null && (typeof result === 'object' || typeof result === 'function')) {                        // 拿到result.then                        const then = result.then;                        if (typeof then === 'function') {                            then(resolve, reject)                        } else {                            resolve(then)                        }                    } else {                        resolve(result)                    }                } catch (error) {                    reject(error)                }            }​​            if (self.status === RESOLVED) {                setTimeout(() => {                    /**                     * 1. 如果抛出异常,return 的 promise就会失败,reason就是error                     * 2. 如果回调函数返回不是promise,return的promise就会成功,value就是返回值                     * 3. 如果回调函数返回的是一个promise,return的promise的结果就是这个promise的结果,value就是返回值                     */                    resolvePromise(onResolved)                }, 0);            } else if (self.status === REJECTED) {                setTimeout(() => {                    resolvePromise(onRejected)                }, 0);            } else {                self.callbacks.push({                    onResolved(value) {                        resolvePromise(onResolved)                    },                    onRejected(reason) {                        resolvePromise(onRejected)                    }                })            }        })    }​    /**     * Promise原型对象的catch方法     */    Promise.prototype.catch = function (onRejected) {        return this.then(undefined, onRejected)    }​    /**     * Promise函数对象的resolve方法     *      * 返回一个指定结果的成功的promise     */    Promise.resolve = function (value) {        return new Promise((resolve, reject) => {            if (value instanceof Promise) {                value.then(resolve, reject)            } else {                resolve(value)            }        })    }​    /**     * Promise函数对象的reject方法     */    Promise.reject = function (reason) {        return new Promise((resolve, reject) => {            reject(reason)        })    }​    /**     * Promise函数对象的all方法     */    Promise.all = function (promises) {        // 判断数组        if (!(promises instanceof Array)) {            return Promise.reject(new Error('params must be a Array!'))        }        // 用来保存所有数据成功value的数组        const resultArray = new Array(promises.length)        // 用来保存成功数量的计数        let resultCount = 0        return new Promise((resolve, reject) => {            // 遍历获取每个promise的结果            promises.forEach((p, index) => {                Promise.resolve(p).then(value => {                    resultCount++                    resultArray[index] = value                    // 如果全部都成功了,将return的promise变为成功                    if (resultCount === promises.length) {                        // 表示成功                        resolve(resultArray)                    }                }, reason => {                    reject(reason)                })            })        })    }​    /**     * Promise函数对象的race方法     */    Promise.race = function (promises) {        // 判断数组        if (!(promises instanceof Array)) {            return Promise.reject(new Error('params must be a Array!'))        }​        return new Promise((resolve, reject) => {            // 遍历数组            promises.forEach((p, index) => {                Promise.resolve(p).then(value => {                    resolve(value)                }, reason => {                    reject(reason)                })            })        })    }​    // ---------------------------------------实现自己的方法---------------------------------------    /**     * 返回一个promise对象,它在指定的时间后才确定成功结果     * @param {*} value      * @param {*} time      */    Promise.resolveDelay = function (value, time) {        return new Promise((resolve, reject) => {            setTimeout(() => {                if (value instanceof Promise) {                    value.then(resolve, reject)                } else {                    resolve(value)                }            }, time);        })    }​​    /**     * 返回一个promise对象,它在指定的时间后才确定失败结果     * @param {*} reason      * @param {*} time      */    Promise.rejectDelay = function (reason, time) {        return new Promise((resolve, reject) => {            setTimeout(() => {                reject(reason)            }, time);        })    }​​​    // 向外暴露函数    window.Promise = Promise})(window)

2、手写call,apply,bind实现详解

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。​        let obj = {            name: "一个"        }         function allName(firstName, lastName) {            console.log(this)            console.log(`我的全名是“${firstName}${this.name}${lastName}”`)        }        // 很明显此时allName函数是没有name属性的        allName('我是', '前端') //我的全名是“我是前端”  this指向window        allName.call(obj, '我是', '前端') //我的全名是“我是一个前端” this指向obj复制代码applyapply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。​allName.apply(obj, ['我是', '前端'])//我的全名是“我是一个前端” this指向obj复制代码bindbind 接收多个参数,第一个是bind返回值返回值是一个函数上下文的this,不会立即执行。​        let obj = {            name: "一个"        }         function allName(firstName, lastName, flag) {            console.log(this)            console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右铭是"${flag}"`)        }        allName.bind(obj) //不会执行        let fn = allName.bind(obj)        fn('我是', '前端', '好好学习天天向上')         // 也可以这样用,参数可以分开传。bind后的函数参数默认排列在原函数参数后边        fn = allName.bind(obj, "你是")        fn('前端', '好好学习天天向上')复制代码接下来搓搓手实现call、apply和bind​实现call      let Person = {            name: 'Tom',            say() {                console.log(this)                console.log(`我叫${this.name}`)            }        }         // 先看代码执行效果        Person.say() //我叫Tom         Person1 = {            name: 'Tom1'        }         // 我们尝试用原生方法call来实现this指向Person1        Person.say.call(Person1) //我叫Tom1 复制代码通过第一次打印执行和第二次打印执行我发现,如果Person1有say方法那么Person1直接执行Person1.say() 结果就是我是Tom1,是的call就是这么实现的。 再看代码​        Function.prototype.MyCall = function(context) {            //context就是demo中的Person1            // 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样this            console.log(this)            context.say = this //Mycall里边的this就是我们虚拟的say方法            context.say()        }        // 测试        Person.say.MyCall(Person1)//我叫Tom1复制代码 perfect!爆棚的满足感!不过拿脚趾头想想也不会这么简单,继续完善 我们自己找茬 1、call支持多个参数,有可能一个也不没有 2、考虑多参数时要把参数传给扩展方法。 3、给上下文定义的函数要保持唯一不能是say 4、扩展完我们需要吧自定义函数删除 接下来针对找茬问题一一解决        let Person = {            name: 'Tom',            say() {                console.log(this)                console.log(`我叫${this.name}`)            }        }        Person1 = {            name: 'Tom1'        }        //如果没有参数        Person.say.call()复制代码没有指定this,this指向window我们也要这样​        Function.prototype.MyCall = function(context) {            // 如果没有参数我们参考call的处理方式            context = context || window                //context就是demo中的Person1                // 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样this            context.say = this //Mycall里边的this就是我们虚拟的say方法            context.say()        }         Person.say.MyCall()复制代码 没毛病! 继续解决// 找茬2:我们默认定义context.say = this  fn如果已经被占用 嘎嘎 sb了。 不怕 搞定它         // say需要是一个唯一值 是不是突然想到es6的新类型 Symbol   fn = Symbol() 不过我们装逼不嫌事大 都说自己实现了         function mySymbol(obj) {            // 不要问我为什么这么写,我也不知道就感觉这样nb            let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)                // 牛逼也要严谨            if (obj.hasOwnProperty(unique)) {                return mySymbol(obj) //递归调用            } else {                return unique            }        }//接下来我们一并把多参数和执行完删除自定义方法删除掉一块搞定        Function.prototype.myCall1 = function(context) {            // 如果没有传或传的值为空对象 context指向window            context = context || window            let fn = mySymbol(context)            context.fn = this //给context添加一个方法 指向this            // 处理参数 去除第一个参数this 其它传入fn函数            let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组            context.fn(...arg) //执行fn            delete context.fn //删除方法        }                let Person = {            name: 'Tom',            say(age) {                console.log(this)                console.log(`我叫${this.name}我今年${age}`)            }        }         Person1 = {            name: 'Tom1'        }         Person.say.call(Person1,18)//我叫Tom1我今年18复制代码测试结果相当完美!​实现apply接下来apply就简单多了,只有多参数时第二个参数是数组,就不一步步细说了。​        Function.prototype.myApply = function(context) {            // 如果没有传或传的值为空对象 context指向window            if (typeof context === "undefined" || context === null) {                context = window            }            let fn = mySymbol(context)            context.fn = this //给context添加一个方法 指向this                // 处理参数 去除第一个参数this 其它传入fn函数            let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组            context.fn(arg) //执行fn            delete context.fn //删除方法         }复制代码实现bind这个和call、apply区别还是很大的,容我去抽根烟回来收拾它 还是老套路先分析bind都能干些什么,有什么特点 1、函数调用,改变this 2、返回一个绑定this的函数 3、接收多个参数 4、支持柯里化形式传参 fn(1)(2)​       Function.prototype.bind = function(context) {            //返回一个绑定this的函数,我们需要在此保存this            let self = this                // 可以支持柯里化传参,保存参数            let arg = [...arguments].slice(1)                // 返回一个函数            return function() {                //同样因为支持柯里化形式传参我们需要再次获取存储参数                let newArg = [...arguments]                console.log(newArg)                    // 返回函数绑定this,传入两次保存的参数                    //考虑返回函数有返回值做了return                return self.apply(context, arg.concat(newArg))            }        }         // 搞定测试        let fn = Person.say.bind(Person1)        fn()        fn(18)

3、手写一个instanceOf

实现思路:首先 instanceof 左侧必须是对象, 才能找到它的原型链​instanceof 右侧必须是函数, 函数才会prototype属性​迭代 , 左侧对象的原型不等于右侧的 prototype时, 沿着原型链重新赋值左侧​ ​复制代码// [1,2,3] instanceof Array ---- true​// L instanceof R// 变量R的原型 存在于 变量L的原型链上function instance_of(L,R){        // 验证如果为基本数据类型,就直接返回false    const baseType = ['string', 'number','boolean','undefined','symbol']    if(baseType.includes(typeof(L))) { return false }        let RP  = R.prototype;  //取 R 的显示原型    L = L.__proto__;       //取 L 的隐式原型    while(true){           // 无线循环的写法(也可以使 for(;;) )        if(L === null){    //找到最顶层            return false;        }        if(L === RP){       //严格相等            return true;        }        L = L.__proto__;  //没找到继续向上一层原型链查找    }}

4、手写一个防抖的实现

!DOCTYPE html> <html><!-- 防抖 --><!-- 防抖就是在n秒内 防止连续触发,在n秒内触发了下一次,那就重新计算 --> <body>  <div id="content"    style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>  <script>    let num = 1;    let content = document.getElementById('content');    /**     *非立即执行版     **/    function debounceNoAtOnce(func, wait) {      let timeout;      return function () {        let context = this;        let args = arguments;        if (timeout) clearTimeout(timeout);        timeout = setTimeout(() => {          func.apply(context, args)        }, wait);      }    };     /**     *立即执行版     **/    function debounceAtOnce(func, wait) {      let timeout;      return function () {        let context = this;        let args = arguments;        debugger        if (timeout) clearTimeout(timeout);         let callNow = !timeout;        timeout = setTimeout(() => {          timeout = null;        }, wait)         if (callNow) func.apply(context, args)      }    }    /**     *聚合版     **/    function debounce(func, wait, immediate) {      let timeout;       return function () {        let context = this;        let args = arguments;         if (timeout) clearTimeout(timeout);        if (immediate) {          var callNow = !timeout;          timeout = setTimeout(() => {            timeout = null;          }, wait)          if (callNow) func.apply(context, args)        } else {          timeout = setTimeout(function () {            func.apply(context, args)          }, wait);        }      }    }     function count() {      content.innerHTML = num++;    };    content.onmousemove = debounce(count, 1000, true);  </script></body><script></script> </html>防抖的目的在于:n秒内点击多少次都算一次+每次点击都重新计算时间

5、手写一个节流的实现

<!DOCTYPE html> <html><!-- 节流 --><!-- 节流是为了固定的时间段内只能点击一次 --> <body>  <div id="content"    style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>  <script>    let num = 1;    let content = document.getElementById('content');    /**     *throttleTime 时间戳版     **/    throttleTime = function (func, wait) {      let previde = 0;      return function () {        let nowDate = Date.now();        if (nowDate - previde > wait) {          func();          previde = nowDate;        }      }    }    /**     *throttleSet 定时器版     **/    throttleSet = function (func, wait) {      let timeout;      return function () {        if (!timeout) {          timeout = setTimeout(() => {            timeout = false            func()          }, wait)        }      }    }     function count() {      content.innerHTML = num++;    };    content.onmousemove = throttleSet(count, 1000);  </script></body><script></script> </html>

节流的目的在于:固定时间段内只能点击一次

应用场景:输入框输入+提交/确定

6、手写ajax  

function createXMLHTTPRequest() {                      //1.创建XMLHttpRequest对象                      //这是XMLHttpReuquest对象无部使用中最复杂的一步                      //需要针对IE和其他类型的浏览器建立这个对象的不同方式写不同的代码                      var xmlHttpRequest;                   if (window.XMLHttpRequest) {                          //针对FireFox,Mozillar,Opera,Safari,IE7,IE8                         xmlHttpRequest = new XMLHttpRequest();                          //针对某些特定版本的mozillar浏览器的BUG进行修正                          if (xmlHttpRequest.overrideMimeType) {                              xmlHttpRequest.overrideMimeType("text/xml");                          }                      } else if (window.ActiveXObject) {                          //针对IE6,IE5.5,IE5                          //两个可以用于创建XMLHTTPRequest对象的控件名称,保存在一个js的数组中                          //排在前面的版本较新                          var activexName = [ "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];                          for ( var i = 0; i < activexName.length; i++) {                              try {                                  //取出一个控件名进行创建,如果创建成功就终止循环                                  //如果创建失败,回抛出异常,然后可以继续循环,继续尝试创建                                 xmlHttpRequest = new ActiveXObject(activexName[i]);                               if(xmlHttpRequest){                                  break;                              }                           } catch (e) {                              }                          }                      }                      return xmlHttpRequest;               }  

7、手写JSONP的原理和实现

JSONP实现原理

jsonp,其实就是单纯为了实现跨域请求而创造的一个欺骗(trick)。

虽然,因为同源策略的影响,不能通过XMLHttpRequest请求不同域上的数据(Cross -origin reads)。但是,在页面上引入不同域上的js脚本文件却是可以的(Cross -origin embedding)。因此,在js文件载入完毕之后,触发回调,可以将需要的data作为参数传入 注意,实现方式(需前后端配合)

优点

兼容性好(兼容低版本IE)

缺点

JSONP只支持GET请求,XMLHttpRequest相对于JSONP有着更好的错误处理机制。

实现

1.服务端采用的是Node,服务端处理请求方法如下

router.get('/', function(req, res, next) {​​​    console.log('收到客户端的请求:', req.query);​​​    // 传回到客户端的数据​​​    let data = JSON.stringify({​​​        'status':200,​​​        'result':{​​​            'name':'柳成荫',​​​            'site':'123456'​​​        }​​​    });​​​    // 获取方法名称 - 这是客户端传过来的方法名参数​​​    // 因为这个方法名必须是客户端有的,必须要客户端告诉服务端是哪个方法​​​    let methodName = req.query.callback;​​​    let methodStr = methodName + '(' + data + ')';​​​    // 快速结束没有任何数据的响应,客户端会执行这个方法,从而获取到服务端返回的数据​​​    res.end(methodStr)​​​});

2.封装JSONP

(function (w) {​​​        /**​​​         * jsonp的实现​​​         * @param {Object}option​​​         */​​​        function jsonp(option) {​​​            // 把success函数挂载在全局的getDate函数上​​​            w.getData = option.success;​​​            // 处理url,拼接参数 - 回调方法是getData​​​            option.url = option.url + '?callback=getData';​​​            // 创建script标签,并插入body​​​            let scriptEle = document.createElement('script');​​​            scriptEle.src = option.url;​​​            document.body.appendChild(scriptEle);​​​        }​​​        // 全局挂载一个jsonp函数​​​        w.jsonp = jsonp;​​​    })(window);​​​ ​​​    /**​​​     * 把对象转换成拼接字符串​​​     * 把形如​​​       data:{​​​          "sex":"男",​​​          "name":"九月"​​​        }​​​       转换成sex=男&name=九月​​​     * @param paramObj 对象参数​​​     * @param words​​​     * @returns {string} 字符串​​​     */​​​    function getStrWithObject(paramObj,words){​​​        let resArr = [];​​​        // 1.转换对象​​​        for(let key in paramObj){​​​            let str = key + '=' + paramObj[key];​​​            resArr.push(str);​​​        }​​​        resArr.push(words);​​​        // 3.数组转换成字符串​​​        return resArr.join("&");​​​    }

看似代码没有任何问题。但是,我们测试一下,多次调用jsonp方法。

jsonp({​​​        url:'http://localhost:3000/',​​​        data:{​​​            "sex":"男",​​​            "name":"九月"​​​        },​​​        success:function (data) {​​​            console.log(data);​​​            alert(1);​​​        }​​​    });​​​ ​​​    jsonp({​​​        url:'http://localhost:3000/',​​​        data:{​​​            "sex":"男",​​​            "name":"九月"​​​        },​​​        success:function (data) {​​​            console.log(data);​​​            alert(2);​​​        }​​​    });

结果会怎么样?的确,还是会执行2次,但是每次执行的都是第二个jsonp方法。这是为什么?

问题出现在这里,多次调用,会发生函数覆盖。

img

解决方案就是让每一次调用函数名不一致,在JS里有很多方法,比如通过随机数、时间戳之类的。这里采用的是随机数,修改如下。

// 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题​​​// 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象​​​let callBackName = 'lcy' + Math.random().toString().substr(2) ​​​+ Math.random().toString().substr(2);​​​// 把success函数挂载在全局的getDate函数上​​​w[callBackName] = option.success;​​​// 处理url,拼接参数
option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);

好,现在看似完美解决。然而,我们发现,每次调用jsonp方法,都会在body里插入一个script标签。我们并不希望在调用jsonp之后,添加这样一个标签,我们需要在调用完之后,将其移除。

怎么做呢?我们在将success函数挂载在全局时,我们在success外层再套个函数。在这个函数里调用success方法之后,即已经请求到数据之后,我们就去把这个script标签给它移除就行了。修改如下

// 1.函数挂载在全局​​​w[callBackName] = function(data){​​​    option.success(data);​​​    // 删除script标签​​​    document.body.removeChild(scriptEle);​​​};整个过程就是这样,以下是完整代码。 (function (w) {​​​        /**​​​         * jsonp的实现​​​         * @param {Object}option​​​         */​​​        function jsonp(option) {​​​            // 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题​​​            // 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象​​​            let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);​​​            // 1.函数挂载在全局​​​            w[callBackName] = function(data){​​​                option.success(data);​​​                // 删除script标签​​​                document.body.removeChild(scriptEle);​​​            };​​​            // 2.处理url​​​            option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);​​​            // 3.创建script标签插入body​​​            let scriptEle = document.createElement('script');​​​            scriptEle.src = option.url;​​​            document.body.appendChild(scriptEle);​​​        }​​​        w.jsonp = jsonp;​​​    })(window);​​​ ​​​    /**​​​     * 把对象转换成拼接字符串​​​     * @param paramObj 对象参数​​​     * @returns {string} 字符串​​​     */​​​    function getStrWithObject(paramObj,words){​​​        let resArr = [];​​​        // 1.转换对象​​​        for(let key in paramObj){​​​            let str = key + '=' + paramObj[key];​​​            resArr.push(str);​​​        }​​​        resArr.push(words);​​​        // 3.数组转换成字符串​​​        return resArr.join("&");​​​    }

8、手写深拷贝

ar a = {    name: "muyiy",    book: {        title: "You Don't Know JS",        price: "45"    },    a1: undefined,    a2: null,    a3: 123}// hasOwnProperty表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。function cloneDeep1(source) {    var target = {};    for (var key in source) {        if (source.hasOwnProperty(key)) {            if (typeof source[key] === 'object') {                target[key] = cloneDeep1(source[key]); // 注意这里            } else {                target[key] = source[key];            }        }    }    return target;}​// 使用上面测试用例测试一下console.log(a)var b = cloneDeep1(a);a.name = "前端进阶";a.book.price = "55";console.log(b);​考虑再全一些的深拷贝​​function deepClone(obj,hash = new WeakMap()){ //递归实现    if(obj instanceof RegExp) return new RegExp(obj);    if(obj instanceof Date) return new Date(obj);    if(obj === null || typeof obj != "object"){        // 普通数据类型        return obj;    }    if(hash.has(obj)){        return hash.get(obj);    }    // 下面是数组和对象的判断    let t = new obj.constructor();    hash.set(obj,t);    for(let key in obj){        //  递归        if(obj.hasOwnProperty(key)){ //是否是自身的属性            t[key] = deepClone(obj[key],hash)        }    }    return t;}

9、bind

function print() {    console.log(this.name, ...arguments);}​const obj = {    name: 'mxin',};​// Function.prototype.__bind()console.log('Function.prototype.__bind()');​// 直接调用,返回原函数拷贝,this 指向 objconst F = print.__bind(obj, 26);F(178); // mxin, 26, 178​// new 情况const _obj = new F(145); // undefined, 26, 145console.log(_obj); // print {}​// Function.prototype.bind()console.log('Function.prototype.bind()');​const Fn = print.bind(obj, 26);Fn(178); // mxin, 26, 178​const __obj = new Fn(145); // undefined, 26, 145console.log(__obj); // print {}

10 call

/** * 模拟 call * 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数 * @param {object} ctx * @param  {...any} args * @returns {any} 调用 this 的返回值,若无有返回值,则返回 undefined */Function.prototype.__call = function (ctx, ...args) {    if (typeof this !== 'function') throw new TypeError('Error');​    // 考虑 null 情况,参数默认赋值会无效    if (!ctx) ctx = window;​    // 将 this 函数保存在 ctx 上    ctx.fn = this;​    // 传参执行并保存返回值    const res = ctx.fn(...args);​    // 删除 ctx 上的 fn    delete ctx.fn;      return res;};
​// ------------------------------ 测试 ------------------------------
function Product(name, price) {    this.name = name;    this.price = price;}​// Function.prototype.__call()console.log('Function.prototype.__call()');​function Food(name, price) {    Product.__call(this, name, price);    this.category = 'food';}const food = new Food('cheese', 5);console.log(food);// Food {name: "cheese", price: 5, category: "food"}//   category: "food"//   name: "cheese"//   price: 5//   __proto__://     constructor: ƒ Food(name, price)//     __proto__: Object​// Function.prototype.call()console.log('Function.prototype.call()');​function Toy(name, price) {    Product.call(this, name, price);    this.category = 'toy';}const toy = new Toy('car', 10);console.log(toy);// Toy {name: "car", price: 10, category: "toy"}//   category: "toy"//   name: "car"//   price: 10//   __proto__://     constructor: ƒ Toy(name, price)//     __proto__: Object

11 apply

/** * 模拟 apply * 调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数 * @param {object} ctx * @param {} args */Function.prototype.__apply = function (ctx, args) {    if (typeof this !== 'function') throw new TypeError('Error');​    // 考虑 null 情况,参数默认赋值会无效    if (!ctx) ctx = window;​    // 将 this 函数保存在 ctx 上    ctx.fn = this;​    // 传参执行并保存返回值    const result = ctx.fn(...args);​    // 删除 ctx 上的 fn    delete ctx.fn;      return result;};
​// ------------------------------ 测试 ------------------------------​
const numbers = [5, 6, 2, 3, 7];​// Function.prototype.__apply()console.log('Function.prototype.__apply()');​const max = Math.max.__apply(null, numbers);console.log(max); // 7​// Function.prototype.apply()console.log('Function.prototype.apply()');const min = Math.min.apply(null, numbers);console.log(min); // 2


点击全文阅读


本文链接:http://zhangshiyu.com/post/179069.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1