乐趣区

关于javascript:前端高频手写题自测你能做出几道

实现字符串的 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') // 帮咱们执行 generator

const 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 查找门路

requiremodule.exports 干的事件并不简单,咱们先假如有一个全局对象{},初始状况下是空的,当你 require 某个文件时,就将这个文件拿进去执行,如果这个文件外面存在module.exports,当运行到这行代码时将 module.exports 的值退出这个对象,键为对应的文件名,最终这个对象就长这样:

{
  "a.js": "hello world",
  "b.js": function add(){},
  "c.js": 2,
  "d.js": {num: 2}
}

当你再次 require 某个文件时,如果这个对象外面有对应的值,就间接返回给你,如果没有就反复后面的步骤,执行指标文件,而后将它的 module.exports 退出这个全局对象,并返回给调用者。这个全局对象其实就是咱们常常据说的缓存。所以 requiremodule.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, __filename
Module._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.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.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 中,又新增了 MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以 ES6 在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备 Symbol.iterator 属性(这个属性就是 Iterator 的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被 for...of... 循环和迭代器的 next 办法遍历。事实上,for...of...的背地正是对 next 办法的重复调用。

在 ES6 中,针对 ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of... 进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用 for...of... 遍历数组时:

const arr = [1, 2, 3]
const len = arr.length
for(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));
  }
}
// 模仿实现 removeListener
EventEmitter.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__ == nullObject 的基类 (null) 下面都没找到,则返回 false
// 实例.__ptoto__ === 类.prototype
function _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)) // false
console.log('test', _instanceof([], Array)) // true
console.log('test', _instanceof('', Array)) // false
console.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)); // 12
console.log(add(3)(6)(9)(25)); // 43

对于 add(3)(4)(5),其执行过程如下:

  1. 先执行 add(3),此时 m =3,并且返回 temp 函数;
  2. 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m =7,并且返回 temp 函数
  3. 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m =12,并且返回 temp 函数
  4. 因为前面没有传入参数,等于返回的 temp 函数不被执行而是打印,理解 JS 的敌人都晓得对象的 toString 是批改对象转换字符串的办法,因而代码中 temp 函数的 toString 函数 return m 值,而 m 值是最初一步执行函数时的值 m =12,所以返回值是 12。
  5. 参数长度不固定
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)())  //15
console.log(addCurry(1)(2)(3, 4, 5)())  //15
console.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
}
退出移动版