本文代码比较多,大部分说明都在注释里了
解析 url 参数 就是提出 url 里的参数并转成对象
1 2 3 4 5 6 7 8 9 10 function getUrlParams (url ) { let reg = /([^?&=]+)=([^?&=]+)/g let obj = {} url.replace(reg, function ( ) { obj[arguments [1 ]] = arguments [2 ] }) return obj } let url = "https://www.junjin.cn?a=1&b=2" console .log(getUrlParams(url))
call 改变 this 指向用的,可以接收多个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Function .prototype.myCall = function (ctx ) { ctx = ctx || window let fn = Symbol () ctx[fn] = this let result = ctx[fn](...arguments) delete ctx[fn] return result } let obj = { name : 沐华 }function foo ( ) { return this .name } console .log(foo.myCall(obj))
用 Symbol
是因为他是独一无二的,避免和 obj 里的属性重名
原理就是把 foo 添加到 obj 里,执行 foo 拿到返回值,再从 obj 里把 foo 删掉
apply 原理同上,只不过 apply 接收第二个参数是数组,不支持第三个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 Function .prototype.myApply = function (ctx ) { ctx = ctx || window let fn = Symbol () ctx[fn] = this let result if (arguments [1 ]) { result = ctx[fn](...arguments[1 ]) } else { result = ctx[fn]() } delete ctx[fn] return result }
bind 1 2 3 4 5 6 7 8 9 10 11 Function .prototype.myBind = function (ctx, ...args ) { const self = this const fn = function ( ) {} const bind = function ( ) { const _this = this instanceof fn ? this : ctx return self.apply(_this, [...args, ...arguments]) } fn.prototype = this .prototype bind.prototype = new fn() return bind }
bind 不会立即执行,会返回一个函数
函数可以直接执行并且传参,如 foo.myBind(obj, 1)(2, 3)
,所以需要 [ ...args, ...arguments ]
合并参数
函数也可以 new
,所以要判断原型 this instanceof fn
然后实现原型继承,如果对原型不太了解的话,请移步我上一篇文章 助力进击大厂,JavaScript 前端考点总结
call、apply、bind 的区别
都可以改变 this
指向
call 和 apply 会立即执行
,bind 不会,而是返回一个函数
call 和 bind 可以接收多个参数
,apply
只能接受两个,第二个是数组
bind 参数可以分多次传入
instanceof 说明在注释里,接受两个参数,判断第二个参数是不是在第一个参数的原型链上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function myInstanceof (left, right ) { let left = Object .getPrototypeOf(left) let prototype = right.prototype while (true ) { if (left === null ) return false if (prototype === left) return true left = Object .getPrototypeOf(left) } }
数组去重 个人感觉这个还蛮喜欢考的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 let arr = [ 1 , 1 , "1" , "1" , true , true , "true" , {}, {}, "{}" , null , null , undefined , undefined , ] let unique1 = Array .from(new Set (arr))console .log(unique1) let unique2 = (arr ) => { let map = new Map () let brr = [] arr.forEach((item ) => { if (!map.has(item)) { map.set(item, true ) brr.push(item) } }) return brr } console .log(unique2(arr)) let unique3 = (arr ) => { let brr = [] arr.forEach((item ) => { if (brr.indexOf(item) === -1 ) brr.push(item) if (!brr.includes(item)) brr.push(item) }) return brr } console .log(unique3(arr)) let unique4 = (arr ) => { let brr = arr.filter((item, index ) => { return arr.indexOf(item) === index }) return brr } console .log(unique4(arr))
上面的方法不能对引用类型去重,除非指针一样,指针是可以去重的,比如下面这样是可以去重的
1 2 let crr = []let arr = [crr, crr]
数组扁平化 就是把多维数组变成一维数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let arr = [1 , [2 , [3 , [4 , [5 ]]]]]let brr1 = arr.flat(Infinity )console .log(brr1) let brr2 = JSON .parse("[" + JSON .stringify(arr).replace(/\[|\]/g , "" ) + "]" )console .log(brr2) let brr3 = (arr ) => { let crr = arr.reduce((pre, cur ) => { return pre.concat(Array .isArray(cur) ? brr3(cur) : cur) }, []) return crr } console .log(brr3(arr))
防抖 连续点击的情况下不会执行,只在最后一下点击过指定的秒数后才会执行
应用场景:点击按钮,输入框模糊查询,词语联想等
1 2 3 4 5 6 7 8 9 10 11 12 13 function debounce (fn, wait ) { let timeout = null return function ( ) { if (timeout !== null ) clearTimeout (timeout) timeout = setTimeout (fn, wait) } } function sayDebounce ( ) { console .log("防抖成功!" ) } btn.addEventListener("click" , debounce(sayDebounce, 1000 ))
节流 频繁触发的时候,比如滚动或连续点击,在指定的间隔时间内,只会执行一次
应用场景:点击按钮,监听滚动条,懒加载等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function throttle (fn, wait ) { let bool = true return function ( ) { if (!bool) return bool = false setTimeout (() => { fn.call(this , arguments ) btn = true }, wait) } } function throttle (fn, wait ) { let date = Date .now() return function ( ) { let now = Date .now() if (now - date > wait) { fn.call(this , arguments ) date = now } } } function sayThrottle ( ) { console .log("节流成功!" ) } btn.addEventListener("click" , throttle(sayThrottle, 1000 ))
new 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function myNew (fn, ...args ) { if (typeof fn !== "function" ) { throw new Error ("TypeError" ) } const newObj = Object .create(fn.prototype) const result = fn.apply(newObj, args) return result && (typeof result === "object" || typeof result == "function" ) ? result : newObj }
Object.create 1 2 3 4 5 function createObj (obj ) { function Fn ( ) {} Fn.prototype = obj return new Fn() }
创建一个空对象并修改原型,这没啥说的,一般传个 null 进去,这样创建出来没有原型的对象不会被原型污染,或者传要继承的对象原型
Es5 继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Parent ( ) {}Parent.prototype.getName = function ( ) { return '沐华' } function Child ( ) {}Child.prototype = Object .create(Parent.prototype) Child.prototype.constructor = Child Child.prototype = Object .create(Parent.prototype,{ constructor :{ value: Child, writable: true , enumerable: true , configurable: true , } }) console .log(new Child().getName)
ES5 的继承方式有很多种,什么原型链继承、组合继承、寄生式继承…等等,了解一种面试就够用了
Es6 继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Parent () { constructor (props ) { this .name = '沐华' } } class Child extends Parent { constructor (props, myAttr ) { super (props) } } console .log(new Child().name)
深拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function myEach (array, iteratee ) { let index = -1 const length = array.length while (++index < length) { iteratee(array[index], index) } return array } function myClone (target, map = new WeakMap () ) { if (target instanceof Object ) { const isArray = Array .isArray(target) let cloneTarget = isArray ? [] : {} if (map.get(target)) { return map.get(target) } map.set(target, cloneTarget) const keys = isArray ? undefined : Object .keys(target) myEach(keys || target, (value, key ) => { if (keys) { key = value } cloneTarget[key] = myClone(target[key], map) }) return cloneTarget } else { return target } }
获取数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function getType (value ) { if (value === null ) { return value + "" } if (typeof value === "object" ) { let valueClass = Object .prototype.toString.call(value) let type = valueClass.split(" " )[1 ].split("" ) type.pop() return type.join("" ).toLowerCase() } else { return typeof value } } console .log(getType(1 )) console .log(getType("1" )) console .log(getType(null )) console .log(getType(undefined )) console .log(getType({})) console .log(getType(function ( ) {}))
函数柯里化 柯里化函数就是高阶函数的一种,好处主要是实现参数的复用和延迟执行,不过性能上就会没那么好,要创建数组存参数,要创建闭包,而且存取 argements 比存取命名参数要慢一点
实现 add(1)(2)(3)
要求参数不固定,类似 add(1)(2, 3, 4)(5)()
这样也行,我这实现的是中间的不能不传参数,最后一个不传参数,以此来区分是最后一次调用然后累计结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function reduce (...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 add = currying(reduce)console .log(add(1 )(2 , 3 , 4 )(5 )()) console .log(add(1 )(2 , 3 )(4 , 5 )())
AJAX 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 myAjax({ type: "get" , url: "https://xxx" , data: { name : "沐华" , age : 18 }, dataType: "json" , async : true , success: function (data ) { console .log(data) }, error: function ( ) { alert("报错" ) }, }) function fn (data ) { let arr = [] for (let i in data) { arr.push(i + "=" + data[i]) } return arr.join("&" ) } function myAjax (options ) { let xhr = null let str = fn(options.data) if (window .XMLHttpRequest) { xhr = new XMLHttpRequest() } else { xhr = new ActiveXObject("Microsoft,XMLHTTP" ) } if (options.type === "get" && options.data !== undefined ) { xhr.open(options.type, options.url + "?" + str, options.async || true ) xhr.send(null ) } else if (options.type === "post" && options.data !== undefined ) { xhr.open(options.type, options.url, options.async || true ) xhr.setRequestHeaders("Content-type" , "application/x-www-form-urlencoede" ) xhr.send(str) } else { xhr.open(options.type, options.url, options.async || true ) xhr.send(null ) } xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 && xhr.status === 200 ) { let res = xhr.responseText try { if (options.success === undefined ) { return xhr.responseText } else if (typeof res === "object" ) { options.success(res) } else if (options.dataType === "json" ) { options.success(JSON .parse(res)) } else { throw new Error () } } catch (e) { if (options.error !== undefined ) { options.error() throw new Error () } else { throw new Error () } } } } }
Promise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class MyPromise { constructor (fn ) { this .callbacks = [] const resolve = (value ) => { this .data = value while (this .callbacks.length) { let cb = this .callbacks.shift() cb(value) } } fn(resolve) } then (onResolvedCallback ) { return new MyPromise((resolve ) => { this .callbacks.push(() => { const res = onResolvedCallback(this .data) if (res instanceof MyPromise) { res.then(resolve) } else { resolve(res) } }) }) } } new MyPromise((resolve ) => { setTimeout (() => { resolve(1 ) }, 1000 ) }) .then((res ) => { console .log(res) return new MyPromise((resolve ) => { setTimeout (() => { resolve(2 ) }, 1000 ) }) }) .then((res ) => { console .log(res) })
完整的 Promise 实在太长了,比 AJAX 还要长很多很多,所以就实现个极简版的,只有 resolve
和 then
方法,可以无限 .then
Promise.all Promise.all 可以把多个 Promise 实例打包成一个新的 Promise 实例。传进去一个值为多个 Promise 对象的数组,成功的时候返回一个结果的数组,返回值的顺序和传进去的顺序是一致对应得上的,如果失败的话就返回最先 reject 状态的值
如果遇到需要同时发送多个请求并且按顺序返回结果的话,Promise.all 就可以完美解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 MyPromise.all = function (promisesList ) { let arr = [] return new MyPromise((resolve, reject ) => { if (!promisesList.length) resolve([]) for (const promise of promisesList) { promise.then((res ) => { arr.push(res) if (arr.length === promisesList.length) { resolve(arr) } }, reject) } }) }
Promise.race 传参和上面的 all 一模一样,传入一个 Promise 实例集合的数组,然后全部同时执行,谁先快先执行完就返回谁,只返回一个结果
1 2 3 4 5 6 7 8 9 MyPromise.race = function (promisesList ) { return new MyPromise((resolve, reject ) => { for (const promise of promisesList) { promise.then(resolve, reject) } }) }
双向数据绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let obj = {}let input = document .getElementById("input" )let box = document .getElementById("box" )Object .defineProperty(obj, "text" , { configurable: true , enumerable: true , get ( ) { console .log("获取数据了" ) }, set (newVal ) { console .log("数据更新了" ) input.value = newVal box.innerHTML = newVal }, }) input.addEventListener("keyup" , function (e ) { obj.text = e.target.value })
路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class myRoute { constructor ( ) { this .routes = {} this .currentHash = "" this .freshRoute = this .freshRoute.bind(this ) window .addEventListener("load" , this .freshRoute, false ) window .addEventListener("hashchange" , this .freshRoute, false ) } storeRoute (path, cb ) { this .routes[path] = cb || function ( ) {} } freshRoute ( ) { this .currentHash = location.hash.slice(1 ) || "/" this .routes[this .currentHash]() } }