- 实际常用
- 节流函数throttle
- 防抖函数debounce
- 深拷贝
- 简单的jquery, 考虑插件和扩展性
- 深拷贝( 破解递归爆栈 )
- 深拷贝( 破解循环引用 )
- 使用setTimeout模拟setInterval
- js实现一个继承方法
- 手写ajax
- http请求封装
- 注意一下 vue 中 mixins 的优先级,component > mixins > extends。
- 介绍下深度优先遍历和广度优先遍历,如何实现?
- 实现sleep函数(使用Promise封装setTimeout)
- 使用 setTimeout 实现 setInterval
- 判断对象是否存在循环引用
- 手写一个观察者模式
- 手写一个观察者模式
- 手写去重
- 使用闭包实现每隔一秒打印 1,2,3,4
- 手写一个 jsonp
- 实现 compose 函数
- 请用js去除字符串空格
- 反转数组
- 要求
- 要求
- 要求
- 要求
实际常用
节流函数throttle
预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。
function throttle(fn, delay = 500) {let isCall = true;return function (...args) {if (!isCall) return;isCall = false; // 不能进来了setTimeout(() => {fn.apply(this, args);isCall = true;}, delay); // 第一次不调用};}// 这种第一次会调用function throttle2(fn, delay = 500) {let oldTime = 0;return function (...args) {if (oldTime + delay <= Date.now()) {oldTime = Date.now();fn.apply(this, args);}};}
防抖函数debounce
在函数需要频繁触发时,只有当有足够空闲的时间时,才执行一次。就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。
function debounce(fn, delay = 500) {let timer;return function (...args) {if (timer) {clearTimeout(timer);}timer = setTimeout(() => {fn.apply(this, args);}, delay);};}
深拷贝
function deepClone(obj,type=1){// 如果不是Object类型或者没有值 直接原样返回if (typeof obj !== "object" || obj==null) {return obj}if(type==1){return JSON.parse(JSON.stringify(obj));}else if(type==2){// 初始化resultlet result=null;if(obj instanceof Array){// 如果是数组result=[];}else{// 不是数组那就是对象了result={};}for(let key in obj){// 判断key是否为obj自身属性值if(obj.hasOwnProperty(key)){result[key]=deepClone(obj[key],2);}}return result;}}let a = {name:'小明',age:15,address:"九龙广场",fun:["零食","羽毛球","篮球"],parent:{father:{name:"小明爸爸",age:32},mother:{name:"小明妈妈",age:34}}}let b = a;// 查看当前a和b的原始数据console.log('a',a);// 使用json方式克隆let c = deepClone(a,2);a.name="小红";console.log('a',a)console.log('c',c)console.log('修改a的父母信息');a.parent.father="小红爸爸";a.parent.mother="小红妈妈";console.log('查看b和c')console.log('b',b)console.log('c',c)
简单的jquery, 考虑插件和扩展性
class Jquery{constructor(selector){let result=null;if(selector.startsWith('.')){result=document.getElementsByClassName(selector.slice(1))}else{result = document.querySelectorAll(selector);}let length = result.length;for(let i = 0;i<length;i++){this[i]=result[i];}this.length=length;this.selector=selector;}get(index){return this[index];}each(fun){for(let i=0;i<this.length;i++){fun(this[i]);}}on(type,fun){return this.each(elem=>{elem.addEventListener(type,fun,false);})}}
深拷贝( 破解递归爆栈 )
其实破解递归爆栈的方法有两条路,第一种是消除尾递归,但在这个例子中貌似行不通,第二种方法就是干脆不用递归,改用循环
用循环遍历一棵树,需要借助一个栈,当栈为空时就遍历完了,栈里面存储下一个需要拷贝的节点
首先我们往栈里放入种子数据,
key用来存储放哪一个父元素的那一个子元素拷贝对象然后遍历当前节点下的子元素,如果是对象就放到栈里,否则直接拷贝
function cloneLoop(x) {const root = {};// 栈const loopList = [{parent: root,key: undefined,data: x,}];while(loopList.length) {// 深度优先const node = loopList.pop();const parent = node.parent;const key = node.key;const data = node.data;// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素let res = parent;if (typeof key !== 'undefined') {res = parent[key] = {};}for(let k in data) {if (data.hasOwnProperty(k)) {if (typeof data[k] === 'object') {// 下一次循环loopList.push({parent: res,key: k,data: data[k],});} else {res[k] = data[k];}}}}return root;}
深拷贝( 破解循环引用 )
引入一个数组
uniqueList用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在uniqueList中了,如果在的话就不执行拷贝逻辑了
find是抽象的一个函数,其实就是遍历uniqueList
// 保持引用关系function cloneForce(x) {// =============const uniqueList = []; // 用来去重// =============let root = {};// 循环数组const loopList = [{parent: root,key: undefined,data: x,}];while(loopList.length) {// 深度优先const node = loopList.pop();const parent = node.parent;const key = node.key;const data = node.data;// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素let res = parent;if (typeof key !== 'undefined') {res = parent[key] = {};}// =============// 数据已经存在let uniqueData = find(uniqueList, data);if (uniqueData) {parent[key] = uniqueData.target;continue; // 中断本次循环}// 数据不存在// 保存源数据,在拷贝数据中对应的引用uniqueList.push({source: data,target: res,});// =============for(let k in data) {if (data.hasOwnProperty(k)) {if (typeof data[k] === 'object') {// 下一次循环loopList.push({parent: res,key: k,data: data[k],});} else {res[k] = data[k];}}}}return root;}function find(arr, item) {for(let i = 0; i < arr.length; i++) {if (arr[i].source === item) {return arr[i];}}return null;}// 测试var b = {};var a = {a1: b, a2: b};a.a1 === a.a2 // truevar c = cloneForce(a);c.a1 === c.a2 // true
使用setTimeout模拟setInterval
function mySetInterval(cb,t){setTimeout (function () {cb()setTimeout (arguments.callee, t)}, t)}// return timer 使得定时器是可以取消的function mySetInterval(cb,t){return setTimeout (function () {cb()setTimeout (arguments.callee, t)}, t)}
js实现一个继承方法
// 借用构造函数继承实例属性function Child () {Parent.call(this)}// 寄生继承原型属性(function () {let Super = function () {}Super.prototype = Parent.prototypeChild.prototype = new Super()})()
手写ajax
//1.简单流程//实例化let xhr = XMLHttpRequest()//初始化xhr.open(method,url,async)//发送请求xhr.send(data)//设置状态变化回调处理请求结果xhr.onreadystatechange = ()=>{if(xhr.readyState === 4 && xhr.state === 200){console.log(xhr.responseText)}}//2.基于promise实现function ajax(options) {//请求地址const url = options.url,//请求方法const method = options.method.toLocaleLowerCase() || 'get'//默认为异步const async = options.async//请求参数const data = options.data//实例化const xhr = new XMLHtmlRequset()//请求超时if(options.timeout && options.timeout > 0) {xhr.timeout = options.timeout}//返回一个promise实例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//处理请求参数 {a:1,b:'1212'} 处理成 a=1&b=1212if(data instanceof Object){for(let key in data){//参数拼接需要通过 encodeURIComponent 进行编码paramArr.push(encodeURIComponent(key)+'='+encodeURIComponent(data[key]))}encodeData = paramArr.join('&')}if(method === 'get'){//检测url中是否已存在 '?' 及其位置}//初始化xhr.open(method,url,async)//发送请求if(method === 'get') xhr.send(null)else{//post需要设置请求头xhr.setRequestHeader('Content-Type','appliction/x-www-form-urlencoded;charset=UTF-8')xhr.send(encodeData)}}
http请求封装
使用
XMLHttpRequest和fetch等原生方法Content-Type 类型
application/x-www-form-urlencoded纯文本传输multipart/form-data可用于上传文件
const baseUrl = 'http://localhost:3000'export default async(url = '', data = {}, type = 'GET', method = 'fetch') => {type = type.toUpperCase();url = baseUrl + url;if (type == 'GET') {let dataStr = ''; //数据拼接字符串Object.keys(data).forEach(key => {dataStr += key + '=' + data[key] + '&';})if (dataStr !== '') {dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));url = url + '?' + dataStr;}}if (window.fetch && method == 'fetch') {let requestConfig = {credentials: 'include',method: type,headers: {'Accept': 'application/json','Content-Type': 'application/json'},mode: "cors",cache: "force-cache"}if (type == 'POST') {Object.defineProperty(requestConfig, 'body', {value: JSON.stringify(data)})}try {const response = await fetch(url, requestConfig);const responseJson = await response.json();return responseJson} catch (error) {throw new Error(error)}} else {return new Promise((resolve, reject) => {let requestObj;if (window.XMLHttpRequest) {requestObj = new XMLHttpRequest();} else {requestObj = new ActiveXObject;}let sendData = '';if (type == 'POST') {sendData = JSON.stringify(data);}requestObj.open(type, url, true);requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");requestObj.send(sendData);requestObj.onreadystatechange = () => {if (requestObj.readyState == 4) {if (requestObj.status == 200) {let obj = requestObj.responseif (typeof obj !== 'object') {obj = JSON.parse(obj);}resolve(obj)} else {reject(requestObj)}}}})}}
注意一下 vue 中 mixins 的优先级,component > mixins > extends。
export function mergeOptions(parent: Object,child: Object,vm?: Component): Object {...// 如果有 child.extends 递归调用 mergeOptions 实现属性拷贝const extendsFrom = child.extendsif (extendsFrom) {parent = mergeOptions(parent, extendsFrom, vm)}// 如果有 child.mixins 递归调用 mergeOptions 实现属性拷贝if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm)}}// 申明 options 空对象,用来保存属性拷贝结果const options = {}let key// 遍历 parent 对象,调用 mergeField 进行属性拷贝for (key in parent) {mergeField(key)}// 遍历 parent 对象,调用 mergeField 进行属性拷贝for (key in child) {if (!hasOwn(parent, key)) {mergeField(key)}}// 属性拷贝实现方法function mergeField(key) {// 穿透赋值,默认为 defaultStratconst strat = strats[key] || defaultStratoptions[key] = strat(parent[key], child[key], vm, key)}return options}
介绍下深度优先遍历和广度优先遍历,如何实现?
深度优先遍历——是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的下一个顶点进行访问。重复此步骤,直至所有结点都被访问完为止。广度优先遍历——是从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所有结点,重复此方法,直到所有结点都被访问完为止。```js//1.深度优先遍历的递归写法function deepTraversal(node) {let nodes = []if (node != null) {nodes.push[node]let childrens = node.childrenfor (let i = 0; i < childrens.length; i++)deepTraversal(childrens[i])}return nodes}//2.深度优先遍历的非递归写法function deepTraversal(node) {let nodes = []if (node != null) {let stack = [] //同来存放将来要访问的节点stack.push(node)while (stack.length != 0) {let item = stack.pop() //正在访问的节点nodes.push(item)let childrens = item.childrenfor (let i = childrens.length - 1;i >= 0;i-- //将现在访问点的节点的子节点存入stack,供将来访问)stack.push(childrens[i])}}return nodes}//3.广度优先遍历的递归写法function wideTraversal(node) {let nodes = [],i = 0if (node != null) {nodes.push(node)wideTraversal(node.nextElementSibling)node = nodes[i++]wideTraversal(node.firstElementChild)}return nodes}//4.广度优先遍历的非递归写法function wideTraversal(node) {let nodes = [],i = 0while (node != null) {nodes.push(node)node = nodes[i++]let childrens = node.childrenfor (let i = 0; i < childrens.length; i++) {nodes.push(childrens[i])}}return nodes}
<a name="g74YS"></a>#### 使用Promise封装AJAX请求```javascript// promise 封装实现:function getJSON(url) {// 创建一个 promise 对象let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();// 新建一个 http 请求xhr.open("GET", url, true);// 设置状态的监听函数xhr.onreadystatechange = function() {if (this.readyState !== 4) return;// 当请求成功或失败时,改变 promise 的状态if (this.status === 200) {resolve(this.response);} else {reject(new Error(this.statusText));}};// 设置错误监听函数xhr.onerror = function() {reject(new Error(this.statusText));};// 设置响应的数据类型xhr.responseType = "json";// 设置请求头信息xhr.setRequestHeader("Accept", "application/json");// 发送 http 请求xhr.send(null); // 参数data为携带的数据});return promise;}
实现sleep函数(使用Promise封装setTimeout)
function timeout(delay) {return new Promise(resolve => {setTimeout(resolve, delay)})};
使用 setTimeout 实现 setInterval
function mySetInterval(fn, timeout) {// 控制器,控制定时器是否继续执行var timer = {flag: true};// 设置递归函数,模拟定时器执行。function interval() {if (timer.flag) {fn();setTimeout(interval, timeout);}}// 启动定时器setTimeout(interval, timeout);// 返回控制器return timer;}
判断对象是否存在循环引用
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用
JSON.stringify()对该类对象进行序列化,就会报错:Converting circular structure to JSON.
const isCycleObject = (obj,parent) => {const parentArr = parent || [obj];for(let i in obj) {if(typeof obj[i] === 'object') {let flag = false;parentArr.forEach((pObj) => {if(pObj === obj[i]){flag = true;}})if(flag) return true;flag = isCycleObject(obj[i],[...parentArr,obj[i]]);if(flag) return true;}}return false;}const a = 1;const b = {a};const c = {b};const o = {d:{a:3},c}o.c.b.aa = a;console.log(isCycleObject(o))
手写一个观察者模式
class Subject{ // 被观察者的类 被观察者 需要将观察者收集起来constructor(name){this.name = name;this.state = '非常开心'this.observers = [];}attach(o){ // 进行收集this.observers.push(o); // on}setState(newState){this.state = newState;this.observers.forEach(o=>o.update(this.name,newState)) // emit}}class Observer{ // 观察者constructor(name){this.name = name;}update(s,state){console.log(this.name+":" + s + '当前'+state);}}// vue 数据变了(状态) 视图要更新 (通知依赖的人)let s = new Subject('小宝宝');let o1 = new Observer('爸爸');let o2 = new Observer('妈妈');s.attach(o1)s.attach(o2)s.setState('不开心了')s.setState('开心了')
手写一个观察者模式
class Subject{ // 被观察者的类 被观察者 需要将观察者收集起来constructor(name){this.name = name;this.state = '非常开心'this.observers = [];}attach(o){ // 进行收集this.observers.push(o); // on}setState(newState){this.state = newState;this.observers.forEach(o=>o.update(this.name,newState)) // emit}}class Observer{ // 观察者constructor(name){this.name = name;}update(s,state){console.log(this.name+":" + s + '当前'+state);}}// vue 数据变了(状态) 视图要更新 (通知依赖的人)let s = new Subject('小宝宝');let o1 = new Observer('爸爸');let o2 = new Observer('妈妈');s.attach(o1)s.attach(o2)s.setState('不开心了')s.setState('开心了')
手写去重
使用闭包实现每隔一秒打印 1,2,3,4
// 使用闭包实现for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);}, i * 1000);})(i);}// 使用 let 块级作用域for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, i * 1000);}
手写一个 jsonp
function jsonp(url, params, callback) {// 判断是否含有参数let queryString = url.indexOf("?") === -1 ? "?" : "&";// 添加参数for (var k in params) {if (params.hasOwnProperty(k)) {queryString += k + "=" + params[k] + "&";}}// 处理回调函数名let random = Math.random().toString.replace(".", ""),callbackName = "myJsonp" + random;// 添加回调函数queryString += "callback=" + callbackName;// 构建请求let scriptNode = document.createElement("script");scriptNode.src = url + queryString;window[callbackName] = function() {// 调用回调函数callback(...arguments);// 删除这个引入的脚本document.getElementsByTagName("head")[0].removeChild(scriptNode);};// 发起请求document.getElementsByTagName("head")[0].appendChild(scriptNode);}
详细资料可以参考:《原生 jsonp 具体实现》《jsonp 的原理与实现》
实现 compose 函数
compose(组合)函数特点:
- compose 的参数是函数,返回的也是一个函数
- 因为除了第一个函数的接受参数,其他函数的接受参数都是上一个函数的返回值,所以初始函数的参数是多元的,而其他函数的接受值是一元的
- compsoe 函数可以接受任意的参数,所有的参数都是函数,且执行方向是自右向左的,初始函数一定放到参数的最右面
实现```jsfunction compose(...fns) {let isFirst = truereturn (...args) => {return fns.reduceRight((result, fn) => {if (!isFirst) return fn(result)isFirst = falsereturn fn(...result)}, args)}}
测试
const greeting = (firstName, lastName) => 'hello, ' + firstName + ' ' + lastNameconst toUpper = str => str.toUpperCase()const fn = compose(toUpper, greeting)console.log(fn('jack', 'smith')) // HELLO, JACK SMITH
<a name="FaW6h"></a>#### 格式化金钱,每千分位加逗号```javascript// 1function format(str) {let s = ''let count = 0for (let i = str.length - 1; i >= 0; i--) {s = str[i] + scount++if (count % 3 == 0 && i != 0) {s = ',' + s}}return s}// 2function format(str) {return str.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')}
请用js去除字符串空格
## 去除所有空格str.replace(/\s/g, '')## 去除两边空格str.replace(/^\s+|\s+$/g, '')// 原生方法str.trim()
反转数组
要求
input: I am a student output: student a am I 输入是数组 输出也是数组不允许用 split splice reverse
// 1function reverseArry(arr) {const result = []while (arr.length) {result.push(arr.pop())}return result}console.log(reverseArry(['I', 'am', 'a', 'student']))// ["student", "a", "am", "I"]// 2function reverseArry(arry) {const result = []const distance = arry.length - 1for (let i = distance; i >= 0; i--) {result[distance - i] = arry[i]}return result}
将金额12345转成中文金额表示
要求
12345 => 一万两千三百四十五 10086 => 一万零八十六 100010001 => 一亿零一万零一 100000000 => 一亿 单位支持到亿
function numToString(num) {if (num > 999999999) throw '超过金额上限,最大单位为亿'const unitMap = ['', '十', '百', '千', '万', '十', '百', '千', '亿']const stringMap = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']const n = num + ''const len = n.lengthlet lastIndex = len - 1let result = ''for (let i = 0; i < len; i++) {if (i > 0 && n[i] == '0') {if (n[i - 1] != '0') result += '零'} else {result += stringMap[n[i]] + unitMap[lastIndex]}lastIndex--}lastIndex = result.length - 1if (result[lastIndex] == '零') return result.slice(0, lastIndex)return result}console.log(numToString(12345)) // 一万二千三百四十五console.log(numToString(10086)) // 一万零八十六console.log(numToString(100010001)) // 一亿零一万零一console.log(numToString(100000000)) // 一亿
异步求和
要求
提供一个异步 add 方法如下,需要实现一个 await sum(…args) 函数: function asyncAdd(a, b, callback) { setTimeout(function() { callback(null, a + b) }, 1000) }
function sum(...args) {let start = 0let result = 0let count = 0 // 用于计算开启了多少个 Promisefunction _sum(resolve) {count++new Promise((r, j) => {let a = args[start++]let b = args[start++]a = a !== undefined? a : 0b = b !== undefined? b : 0 // 如果访问的元素超出了数组范围,则转为 0asyncAdd(a, b, (context, sum) => {r(sum)})if (start < args.length) {_sum(resolve)}}).then(sum => {result += sumcount--if (count == 0) resolve(result) // 所有的 Promise 执行完毕,返回结果})}return new Promise((resolve, reject) => {if (!args || !args.length) return resolve(0)if (args.length == 1) return resolve(args[0])_sum(resolve)})}// 测试sum(1,2,3,4,5,6,7,8,9,10,11).then(sum => console.log(sum)) // 66// orasync function test() {console.log(await sum(1,2,3,4,5,6,7,8,9,10,11))}test() // 66
数字集转换成字母集
要求
a~z 有 26个字母,按照 1~26 编码,现在给定一个数字字符串,输出所有可能的解码结果,如:输入 1234,输出 [‘awd’, ‘abcd’, ‘lcd’]
const map = [0,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']function getDecodes(num) {if (!num) return []num += ''const result = []_getDecodes(num, 0, [], result)return result}function _getDecodes(num, start, path, result) {if (start == num.length) return result.push([...path])let c = num[start++]path.push(map[c])_getDecodes(num, start, path, result)path.pop()if (start == num.length) returnc += num[start]if (c > 26) returnpath.push(map[c])_getDecodes(num, start + 1, path, result)path.pop()}
