实现字符串的repeat办法
输出字符串s,以及其反复的次数,输入反复的后果,例如输出abc,2,输入abcabc。
function repeat(s, n) { return (new Array(n + 1)).join(s);}
递归:
function repeat(s, n) { return (n > 0) ? s.concat(repeat(s, --n)) : "";}
实现async/await
剖析
// generator生成器 生成迭代器iterator// 默认这样写的类数组是不能被迭代的,短少迭代办法let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}// // 应用迭代器使得能够开展数组// // Symbol有很多元编程办法,能够改js自身性能// likeArray[Symbol.iterator] = function () {// // 迭代器是一个对象 对象中有next办法 每次调用next 都须要返回一个对象 {value,done}// let index = 0// return {// next: ()=>{// // 会主动调用这个办法// console.log('index',index)// return {// // this 指向likeArray// value: this[index],// done: index++ === this.length// }// }// }// }// let arr = [...likeArray]// console.log('arr', arr)// 应用生成器返回迭代器// likeArray[Symbol.iterator] = function *() {// let index = 0// while (index != this.length) {// yield this[index++]// }// }// let arr = [...likeArray]// console.log('arr', arr)// 生成器 碰到yield就会暂停// function *read(params) {// yield 1;// yield 2;// }// 生成器返回的是迭代器// let it = read()// console.log(it.next())// console.log(it.next())// console.log(it.next())// 通过generator来优化promise(promise的毛病是不停的链式调用)const fs = require('fs')const path = require('path')// const co = require('co') // 帮咱们执行generatorconst promisify = fn=>{ return (...args)=>{ return new Promise((resolve,reject)=>{ fn(...args, (err,data)=>{ if(err) { reject(err) } resolve(data) }) }) }}// promise化let asyncReadFile = promisify(fs.readFile)function * read() { let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8') let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8') return content2}// 这样写太繁琐 须要借助co来实现// let re = read()// let {value,done} = re.next()// value.then(data=>{// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 // let {value,done} = re.next(data) // value.then(d=>{// let {value,done} = re.next(d)// console.log(value,done)// })// }).catch(err=>{// re.throw(err) // 手动抛出谬误 能够被try catch捕捉// })// 实现co原理function co(it) {// it 迭代器 return new Promise((resolve,reject)=>{ // 异步迭代 须要依据函数来实现 function next(data) { // 递归得有停止条件 let {value,done} = it.next(data) if(done) { resolve(value) // 间接让promise变成胜利 用以后返回的后果 } else { // Promise.resolve(value).then(data=>{ // next(data) // }).catch(err=>{ // reject(err) // }) // 简写 Promise.resolve(value).then(next,reject) } } // 首次调用 next() })}co(read()).then(d=>{ console.log(d)}).catch(err=>{ console.log(err,'--')})
整体看一下构造
function asyncToGenerator(generatorFunc) { return function() { const gen = generatorFunc.apply(this, arguments) return new Promise((resolve, reject) => { function step(key, arg) { let generatorResult try { generatorResult = gen[key](arg) } catch (error) { return reject(error) } const { value, done } = generatorResult if (done) { return resolve(value) } else { return Promise.resolve(value).then(val => step('next', val), err => step('throw', err)) } } step("next") }) }}
剖析
function asyncToGenerator(generatorFunc) { // 返回的是一个新的函数 return function() { // 先调用generator函数 生成迭代器 // 对应 var gen = testG() const gen = generatorFunc.apply(this, arguments) // 返回一个promise 因为内部是用.then的形式 或者await的形式去应用这个函数的返回值的 // var test = asyncToGenerator(testG) // test().then(res => console.log(res)) return new Promise((resolve, reject) => { // 外部定义一个step函数 用来一步一步的跨过yield的妨碍 // key有next和throw两种取值,别离对应了gen的next和throw办法 // arg参数则是用来把promise resolve进去的值交给下一个yield function step(key, arg) { let generatorResult // 这个办法须要包裹在try catch中 // 如果报错了 就把promise给reject掉 内部通过.catch能够获取到谬误 try { generatorResult = gen[key](arg) } catch (error) { return reject(error) } // gen.next() 失去的后果是一个 { value, done } 的构造 const { value, done } = generatorResult if (done) { // 如果曾经实现了 就间接resolve这个promise // 这个done是在最初一次调用next后才会为true // 以本文的例子来说 此时的后果是 { done: true, value: 'success' } // 这个value也就是generator函数最初的返回值 return resolve(value) } else { // 除了最初完结的时候外,每次调用gen.next() // 其实是返回 { value: Promise, done: false } 的构造, // 这里要留神的是Promise.resolve能够承受一个promise为参数 // 并且这个promise参数被resolve的时候,这个then才会被调用 return Promise.resolve( // 这个value对应的是yield前面的promise value ).then( // value这个promise被resove的时候,就会执行next // 并且只有done不是true的时候 就会递归的往下解开promise // 对应gen.next().value.then(value => { // gen.next(value).value.then(value2 => { // gen.next() // // // 此时done为true了 整个promise被resolve了 // // 最内部的test().then(res => console.log(res))的then就开始执行了 // }) // }) function onResolve(val) { step("next", val) }, // 如果promise被reject了 就再次进入step函数 // 不同的是,这次的try catch中调用的是gen.throw(err) // 那么天然就被catch到 而后把promise给reject掉啦 function onReject(err) { step("throw", err) }, ) } } step("next") }) }}
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 后果{ user: 'anonymous', id: [ 123, 456 ], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true}*/
function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来 const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中 let paramsObj = {}; // 将 params 存到对象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 解决有 value 的参数 let [key, val] = param.split('='); // 宰割 key 和 value val = decodeURIComponent(val); // 解码 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字 if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创立 key 并设置值 paramsObj[key] = val; } } else { // 解决没有 value 的参数 paramsObj[param] = true; } }) return paramsObj;}
实现redux中间件
简略实现
function createStore(reducer) { let currentState let listeners = [] function getState() { return currentState } function dispatch(action) { currentState = reducer(currentState, action) listeners.map(listener => { listener() }) return action } function subscribe(cb) { listeners.push(cb) return () => {} } dispatch({type: 'ZZZZZZZZZZ'}) return { getState, dispatch, subscribe }}// 利用实例如下:function reducer(state = 0, action) { switch (action.type) { case 'ADD': return state + 1 case 'MINUS': return state - 1 default: return state }}const store = createStore(reducer)console.log(store);store.subscribe(() => { console.log('change');})console.log(store.getState());console.log(store.dispatch({type: 'ADD'}));console.log(store.getState());
2. 迷你版
export const createStore = (reducer,enhancer)=>{ if(enhancer) { return enhancer(createStore)(reducer) } let currentState = {} let currentListeners = [] const getState = ()=>currentState const subscribe = (listener)=>{ currentListeners.push(listener) } const dispatch = action=>{ currentState = reducer(currentState, action) currentListeners.forEach(v=>v()) return action } dispatch({type:'@@INIT'}) return {getState,subscribe,dispatch}}//中间件实现export applyMiddleWare(...middlewares){ return createStore=>...args=>{ const store = createStore(...args) let dispatch = store.dispatch const midApi = { getState:store.getState, dispatch:...args=>dispatch(...args) } const middlewaresChain = middlewares.map(middleware=>middleware(midApi)) dispatch = compose(...middlewaresChain)(store.dispatch) return { ...store, dispatch } }// fn1(fn2(fn3())) 把函数嵌套顺次调用export function compose(...funcs){ if(funcs.length===0){ return arg=>arg } if(funs.length===1){ return funs[0] } return funcs.reduce((ret,item)=>(...args)=>ret(item(...args)))}//bindActionCreator实现function bindActionCreator(creator,dispatch){ return ...args=>dispatch(creator(...args))}function bindActionCreators(creators,didpatch){ //let bound = {} //Object.keys(creators).forEach(v=>{ // let creator = creator[v] // bound[v] = bindActionCreator(creator,dispatch) //}) //return bound return Object.keys(creators).reduce((ret,item)=>{ ret[item] = bindActionCreator(creators[item],dispatch) return ret },{})}
实现find办法
find
接管一个办法作为参数,办法外部返回一个条件find
会遍历所有的元素,执行你给定的带有条件返回值的函数- 合乎该条件的元素会作为
find
办法的返回值 - 如果遍历完结还没有合乎该条件的元素,则返回
undefined
var users = [ {id: 1, name: '张三'}, {id: 2, name: '张三'}, {id: 3, name: '张三'}, {id: 4, name: '张三'}]Array.prototype.myFind = function (callback) { // var callback = function (item, index) { return item.id === 4 } for (var i = 0; i < this.length; i++) { if (callback(this[i], i)) { return this[i] } }}var ret = users.myFind(function (item, index) { return item.id === 2})console.log(ret)
原生实现
function ajax() { let xhr = new XMLHttpRequest() //实例化,以调用办法 xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步 xhr.onreadystatechange = () => { //每当 readyState 属性扭转时,就会调用该函数。 if (xhr.readyState === 4) { //XMLHttpRequest 代理以后所处状态。 if (xhr.status >= 200 && xhr.status < 300) { //200-300申请胜利 let string = request.responseText //JSON.parse() 办法用来解析JSON字符串,结构由字符串形容的JavaScript值或对象 let object = JSON.parse(string) } } } request.send() //用于理论收回 HTTP 申请。不带参数为GET申请}
实现Node的require办法
require 基本原理
require 查找门路
require
和module.exports
干的事件并不简单,咱们先假如有一个全局对象{}
,初始状况下是空的,当你require
某个文件时,就将这个文件拿进去执行,如果这个文件外面存在module.exports
,当运行到这行代码时将module.exports
的值退出这个对象,键为对应的文件名,最终这个对象就长这样:
{ "a.js": "hello world", "b.js": function add(){}, "c.js": 2, "d.js": { num: 2 }}
当你再次require
某个文件时,如果这个对象外面有对应的值,就间接返回给你,如果没有就反复后面的步骤,执行指标文件,而后将它的module.exports
退出这个全局对象,并返回给调用者。这个全局对象其实就是咱们常常据说的缓存。所以require
和module.exports
并没有什么黑魔法,就只是运行并获取指标文件的值,而后退出缓存,用的时候拿进去用就行
手写实现一个require
const path = require('path'); // 门路操作const fs = require('fs'); // 文件读取const vm = require('vm'); // 文件执行// node模块化的实现// node中是自带模块化机制的,每个文件就是一个独自的模块,并且它遵循的是CommonJS标准,也就是应用require的形式导入模块,通过module.export的形式导出模块。// node模块的运行机制也很简略,其实就是在每一个模块外层包裹了一层函数,有了函数的包裹就能够实现代码间的作用域隔离// require加载模块// require依赖node中的fs模块来加载模块文件,fs.readFile读取到的是一个字符串。// 在javascrpt中咱们能够通过eval或者new Function的形式来将一个字符串转换成js代码来运行。// eval// const name = 'poetry';// const str = 'const a = 123; console.log(name)';// eval(str); // poetry;// new Function// new Function接管的是一个要执行的字符串,返回的是一个新的函数,调用这个新的函数字符串就会执行了。如果这个函数须要传递参数,能够在new Function的时候顺次传入参数,最初传入的是要执行的字符串。比方这里传入参数b,要执行的字符串str// const b = 3;// const str = 'let a = 1; return a + b';// const fun = new Function('b', str);// console.log(fun(b, str)); // 4// 能够看到eval和Function实例化都能够用来执行javascript字符串,仿佛他们都能够来实现require模块加载。不过在node中并没有选用他们来实现模块化,起因也很简略因为他们都有一个致命的问题,就是都容易被不属于他们的变量所影响。// 如下str字符串中并没有定义a,然而确能够应用下面定义的a变量,这显然是不对的,在模块化机制中,str字符串应该具备本身独立的运行空间,本身不存在的变量是不能够间接应用的// const a = 1;// const str = 'console.log(a)';// eval(str);// const func = new Function(str);// func();// node存在一个vm虚拟环境的概念,用来运行额定的js文件,他能够保障javascript执行的独立性,不会被内部所影响// vm 内置模块// 尽管咱们在内部定义了hello,然而str是一个独立的模块,并不在村hello变量,所以会间接报错。// 引入vm模块, 不须要装置,node 自建模块// const vm = require('vm');// const hello = 'poetry';// const str = 'console.log(hello)';// wm.runInThisContext(str); // 报错// 所以node执行javascript模块时能够采纳vm来实现。就能够保障模块的独立性了// 剖析实现步骤// 1.导入相干模块,创立一个Require办法。// 2.抽离通过Module._load办法,用于加载模块。// 3.Module.resolveFilename 依据相对路径,转换成绝对路径。// 4.缓存模块 Module._cache,同一个模块不要反复加载,晋升性能。// 5.创立模块 id: 保留的内容是 exports = {}相当于this。// 6.利用tryModuleLoad(module, filename) 尝试加载模块。// 7.Module._extensions应用读取文件。// 8.Module.wrap: 把读取到的js包裹一个函数。// 9.将拿到的字符串应用runInThisContext运行字符串。// 10.让字符串执行并将this改编成exports// 定义导入类,参数为模块门路function Require(modulePath) { // 获取以后要加载的绝对路径 let absPathname = path.resolve(__dirname, modulePath); // 主动给模块增加后缀名,实现省略后缀名加载模块,其实也就是如果文件没有后缀名的时候遍历一下所有的后缀名看一下文件是否存在 // 获取所有后缀名 const extNames = Object.keys(Module._extensions); let index = 0; // 存储原始文件门路 const oldPath = absPathname; function findExt(absPathname) { if (index === extNames.length) { throw new Error('文件不存在'); } try { fs.accessSync(absPathname); return absPathname; } catch(e) { const ext = extNames[index++]; findExt(oldPath + ext); } } // 递归追加后缀名,判断文件是否存在 absPathname = findExt(absPathname); // 从缓存中读取,如果存在,间接返回后果 if (Module._cache[absPathname]) { return Module._cache[absPathname].exports; } // 创立模块,新建Module实例 const module = new Module(absPathname); // 增加缓存 Module._cache[absPathname] = module; // 加载以后模块 tryModuleLoad(module); // 返回exports对象 return module.exports;}// Module的实现很简略,就是给模块创立一个exports对象,tryModuleLoad执行的时候将内容退出到exports中,id就是模块的绝对路径// 定义模块, 增加文件id标识和exports属性function Module(id) { this.id = id; // 读取到的文件内容会放在exports中 this.exports = {};}Module._cache = {};// 咱们给Module挂载动态属性wrapper,外面定义一下这个函数的字符串,wrapper是一个数组,数组的第一个元素就是函数的参数局部,其中有exports,module. Require,__dirname, __filename, 都是咱们模块中罕用的全局变量。留神这里传入的Require参数是咱们本人定义的Require// 第二个参数就是函数的完结局部。两局部都是字符串,应用的时候咱们将他们包裹在模块的字符串内部就能够了Module.wrapper = [ "(function(exports, module, Require, __dirname, __filename) {", "})"]// _extensions用于针对不同的模块扩展名应用不同的加载形式,比方JSON和javascript加载形式必定是不同的。JSON应用JSON.parse来运行。// javascript应用vm.runInThisContext来运行,能够看到fs.readFileSync传入的是module.id也就是咱们Module定义时候id存储的是模块的绝对路径,读取到的content是一个字符串,咱们应用Module.wrapper来包裹一下就相当于在这个模块内部又包裹了一个函数,也就实现了公有作用域。// 应用call来执行fn函数,第一个参数扭转运行的this咱们传入module.exports,前面的参数就是函数里面包裹参数exports, module, Require, __dirname, __filenameModule._extensions = { '.js'(module) { const content = fs.readFileSync(module.id, 'utf8'); const fnStr = Module.wrapper[0] + content + Module.wrapper[1]; const fn = vm.runInThisContext(fnStr); fn.call(module.exports, module.exports, module, Require,__filename,__dirname); }, '.json'(module) { const json = fs.readFileSync(module.id, 'utf8'); module.exports = JSON.parse(json); // 把文件的后果放在exports属性上 }}// tryModuleLoad函数接管的是模块对象,通过path.extname来获取模块的后缀名,而后应用Module._extensions来加载模块// 定义模块加载办法function tryModuleLoad(module) { // 获取扩展名 const extension = path.extname(module.id); // 通过后缀加载以后模块 Module._extensions[extension](module);}// 至此Require加载机制咱们根本就写完了,咱们来从新看一下。Require加载模块的时候传入模块名称,在Require办法中应用path.resolve(__dirname, modulePath)获取到文件的绝对路径。而后通过new Module实例化的形式创立module对象,将模块的绝对路径存储在module的id属性中,在module中创立exports属性为一个json对象// 应用tryModuleLoad办法去加载模块,tryModuleLoad中应用path.extname获取到文件的扩展名,而后依据扩展名来执行对应的模块加载机制// 最终将加载到的模块挂载module.exports中。tryModuleLoad执行结束之后module.exports曾经存在了,间接返回就能够了// 给模块增加缓存// 增加缓存也比较简单,就是文件加载的时候将文件放入缓存中,再去加载模块时先看缓存中是否存在,如果存在间接应用,如果不存在再去从新,加载之后再放入缓存// 测试let json = Require('./test.json');let test2 = Require('./test2.js');console.log(json);console.log(test2);
参考:前端手写面试题具体解答
实现Object.freeze
Object.freeze
解冻一个对象,让其不能再增加/删除属性,也不能批改该对象已有属性的可枚举性、可配置可写性,也不能批改已有属性的值和它的原型属性,最初返回一个和传入参数雷同的对象
function myFreeze(obj){ // 判断参数是否为Object类型,如果是就关闭对象,循环遍历对象。去掉原型属性,将其writable个性设置为false if(obj instanceof Object){ Object.seal(obj); // 关闭对象 for(let key in obj){ if(obj.hasOwnProperty(key)){ Object.defineProperty(obj,key,{ writable:false // 设置只读 }) // 如果属性值仍然为对象,要通过递归来进行进一步的解冻 myFreeze(obj[key]); } } }}
类数组转化为数组的办法
const arrayLike=document.querySelectorAll('div')// 1.扩大运算符[...arrayLike]// 2.Array.fromArray.from(arrayLike)// 3.Array.prototype.sliceArray.prototype.slice.call(arrayLike)// 4.Array.applyArray.apply(null, arrayLike)// 5.Array.prototype.concatArray.prototype.concat.apply([], arrayLike)
实现Ajax
步骤
- 创立
XMLHttpRequest
实例 - 收回 HTTP 申请
- 服务器返回 XML 格局的字符串
- JS 解析 XML,并更新部分页面
- 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。
理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。
实现节流函数(throttle)
节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的工夫才会执行回调函数。总结起来就是: 事件,依照一段时间的距离来进行触发 。
像dom的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多
手写简版
应用工夫戳的节流函数会在第一次触发事件时立刻执行,当前每过 wait 秒之后才执行一次,并且最初一次触发事件不会被执行
工夫戳形式:
// func是用户传入须要防抖的函数// wait是等待时间const throttle = (func, wait = 50) => { // 上一次执行该函数的工夫 let lastTime = 0 return function(...args) { // 以后工夫 let now = +new Date() // 将以后工夫和上一次执行函数工夫比照 // 如果差值大于设置的等待时间就执行函数 if (now - lastTime > wait) { lastTime = now func.apply(this, args) } }}setInterval( throttle(() => { console.log(1) }, 500), 1)
定时器形式:
应用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最初一次进行触发后,还会再执行一次函数
function throttle(func, delay){ var timer = null; returnfunction(){ var context = this; var args = arguments; if(!timer){ timer = setTimeout(function(){ func.apply(context, args); timer = null; },delay); } }}
实用场景:
DOM
元素的拖拽性能实现(mousemove
)- 搜寻联想(
keyup
) - 计算鼠标挪动的间隔(
mousemove
) Canvas
模仿画板性能(mousemove
)- 监听滚动事件判断是否到页面底部主动加载更多
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器
resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
总结
- 函数防抖 :将几次操作合并为一次操作进行。原理是保护一个计时器,规定在delay工夫后触发函数,然而在delay工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
- 函数节流 :使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。
Object.assign
Object.assign()
办法用于将所有可枚举属性的值从一个或多个源对象复制到指标对象。它将返回指标对象(请留神这个操作是浅拷贝)
Object.defineProperty(Object, 'assign', { value: function(target, ...args) { if (target == null) { return new TypeError('Cannot convert undefined or null to object'); } // 指标对象须要对立是援用数据类型,若不是会主动转换 const to = Object(target); for (let i = 0; i < args.length; i++) { // 每一个源对象 const nextSource = args[i]; if (nextSource !== null) { // 应用for...in和hasOwnProperty双重判断,确保只拿到自身的属性、办法(不蕴含继承的) for (const nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, // 不可枚举 enumerable: false, writable: true, configurable: true,})
实现一个迭代器生成函数
ES6对迭代器的实现
JS原生的汇合类型数据结构,只有Array
(数组)和Object
(对象);而ES6
中,又新增了Map
和Set
。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6
在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator
)。
ES6
约定,任何数据结构只有具备Symbol.iterator
属性(这个属性就是Iterator
的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...
循环和迭代器的next办法遍历。 事实上,for...of...
的背地正是对next
办法的重复调用。
在ES6中,针对Array
、Map
、Set
、String
、TypedArray
、函数的 arguments
对象、NodeList
对象这些原生的数据结构都能够通过for...of...
进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用for...of...
遍历数组时:
const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) { console.log(`以后元素是${item}`)}
之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的Symbol.iterator
生成了它对应的迭代器对象,通过重复调用迭代器对象的next
办法拜访了数组成员,像这样:
const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 对迭代器对象执行next,就能一一拜访汇合的成员iterator.next()iterator.next()iterator.next()
丢进控制台,咱们能够看到next
每次会按程序帮咱们拜访一个汇合成员:
而for...of...
做的事件,根本等价于上面这通操作:
// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 初始化一个迭代后果let now = { done: false }// 循环往外迭代成员while(!now.done) { now = iterator.next() if(!now.done) { console.log(`当初遍历到了${now.value}`) }}
能够看出,for...of...
其实就是iterator
循环调用换了种写法。在ES6中咱们之所以可能开心地用for...of...
遍历各种各种的汇合,全靠迭代器模式在背地给力。
ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在ES6中的实现有更深的了解。
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 后果{ user: 'anonymous', id: [ 123, 456 ], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true}*/
function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来 const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中 let paramsObj = {}; // 将 params 存到对象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 解决有 value 的参数 let [key, val] = param.split('='); // 宰割 key 和 value val = decodeURIComponent(val); // 解码 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字 if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创立 key 并设置值 paramsObj[key] = val; } } else { // 解决没有 value 的参数 paramsObj[param] = true; } }) return paramsObj;}
event模块
实现node中回调函数的机制,node中回调函数其实是外部应用了观察者模式。
观察者模式:定义了对象间一种一对多的依赖关系,当指标对象Subject产生扭转时,所有依赖它的对象Observer都会失去告诉。
function EventEmitter() { this.events = new Map();}// 须要实现的一些办法:// addListener、removeListener、once、removeAllListeners、emit// 模仿实现addlistener办法const wrapCallback = (fn, once = false) => ({ callback: fn, once });EventEmitter.prototype.addListener = function(type, fn, once = false) { const hanlder = this.events.get(type); if (!hanlder) { // 没有type绑定事件 this.events.set(type, wrapCallback(fn, once)); } else if (hanlder && typeof hanlder.callback === 'function') { // 目前type事件只有一个回调 this.events.set(type, [hanlder, wrapCallback(fn, once)]); } else { // 目前type事件数>=2 hanlder.push(wrapCallback(fn, once)); }}// 模仿实现removeListenerEventEmitter.prototype.removeListener = function(type, listener) { const hanlder = this.events.get(type); if (!hanlder) return; if (!Array.isArray(this.events)) { if (hanlder.callback === listener.callback) this.events.delete(type); else return; } for (let i = 0; i < hanlder.length; i++) { const item = hanlder[i]; if (item.callback === listener.callback) { hanlder.splice(i, 1); i--; if (hanlder.length === 1) { this.events.set(type, hanlder[0]); } } }}// 模仿实现once办法EventEmitter.prototype.once = function(type, listener) { this.addListener(type, listener, true);}// 模仿实现emit办法EventEmitter.prototype.emit = function(type, ...args) { const hanlder = this.events.get(type); if (!hanlder) return; if (Array.isArray(hanlder)) { hanlder.forEach(item => { item.callback.apply(this, args); if (item.once) { this.removeListener(type, item); } }) } else { hanlder.callback.apply(this, args); if (hanlder.once) { this.events.delete(type); } } return true;}EventEmitter.prototype.removeAllListeners = function(type) { const hanlder = this.events.get(type); if (!hanlder) return; this.events.delete(type);}
打印出以后网页应用了多少种HTML元素
一行代码能够解决:
const fn = () => { return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;}
值得注意的是:DOM操作返回的是类数组,须要转换为数组之后才能够调用数组的办法。
实现instanceOf
思路:
- 步骤1:先获得以后类的原型,以后实例对象的原型链
步骤2:始终循环(执行原型链的查找机制)
- 获得以后实例对象原型链的原型链(
proto = proto.__proto__
,沿着原型链始终向上查找) - 如果 以后实例的原型链
__proto__
上找到了以后类的原型prototype
,则返回true
- 如果 始终找到
Object.prototype.__proto__ == null
,Object
的基类(null
)下面都没找到,则返回false
- 获得以后实例对象原型链的原型链(
// 实例.__ptoto__ === 类.prototypefunction _instanceof(example, classFunc) { // 因为instance要检测的是某对象,须要有一个前置判断条件 //根本数据类型间接返回false if(typeof example !== 'object' || example === null) return false; let proto = Object.getPrototypeOf(example); while(true) { if(proto == null) return false; // 在以后实例对象的原型链上,找到了以后类 if(proto == classFunc.prototype) return true; // 沿着原型链__ptoto__一层一层向上查 proto = Object.getPrototypeof(proto); // 等于proto.__ptoto__ }}console.log('test', _instanceof(null, Array)) // falseconsole.log('test', _instanceof([], Array)) // trueconsole.log('test', _instanceof('', Array)) // falseconsole.log('test', _instanceof({}, Object)) // true
验证是否是邮箱
function isEmail(email) { var regx = /^([a-zA-Z0-9_\-])[email protected]([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/; return regx.test(email);}
实现 add(1)(2)(3)
函数柯里化概念: 柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。
1)粗犷版
function add (a) {return function (b) { return function (c) { return a + b + c; }}}console.log(add(1)(2)(3)); // 6
2)柯里化解决方案
- 参数长度固定
var add = function (m) { var temp = function (n) { return add(m + n); } temp.toString = function () { return m; } return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43
对于add(3)(4)(5),其执行过程如下:
- 先执行add(3),此时m=3,并且返回temp函数;
- 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
- 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
- 因为前面没有传入参数,等于返回的temp函数不被执行而是打印,理解JS的敌人都晓得对象的toString是批改对象转换字符串的办法,因而代码中temp函数的toString函数return m值,而m值是最初一步执行函数时的值m=12,所以返回值是12。
- 参数长度不固定
function add (...args) { //求和 return args.reduce((a, b) => a + b)}function currying (fn) { let args = [] return function temp (...newArgs) { if (newArgs.length) { args = [ ...args, ...newArgs ] return temp } else { let val = fn.apply(this, args) args = [] //保障再次调用时清空 return val } }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)()) //15console.log(addCurry(1)(2)(3, 4, 5)()) //15console.log(addCurry(1)(2, 3, 4, 5)()) //15
判断对象是否存在循环援用
循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用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)
查找有序二维数组的目标值:
var findNumberIn2DArray = function(matrix, target) { if (matrix == null || matrix.length == 0) { return false; } let row = 0; let column = matrix[0].length - 1; while (row < matrix.length && column >= 0) { if (matrix[row][column] == target) { return true; } else if (matrix[row][column] > target) { column--; } else { row++; } } return false;};
二维数组斜向打印:
function printMatrix(arr){ let m = arr.length, n = arr[0].length let res = [] // 左上角,从0 到 n - 1 列进行打印 for (let k = 0; k < n; k++) { for (let i = 0, j = k; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } // 右下角,从1 到 n - 1 行进行打印 for (let k = 1; k < m; k++) { for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } return res}