2025年3月15日 星期六 甲辰(龙)年 月十四 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > JavaScript

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

时间:02-21来源:作者:点击数:11
CDSY,CDSY.XYZ

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


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
  • 复制代码
  • apply
  • apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。
  • allName.apply(obj, ['我是', '前端'])//我的全名是“我是一个前端” this指向obj
  • 复制代码
  • bind
  • bind 接收多个参数,第一个是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()
  • 复制代码
  • 没有指定thisthis指向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 指向 obj
  • const F = print.__bind(obj, 26);
  • F(178); // mxin, 26, 178
  • // new 情况
  • const _obj = new F(145); // undefined, 26, 145
  • console.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, 145
  • console.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
CDSY,CDSY.XYZ
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐