码迷,mamicode.com
首页 > 移动开发 > 详细

javascript常用手写代码,new,、nstanceof、深拷贝、浅拷贝、防抖、节流、递归、call、apply、bind、Promise、函数柯里化、深度优先遍历、广度优先遍历、发布订阅模式、实现数组的扁平化、二分查找、递归

时间:2020-10-12 20:04:55      阅读:32      评论:0      收藏:0      [点我收藏+]

标签:超过   有一个   构造函数   window   多个参数   win   长度   保留   contex   

new

new用构造函数创建实例对象,为实例对象添加this属性和方法。

new在调用过程中实现了以下几个步骤:

  1. 创建一个新的对象
  2. 链接到原型,将该对象 obj 的原型链指向构造函数的原型 prototype
  3. 绑定this,让this变量指向这个新创建的对象
  4. 返回新对象
 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 复制代码

 

instanceof

 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 复制代码

 

该方法有一些局限性:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

递归写法(简版):

 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 复制代码

 

call

 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

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

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.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.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不管 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 }

 



 
 
 
引用掘金!

javascript常用手写代码,new,、nstanceof、深拷贝、浅拷贝、防抖、节流、递归、call、apply、bind、Promise、函数柯里化、深度优先遍历、广度优先遍历、发布订阅模式、实现数组的扁平化、二分查找、递归

标签:超过   有一个   构造函数   window   多个参数   win   长度   保留   contex   

原文地址:https://www.cnblogs.com/ajaxlu/p/13800873.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!