标签:超过 有一个 构造函数 window 多个参数 win 长度 保留 contex
new
用构造函数创建实例对象,为实例对象添加this属性和方法。
new
在调用过程中实现了以下几个步骤:
1 function createNew() { 2 // 创建一个空的对象 3 var obj = new Object(); 4 // 获得构造函数,arguments中去除第一个参数 5 var Con = [].shift.call(arguments); 6 // 链接到原型,obj 可以访问到构造函数原型中的属性 7 obj.__proto__ = Con.prototype; 8 // 绑定 this 实现继承,obj 可以访问到构造函数中的属性 9 var res = Con.apply(obj, arguments); 10 // 优先返回构造函数返回的对象,判断下返回的值是不是一个对象,如果是对象则返回这个对象,不然返回新创建的 obj对象。 11 return res instanceof Object ? res : obj; 12 }; 13 复制代码
1 instanceof用于判断对象的具体类型。它依靠原型链向上查找,遍历左侧变量的原型链,查看右侧变量的 prototype是否在左边的原型链上。如果查找失败,返回false。 2 3 function creatInstanceof(left, right) { 4 //如果不是object或者为null,直接返回false 5 if (typeof(left)!== ‘object‘ || left === null) return false; 6 // 取左表达式的__proto__值 7 let left = left.__proto__; 8 while (true) { 9 if (left === null || left === undefined) 10 return false; 11 //判断右表达式的 prototype 值是否和左表达式的__proto__值相等 12 if (right.prototype === left) 13 return true; 14 //往下走 15 left = left.__proto__; 16 } 17 } 18 复制代码
节流
主要用来稀释函数的执行次数,当持续触发事件时,让函数在特定的时间内只执行一次。例如:假设我们设定延时时间为1000ms,有一个函数A一直在被调用,我们使用节流
,就可以让它1000ms执行一次,而不是一直在执行。
1 function throttle(fn, delay = 1000) { 2 // 定义开始时间 3 var startTime = 0; 4 return function () { 5 //获取当前时间 6 var nowTime = Date.now(); 7 //如果当前时间减去开始时间大于约定的延时时间,则执行 8 if (nowTime - startTime > delay) { 9 //执行函数,同时改变this当前指向 10 fn.call(this); 11 //更新开始时间的值 12 startTime = nowTime; 13 } 14 } 15 } 16 17 //应用:滑动鼠标输出12345(如果一直滑动的话,会1000ms输出一次) 18 document.onmousemove = throttle(() =>{ 19 console.log(‘12345‘); 20 },1000) 21 复制代码
防抖
是函数在特定的时间内不被调用后再执行。例如:假设我们设定延时时间为1000ms,有一个按钮,点击这个按钮生成随机数。我们使用防抖
,点击按钮之后的1000ms内,按钮没有被再次点击,才会生成随机数,如果在1000ms内按钮被再次点击,那么它会重新计时,不会生成随机数。(搜索框的联想功能也会应用到防抖
思想)
1 const debounce = function(fn,delay){ 2 //定义一个timer 3 var timer = null; 4 return function(){ 5 //如果函数执行了,那么清除定时器 6 clearTimeout(timer) 7 //应用定时器计时,超过延时时间,执行函数 8 timer = setTimeout(() =>{ 9 //执行函数,同时改变this指向 10 fn.call(this); 11 },delay) 12 } 13 } 14 15 //举例中的应用:点击按钮输出12345。 16 obtn.onclick = debounce(function(){ 17 console.log(‘12345‘); 18 },1000) 19 复制代码
针对像Object, Array 这样的复杂对象的。简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。
1 let newObj1 = JSON.parse(JSON.stringfy(obj)); 2 复制代码
该方法有一些局限性:
1 const deepClone = function (obj) { 2 // 判断是否为对象,不是的话返回 3 if (typeof obj !== ‘object‘) return; 4 // 判断是数组还是对象 5 let newObj = obj instanceof Array ? [] : {}; 6 //循环这个对象 7 for (var key in obj) { 8 //判断对象自身属性是否有这个key 9 if (obj.hasOwnProperty(key)) { 10 //如果obj[key]为对象的话,递归。否则直接赋值即可。 11 newObj[key] = typeof obj[key] === ‘object‘ ? deepClone(obj[key]) : obj[key]; 12 } 13 } 14 return newObj; 15 } 16 复制代码
1 call用来改变this的指向。使用:fn.call(obj, arg1, arg2, ...) 2 3 Function.prototype.myCall = function (context) { 4 // 判断如果不是函数的话,报出错误 5 if (typeof this !== ‘function‘) { 6 throw new TypeError(‘Error‘); 7 } 8 // context为null或undefined的话会被全局对象代替 9 context = context || window; 10 // 获取调用call的函数(就是this)把它作为context的一个属性 11 context.fn = this; 12 // 获取剩余参数 13 const args = [...arguments].slice(1); 14 // 执行 15 const result = context.fn(...args); 16 // 执行后删除即可 17 delete context.fn; 18 // 返回结果 19 return result; 20 } 21 复制代码
apply
用来改变this的指向,和call的区别是传递的参数格式不同,apply
接受数组作为参数。
1 使用:fn.apply(obj, [arg1, arg2, ...]) 2 3 Function.prototype.myApply = function (context) { 4 if (typeof this !== ‘function‘) { 5 throw new TypeError(‘Error‘); 6 } 7 context = context || window; 8 context.fn = this; 9 let result; 10 // 参数处理和 call 有区别,其余基本一致 11 if (arguments[1]) { 12 result = context.fn(...arguments[1]); 13 } else { 14 result = context.fn(); 15 } 16 delete context.fn; 17 return result; 18 } 19 复制代码
注:上面实现的call和apply,当我们被问到的时候,通常可以直接这么写。假如写完之后,面试官问你,如果context里面原来就有fn属性,你该怎么办? 由于我们最后一步delete context.fn;
将我们建的fn删除了,如果它原来存在的话,那么也就被我们删除啦。我们可以考虑复制一个新的context2,而不是直接改context,或者先保留旧的context.fn的值,执行完逻辑之后再将它还原,这样的话就可以解决这个问题了。(有把握的话也可以直接说哦~)
bind
用来改变this指向,和call、apply的区别是bind
是绑定,执行需要再次调用。
1 使用:fn.bind(obj, arg1, arg2, ...)()
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
1 Function.prototype.myBind = function (context) { 2 if (typeof this !== ‘function‘) { 3 throw new TypeError(‘Error‘); 4 } 5 const _this = this; 6 // 获取当前的参数 7 const args = [...arguments].slice(1); 8 // 返回一个函数 9 return function F() { 10 // 因为返回了一个函数,我们可以 new F(),所以需要判断 11 if (this instanceof F) { 12 return new _this(...args, ...arguments); 13 } 14 // 利用apply改变this指向,同时拼接返回的这个函数中的参数 15 return _this.apply(context, args.concat(...arguments)); 16 } 17 } 18 复制代码
Promise.all
方法接受一个数组,当数组中所有的promise请求成功之后,会走到.then的方法里面。如果中间哪一个promise失败了,那么promise.all
就会直接走到.catch的方法里面。
1 使用:Promise.all([p1,p2,p3,···]).then(function(){}).catch(function(){}) 2 3 Promise.prototype.all = function (promiseAry = []) { 4 // 定义一个index,用于计数。 5 let index = 0; 6 // 定义空数组,用于保存结果 7 let result = []; 8 // 返回新的Promise 9 return new Promise((resolve, reject) => { 10 // 循环传入的promise数组 11 for (let i = 0; i < promiseAry.length; i++) { 12 // 执行成功走到then 13 promiseAry[i].then(val => { 14 // 成功,index+1 15 index++; 16 // 存入对应结果 17 result[i] = val; 18 // 当所有函数都正确执行了,resolve输出所有返回结果 19 if (index === promiseAry.length) { 20 resolve(result) 21 } 22 }, reject) 23 } 24 }) 25 } 26 复制代码
Promise.race
方法接受一个数组,只要请求最快的promise请求成功,那么就会直接走到then的方法里面,即使后面有请求失败的promise不用管。同理,只要请求最快的promise请求失败。那么promise.race就直接走到catch啦。
1 使用:Promise.all([p1,p2,p3,···]).then(function(){}).catch(function(){}) 2 3 Promise.prototype.race = function (promiseAry) { 4 // 返回一个promise 5 return new Promise((resolve, reject) => { 6 // 如果数组长度为0,直接返回 7 if (promiseAry.length === 0) { 8 return; 9 } else { 10 // 循环数组 11 for (let i = 0; i < promiseAry.length; i++) { 12 // 最快的那个成功的话走到then,失败走到后面 13 promiseAry[i].then(val => { 14 // resolve输出对应结果 15 resolve(result); 16 return; 17 }, reject) 18 } 19 } 20 }) 21 } 22 复制代码
Promise.finally
不管 Promise 对象最后状态如何,都会执行finally方法指定的回调函数。
1 Promise.prototype.finally = function (callback) { 2 let P = this.constructor; 3 return this.then( 4 // onFulfilled 5 value => P.resolve(callback()).then(() => value), 6 // onRejected 7 reason => P.resolve(callback()).then(() => { 8 throw reason 9 }) 10 ); 11 }; 12 复制代码
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。(来自于百度百科)
简单点来说就是把一个多参数的函数(fn)作为参数,传递到柯里化的这个函数中(curry),运行后能够返回一个新的函数。这个新的函数能够继续处理这个函数(fn)的剩余参数。
1 function curry(fn, args) { 2 // 获取函数需要的参数长度 3 let length = fn.length; 4 args = args || []; 5 return function () { 6 let subArgs = args.slice(0); 7 // 拼接得到现有的所有参数 8 subArgs = subArgs.concat(Array.prototype.slice.call(arguments)) 9 // 判断参数的长度是否已经满足函数所需参数的长度 10 if (subArgs.length >= length) { 11 // 如果满足,执行函数 12 return fn.apply(this, subArgs); 13 } else { 14 // 如果不满足,递归返回科里化的函数,等待参数的传入 15 return curry.call(this, fn, subArgs); 16 } 17 }; 18 } 19 20 //应用: 21 function add(a, b, c) { 22 return a + b + c; 23 } 24 var _add = curry(add) 25 console.log(_add(1, 2, 3)) //6 26 console.log(_add(1)(2)(3)) //6 27 console.log(_add(1, 2)(3)) //6 28 复制代码
深度优先遍历算法(DFS)
,是从根节点开始,沿着树的深度遍历树的节点。简单来说,首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边开始走未访问过的顶点。当没有未访问过的顶点时,则回到上一个顶点,继续重复以上过程,直至所有的顶点都被访问,算法中止。
1 let deepTraversal = (node) => { 2 // 定义空数组,用于存储节点 3 let nodes = []; 4 // 当节点不为空时 5 if (node !== null) { 6 // 将当前节点push进数组中 7 nodes.push(node); 8 // 取出当前节点的孩子节点 9 let children = node.children; 10 // 循环所有的孩子节点 11 if (children) { 12 for (let i = 0; i < children.length; i++) { 13 // 递归调用并将结果进行拼接 14 nodes = nodes.concat(deepTraversal(children[i])); 15 } 16 } 17 } 18 // 返回结果 19 return nodes 20 } 21 复制代码
1 let deepTraversal = function (node) { 2 // 定义保存结果数组nodes,以及辅助数组stack(栈) 3 let stack = []; 4 let nodes = []; 5 if (node) { 6 // 推入当前处理的node 7 stack.push(node); 8 while (stack.length) { 9 // 将最后一个弹出 10 let item = stack.pop(); 11 // 取出他的孩子节点 12 let children = item.children; 13 // 将这个节点push进结果数组 14 nodes.push(item); 15 // 将孩子节点倒过来push进辅助栈中。例如当前节点有两个孩子,children1和children2 16 // 那么stack里面为[children2,children1],这样pop()的时候children1会先弹出, 17 // 进而children1会先被push进nodes,先遍历children1的孩子节点(以此类推) 18 if (children) { 19 for (let i = children.length - 1; i >= 0; i--) { 20 stack.push(children[i]); 21 } 22 } 23 } 24 } 25 // 返回结果数组 26 return nodes; 27 } 28 复制代码
广度优先遍历算法(BFS)
,是从根节点开始,沿着树的宽度遍历树的节点(一层一层的遍历)。如果所有节点均被访问,则算法中止。
1 let widthTraversal = (node) => { 2 // 定义保存结果数组nodes,以及辅助数组queue(队列) 3 let nodes = []; 4 let queue = []; 5 if (node) { 6 // 将节点push进队列中 7 queue.push(node); 8 // 当队列长度不为0时循环 9 while (queue.length) { 10 // 将值从头部弹出 11 let item = queue.shift(); 12 // 取出当前节点的孩子节点 13 let children = item.children; 14 // 将当前节点push进结果数组 15 nodes.push(item); 16 // 将孩子节点顺次push进辅助队列中。例如当前节点有两个孩子,children1和children2 17 // 那么queue里面为[children1,children2],这样shift()的时候children1会先弹出, 18 // 进而children1会先被push进nodes,children1的孩子节点会顺次push进queue中 [child2,child1-1](以此类推) 19 if (children) { 20 for (let i = 0; i < children.length; i++) { 21 queue.push(children[i]); 22 } 23 } 24 } 25 } 26 return nodes; 27 } 28 复制代码
发布订阅模式
是比较常问的设计模式之一,实现一般分为两步,一是注册也就是添加订阅(添加监听事件及对应的方法),二是进行激活也就是发布消息(根据传入的事件类型触发相应的方法)。
1 let event = { 2 // 存放订阅事件 3 childrenList: {}, 4 //订阅函数 5 listen(key, fn) { 6 //如果chilidrenlist里这个缓存不存在,就先将它创建为空,为后续做准备 7 if (!this.childrenList[key]) { 8 this.childrenList[key] = []; 9 } 10 // 判断传进来的是否是一个函数,若是就加到childrenList[key]下的数组中等待执行 11 if (typeof fn == ‘function‘) { 12 this.childrenList[key].push(fn); 13 } 14 }, 15 // 发布函数 16 touch(key) { 17 // 取出对应key中的函数 18 let fns = this.childrenList[key]; 19 // 判断如果不存在直接返回 20 if (!fns && fns.length === 0) { 21 return false; 22 } 23 // 循环出每一个函数,进行执行 24 fns.forEach(fn => { 25 fn.apply(this, [arguments]); 26 }); 27 }, 28 // 删除订阅函数 29 remove(key, fn) { 30 // 取出该类型对应的消息集合 31 var fns = this.childrenList[key]; 32 if (!fns) { 33 return false; 34 } 35 // 如果没有传入具体的fn,就表示需要取消所有订阅 36 if (!fn) { 37 fns && (fns.length = 0); 38 } else { 39 // 将函数循环取出 40 for (var i = 0; i < fns.length; i++) { 41 if (fn === fns[i]) { 42 // 删除订阅者的这个回掉 43 fns.splice(i, 1); 44 } 45 } 46 } 47 } 48 } 49 50 // 应用: 51 event.listen(‘zs‘, arguments => { 52 console.log(`${arguments[0]},${arguments[1]}`) 53 }) 54 event.listen(‘lisi‘, arguments => { 55 console.log(`${arguments[0]},${arguments[1]}`) 56 }) 57 event.touch(‘zs‘, ‘收到啦面试邀请‘); 58 event.touch(‘lisi‘, ‘接到啦offer‘); 59 复制代码
数组的扁平化主要是将多维数组转化为一维数组。平常的写法可以直接调用数组的Api:var newArr = arr.flat(Infinity);
。下面用原生简单实现:
1 const getFlat = function (arr) { 2 // 循环数组,当发现里面元素包含数组时 3 while (arr.some(item => Array.isArray(item))) { 4 // 用扩展运算符取出元素,concat进行拼接 5 arr = [].concat(...arr); 6 } 7 return arr; 8 } 9 10 //应用 11 let arr = [1, 2, [3, 4, 5, [6, 7], 8], [9, [10]]]; 12 console.log(getFlat(arr)) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 13 复制代码
二分法查找
,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。
实现思路:先取出中间值,如果和目标值相等则直接返回索引。否则比较目标值和中间值的大小,进而在数组大于或小于中间值的那一半区域查找,重复上述操作。
1 function search(arr, key) { 2 // 初始化low和height 3 var low = 0; 4 var height = arr.length - 1; 5 var mid; 6 while (low <= height) { 7 // (小索引+大索引)除以2,向下取整找到中间值 8 mid = Math.floor((low + height) / 2); 9 // 如果当前索引为中间值的数值刚好和这个目标值想等 10 if (arr[mid] == key) { 11 // 返回目标元素的索引值 12 return mid; 13 } else if (arr[mid] < key) {//如果当前索引为中间值的数值小于这个目标值 14 // 将中间值+1赋值给low 15 low = mid + 1; 16 } else {// 如果当前索引为中间值的数值大于这个目标值 17 // 将中间值-1赋值给height 18 height = mid - 1; 19 } 20 } 21 // 没有查到,返回-1 22 return -1; 23 } 24 复制代码
1 function search(arr, low, height, key) { 2 // 递归出口(没找到返回-1) 3 if (low > height) { 4 return -1; 5 } 6 // (小索引+大索引)除以2,向下取整找到中间值 7 var mid = Math.floor((low + height) / 2); 8 // 如果当前索引为中间值的数值刚好和这个目标值想等 9 if (arr[mid] == key) { 10 // 返回目标元素的索引值 11 return mid; 12 } else if (arr[mid] < key) { // 如果当前索引为中间值的数值小于这个目标值 13 // 将中间值+1赋值给low 14 low = mid + 1; 15 // 递归调用 16 return search(arr, low, height, key); 17 } else { // 如果当前索引为中间值的数值大于这个目标值 18 // 将中间值-1赋值给height 19 height = mid - 1; 20 // 递归调用 21 return search(arr, low, height, key); 22 } 23 }
标签:超过 有一个 构造函数 window 多个参数 win 长度 保留 contex
原文地址:https://www.cnblogs.com/ajaxlu/p/13800873.html