ES6
实现 async/await
利用 generator() 实现 async/await 主要就是用一个函数(自动执行器)来包装 generator(),从而实现自动执行 generator()。 每次执行 next() 返回的 { value, done } 中的 value 是一个 Promise,所以要等它执行完毕后再执行下一次 next()。 即在它的后面加一个 then() 函数,并且在 then() 函数中执行 next()。
function t(data) {return new Promise(r => setTimeout(() => r(data), 100))}function *test() {const data1 = yield t(1)console.log(data1)const data2 = yield t(2)console.log(data2)return 3}function async(generator) {return new Promise((resolve, reject) => {const gen = generator()function step(nextFun) {// 每一次 next() 都是返回这样的数据 { value: xx, done: false },结束时 done 为 truelet nexttry {// 如果 generator() 执行报错,需要 reject 给外面的 catch 函数next = nextFun()} catch(e) {return reject(e)}// done: true 代表 generator() 结束了if (next.done) {return resolve(next.value)}Promise.resolve(next.value).then((val) => step(() => gen.next(val)), // 通过 next(val) 将 val 传给 yield 后面的变量(err) => step(() => gen.trhow(err)),)}step(() => gen.next())})}// 1 2 3async(test).then(val => console.log(val))
Object 对象
new操作符
- new做了什么?
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性) ;
- 返回新对象
// 方法一function myNew() {// 通过arguments类数组打印出的结果,我们可以看到其中包含了构造函数以及我们调用objectfactory时传入的其他参数// 接下来就是要想如何得到其中这个构造函数和其他的参数// 由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:// 1. Array.from(arguments).shift(); //转换成数组 使用数组的方法shift将第一项弹出// 2.[].shift().call(arguments); // 通过call() 让arguments能够借用shift方法const Constructor = [].shift.call(arguments);const args = arguments;// 新建一个空对象 纯洁无邪let obj = new Object();// 接下来的想法 给obj这个新生对象的原型指向它的构造函数的原型// 给构造函数传入属性,注意:构造函数的this属性// 参数传进Constructor对obj的属性赋值,this要指向obj对象// 在Coustructor内部手动指定函数执行时的this 使用call、apply实现Constructor.call(obj, ...args);return obj;}// 方法二function createNew() {//创建一个空对象let obj = new Object();//获取构造函数let Constructor = [].shift.call(arguments);//链接到原型obj.__proto__ = Constructor.prototype;//绑定this值let result = Constructor.apply(obj, arguments); //使用apply,将构造函数中的this指向新对象,这样新对象就可以访问构造函数中的属性和方法//返回新对象return typeof result === "object" ? result : obj; //如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象}
function create(Con, ...args) {let obj = {}// 等同于 obj.__proto__ = Con.prototypeObject.setPrototypeOf(obj, Con.prototype)let result = Con.apply(obj, args)return result instanceof Object ? result : obj}
entries
将对象中的可枚举属性转换为键值对数组
Object.prototype.entries = function (obj) {const res = []for (let key in obj) {obj.hasOwnProperty(key) && res.push([key, obj[key]])}return res}console.log(Object.entries(obj))
fromEntries
将键值对数组转为对象
Object.prototype.fromEntries = function (arr) {const obj = {}for (let i = 0; i < arr.length; i++) {const [key, value] = arr[i]obj[key] = value}return obj}
key
返回对象的key组成的数组
Object.prototype.keys = function (obj) {const keys = []for (let key in obj) {obj.hasOwnProperty(key) && res.push(key)}return keys}console.log(Object.keys(obj))
instanceof
判断a是否是b的实例
function instance_of(Case, Constructor) {// 基本数据类型返回false// 兼容一下函数对象if ((typeof(Case) != 'object' && typeof(Case) != 'function') || Case == 'null') return false;let CaseProto = Object.getPrototypeOf(Case);while (true) {// 查到原型链顶端,仍未查到,返回falseif (CaseProto == null) return false;// 找到相同的原型if (CaseProto === Constructor.prototype) return true;CaseProto = Object.getPrototypeOf(CaseProto);}}// orfunction instanceOf(left, right) {let leftValue = left.__proto__;let rightValue = right.prototype;while (true) {if (leftValue === null) {return false;}if (leftValue === rightValue) return true;leftValue = leftValue.__proto__; // 疯狂往上找原型,找到顶层object的原型就是null,表示都没找到}}
is
Object.is(a, b) 判断a是否等于b
Object.prototype.is = function (x, y) {if (x === y) {// 防止 -0 和 +0return x !== 0 || 1 / x === 1 / y}// 防止NaNreturn x !== x && y !== y}
Object.create
传入对象作为参数, 返回对象, 该对象的原型是传入的对象
function newCreate(proto, propertiesObject) {if (typeof proto !== 'object' && typeof proto !== 'function') {throw TypeError('Object prototype may only be an Object: ' + proto)}function F() { }F.prototype = protoconst o = new F()if (propertiesObject !== undefined) {Object.keys(propertiesObject).forEach(prop => {let desc = propertiesObject[prop]if (typeof desc !== 'object' || desc === null) {throw TypeError('Object prorotype may only be an Object: ' + desc)} else {Object.defineProperty(o, prop, desc)}})}return o}// orfunction create(targetObj) {function F() {}F.prototype = targetObj || {};const result = new F();result.__proto__.__proto__ = Object.prototype.__proto__;return result;}// orfunction create(obj) {function F() {}F.prototype = objreturn new F()}
Object.assign
assign接受多个对象, 并将多个对象合成一个对象 这些对象如果有重名属性, 则后来的覆盖先前的 assign返回一个对象, 这个 对象===第一个对象 等于是对第一个对象的属性增加和覆盖
Object.prototype.assign = function (target, ...args) {if (target === null || target === undefined) {throw new TypeError('Cannot convert undefined or null to object')}target = Object(target)for (let nextObj of args) {for (let key in nextObj) {nextObj.hasOwnProperty(key) && (target[key] = nextObj[key])}}return target}
深拷贝
- 实现一: 大众乞丐版
- 问题1: 函数属性会丢失
- 问题2: 循环引用会出错
- 实现二: 面试基础版
- 解决问题1: 函数属性还没丢失
- 实现三: 面试加强版本
- 解决问题2: 循环引用正常
- 实现四: 面试加强版本2(优化遍历性能)
- 数组: while | for | forEach() 优于 for-in | keys()&forEach()
- 对象: for-in 与 keys()&forEach() 差不多
1). 大众乞丐版问题1: 函数属性会丢失问题2: 循环引用会出错*/export function deepClone1(target) {return JSON.parse(JSON.stringify(target))}/*2). 面试基础版本解决问题1: 函数属性还没丢失*/export function deepClone2 (target) {if (target!==null && typeof target==='object') {const cloneTarget = target instanceof Array ? [] : {}for (const key in target) {if (target.hasOwnProperty(key)) {cloneTarget[key] = deepClone2(target[key])}}return cloneTarget}return target}/*3). 面试加强版本解决问题2: 循环引用正常*/export function deepClone3 (target, map=new Map()) {if (target!==null && typeof target==='object') {// 从缓存容器中读取克隆对象let cloneTarget = map.get(target)// 如果存在, 返回前面缓存的克隆对象if (cloneTarget) {return cloneTarget}// 创建克隆对象(可能是{}或者[])cloneTarget = target instanceof Array ? [] : {}// 缓存到map中map.set(target, cloneTarget)for (const key in target) {if (target.hasOwnProperty(key)) {// 递归调用, 深度克隆对象, 且传入缓存容器mapcloneTarget[key] = deepClone3(target[key], map)}}return cloneTarget}return target}
Array 数组
reverse
可以通过判断数组长度是奇数还是偶数来减少循环条件 譬如: 2只需要换一次, 3也只需要换一次 即 count = len%2 ===0? len/2 : len-1/2
// 改变原数组Array.prototype.myReverse = function () {var len = this.length;for (var i = 0; i < len; i++) {var temp = this[i];this[i] = this[len - 1 - i];this[len - 1 - i] = temp;}return this;}
forEach
Array.prototype.myForEach = function (func, obj) {var len = this.length;var _this = arguments[1] ? arguments[1] : window;// var _this=arguments[1]||window;for (var i = 0; i < len; i++) {func.call(_this, this[i], i, this)}}Array.prototype.myForEach = function (callbackFn) {// 判断this是否合法if (this === null || this === undefined) {throw new TypeError("Cannot read property 'myForEach' of null");}// 判断callbackFn是否合法if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {throw new TypeError(callbackFn + ' is not a function')}// 取到执行方法的数组对象和传入的this对象var _arr = this, thisArg = arguments[1] || window;for (var i = 0; i < _arr.length; i++) {// 执行回调函数callbackFn.call(thisArg, _arr[i], i, _arr);}}
??? map
Array.prototype.map2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')}if (typeof callback !== "function") {throw new TypeError(callback + ' is not a function')}const O = Object(this)const len = O.length >>> 0let k = 0, res = []while (k < len) {if (k in O) {res[k] = callback.call(thisArg, O[k], k, O);}k++;}return res}
filter
Array.prototype.myFilter = function (func, obj) {var len = this.length;var arr = [];var _this = arguments[1] || window;for (var i = 0; i < len; i++) {func.call(_this, this[i], i, this) && arr.push(this[i]);}return arr;}
reduce
var selfReduce = function(fn, initialValue) {var arr = ([]).slice.call(this);// 通过判断入参长度,可以避免过滤initialValue传入的非法值(0,undifind,...)if(arguments.length === 2) {arr.unshift(initialValue);}var result = arr[0];for(var i = 1; i < arr.length; i++) {if(!arr.hasOwnProperty(i)) {continue;}// 将第一次的出参作为第二次的入参result = fn.call(null, result, arr[i], i, this);}return result;}
every
Array.prototype.myEvery = function (func) {var flag = true;var len = this.length;var _this = arguments[1] || window;for (var i = 0; i < len; i++) {if (func.apply(_this, [this[i], i, this]) == false) {flag = false;break;}}return flag;}
some
Array.prototype.mySome = function(callbackFn) {var _arr = this, thisArg = arguments[1] || window;// 开始标识值为false// 遇到回调返回true,直接返回true// 如果循环执行完毕,意味着所有回调返回值为false,最终结果为falsevar flag = false;for (var i = 0; i<_arr.length; i++) {// 回调函数执行为false,函数中断if (callbackFn.call(thisArg, _arr[i], i, _arr)) {return true;}}return flag;}
find / findIndex
Array.prototype.myFind = function(callbackFn) {var _arr = this, thisArg = arguments[1] || window;// 遇到回调返回true,直接返回该数组元素// 如果循环执行完毕,意味着所有回调返回值为false,最终结果为undefinedfor (var i = 0; i<_arr.length; i++) {// 回调函数执行为false,函数中断if (callbackFn.call(thisArg, _arr[i], i, _arr)) {return _arr[i];}}return undefined;}
indexOf
function indexOf(findVal, beginIndex = 0) {if (this.length < 1 || beginIndex > findVal.length) {return -1;}if (!findVal) {return 0;}beginIndex = beginIndex <= 0 ? 0 : beginIndex;for (let i = beginIndex; i < this.length; i++) {if (this[i] == findVal) return i;}return -1;}
fill
Array.prototype.sx_fill = function (value, start = 0, end) {end = end || this.lengthfor (let i = start; i < end; i++) {this[i] = value}return this}
includes
Array.prototype.sx_includes = function (value, start = 0) {if (start < 0) start = this.length + startconst isNaN = Number.isNaN(value)for (let i = start; i < this.length; i++) {if (this[i] === value || Number.isNaN(this[i]) === isNaN) {return true}}return false}console.log([1, 2, 3].sx_includes(2)) // trueconsole.log([1, 2, 3, NaN].sx_includes(NaN)) // trueconsole.log([1, 2, 3].sx_includes(1, 1)) // false
join
Array.prototype.sx_join = function (s = ',') {let str = ''for(let i = 0; i < this.length; i++) {str = i === 0 ? `${str}${this[i]}` : `${str}${s}${this[i]}`}return str}console.log([1, 2, 3].sx_join()) // 1,2,3console.log([1, 2, 3].sx_join('*')) // 1*2*3
flat
Array.prototype.sx_flat = function () {let arr = thiswhile (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr)}return arr}const testArr = [1, [2, 3, [4, 5]], [8, 9]]console.log(testArr.sx_flat())// [1, 2, 3, 4, 5, 8, 9]
push
let arr = [];Array.prototype.push = function() {for( let i = 0 ; i < arguments.length ; i++){this[this.length] = arguments[i] ;}return this.length;}
pop
find / findIndex
function find (array, callback) {for (let index = 0; index < array.length; index++) {if (callback(array[index], index)) {return array[index]}}return undefined}function findIndex (array, callback) {for (let index = 0; index < array.length; index++) {if (callback(array[index], index)) {return index}}return -1}
concat
- 语法: var new_array = concat(array, value1[, value2[, …[, valueN]]])
- 功能: 将n个数组或值与当前数组合并生成一个新数组, 原始数组不会被改变
export function concat (array, ...values) {const arr = [...array]values.forEach(value => {if (Array.isArray(value)) {arr.push(...value)} else {arr.push(value)}})return arr}
slice
- 语法: var new_array = slice(array, [begin[, end]])
- 功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变
function slice (array, begin, end) {// 如果当前数组是[], 直接返回[]if (array.length === 0) {return []}// 如果begin超过最大下标, 直接返回[]begin = begin || 0if (begin >= array.length) {return []}// 如果end不大于begin, 直接返回[]end = end || array.lengthif (end > array.length) {end = array.length}if (end <= begin) {return []}// 取出下标在[begin, end)区间的元素, 并保存到最终的数组中const arr = []for (let index = begin; index < end; index++) {arr.push(array[index])}return arr}
数组取差异
- 语法: difference(arr1, arr2)
- 功能: 得到当前数组中所有不在arr中的元素组成的数组(不改变原数组)
- 例子: difference([1,3,5,7], [5, 8]) ==> [1, 3, 7]
export function difference (arr1, arr2) {if (arr1.length===0) {return []} else if (arr2.length===0) {return arr1.slice()}return arr1.filter(item => arr2.indexOf(item)===-1)}
删除数组中部分元素
- pull(array, …values):
- 删除原数组中与value相同的元素, 返回所有删除元素的数组
- 说明: 原数组发生了改变
- 如: pull([1,3,5,3,7], 2, 7, 3, 7) ===> 原数组变为[1, 5], 返回值为[3,3,7]
- pullAll(array, values):
- 功能与pull一致, 只是参数变为数组
- 如: pullAll([1,3,5,3,7], [2, 7, 3, 7]) ===> 数组1变为[1, 5], 返回值为[3,3,7]
export function pull (array, ...values) {if (array.length===0 || values.length===0) {return []}var result = []for (let index = 0; index < array.length; index++) {const item = array[index]if (values.indexOf(item)!==-1) {array.splice(index, 1)result.push(item)index--}}return result}export function pullAll (array, values) {if (!values || !Array.isArray(values)) {return []}return pull(array, ...values)}
得到数组的部分元素
- drop(array, count)
- 得到当前数组过滤掉左边count个后剩余元素组成的数组
- 说明: 不改变当前数组, count默认是1
- 如: drop([1,3,5,7], 2) ===> [5, 7]
- dropRight(array, count)
- 得到当前数组过滤掉右边count个后剩余元素组成的数组
- 说明: 不改变当前数组, count默认是1
- 如: dropRight([1,3,5,7], 2) ===> [1, 3]
export function drop (array, count=1) {if (array.length === 0 || count >= array.length) {return []}return array.filter((item, index) => index>=count)}export function dropRight (array, count=1) {if (array.length === 0 || count >= array.length) {return []}return array.filter((item, index) => index < array.length-count)
Function 函数
bind
bind(thisVal, params1, params2,…) 接受多个参数
- 第一个参数作为this指向, 如果该值为null或者undefined, 则this指向全局this, 也就是window
- 剩余参数单个形式传入, 作为绑定的函数参数
返回
- 返回改变了this指向和传入参数的函数, 不自动执行
注意点
- bind()返回的函数也接收参数,这两部分的参数都要传给返回的函数
- new的优先级:如果bind绑定后的函数被new了,那么此时this指向就发生改变。此时的this就是当前函数的实例
- 没有保留原函数在原型链上的属性和方法
关键实现 Function.prototype.myBind = function(thisArg, …args) { return () => { this.apply(thisArg, args) } }
Function.prototype.myBind = function (target) {var target = target || window;var _args1 = [].slice.call(arguments, 1);var self = this;var temp = function () {};var F = function () {var _args2 = [].slice.call(arguments, 0);var parasArr = _args1.concat(_args2);return self.apply(this instanceof temp ? this : target, parasArr)}temp.prototype = self.prototype;F.prototype = new temp();return F;}// orFunction.prototype.myBind = function (thisArg, ...args) {if (typeof this !== "function") {throw TypeError("Bind must be called on a function")}var self = this// new优先级var fbound = function () {self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))}// 继承原型上的属性和方法fbound.prototype = Object.create(self.prototype);return fbound;}// 乞丐版import {call} from './call'/*自定义函数对象的bind方法*/function bind(fn, obj, ...args) {// 返回一个新函数return (... args2) => {// 通过call调用原函数, 并指定this为obj, 实参为args与args2return call(fn, obj, ...args, ...args2)}// 简洁有效版Function.prototype.bind = function( context ){var self = this; // 保存原函数return function(){ // 返回一个新的函数return self.apply( context, arguments ); // 执行新的函数的时候,会把之前传入的 context// 当作新函数体内的 this}};// 至尊版Function.prototype.bind = function(){var self = this, // 保存原函数context = [].shift.call( arguments ), // 需要绑定的 this 上下文args = [].slice.call( arguments ); // 剩余的参数转成数组return function(){ // 返回一个新的函数return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );// 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this// 并且组合两次分别传入的参数,作为新函数的参数}};
call
使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
call(thisVal, params1, params2,…)
接受多个参数
- 第一个参数作为this指向, 如果该值为null或者undefined, 则this指向全局this, 也就是window
- 剩余参数单个形式传入, 作为绑定的函数参数
注意
- 修改this指向并立即执行函数
Function.prototype.myCall = function (thisVal, ...args) {var ctx = thisVal || window;ctx.fn = this;var result = ctx.fn(...args);delete ctx.fn;return result;}// orFunction.prototype.call = function call(context, ...params) {if (context == null) context = window;if (!/^(object|function)$/.test(typeof context)) context = Object(context);let self = this,key = Symbol('KEY'),result;context[key] = self;result = context[key](...params);Reflect.deleteProperty(context, key);return result;};// orFunction.prototype.myCall = function(thisArg, ...args) {thisArg.fn = this // this指向调用call的对象,即我们要改变this指向的函数return thisArg.fn(...args) // 执行函数并return其执行结果}// 细节优化Function.prototype.myCall = function(thisArg, ...args) {if(typeof this !== 'function') {throw new TypeError('error')}const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性thisArg = thisArg || window // 若没有传入this, 默认绑定window对象thisArg[fn] = this //thisArg.fn = this // this指向调用call的对象,即我们要改变this指向的函数const result = thisArg[fn](...args) // 执行当前函数delete thisArg[fn] // 删除我们声明的fn属性return result // 返回函数执行结果}
apply
call(thisVal, [params1, params2,…])
接受多个参数
- 第一个参数作为this指向, 如果该值为null或者undefined, 则this指向全局this, 也就是window
- 剩余参数以数组形式传入, 作为绑定的函数参数
注意
- 修改this指向并立即执行函数
Function.prototype.myApply = function (thisVal, args) {var ctx = thisVal || window;ctx.fn = this;if (!args) {var result = ctx.fn();delete ctx.fn;return result;}var result = ctx.fn(...args);delete ctx.fn;return result;}// orFunction.prototype.myApply = function(thisArg, args) {if(typeof this !== 'function') {throw new TypeError('error')}const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性thisArg = thisArg || window // 若没有传入this, 默认绑定window对象thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数const result = thisArg[fn](...args) // 执行当前函数delete thisArg[fn] // 删除我们声明的fn属性return result // 返回函数执行结果}
String 字符串
trim
const trim = function(str, type) { // 去除空格, type: 1-所有空格 2-前后空格 3-前空格 4-后空格type = type || 1switch (type) {case 1:return str.replace(/\s+/g, '')case 2:return str.replace(/(^\s*)|(\s*$)/g, '')case 3:return str.replace(/(^\s*)/g, '')case 4:return str.replace(/(\s*$)/g, '')default:return str}}
split
function getParams(url) {const res = {}if (url.includes('?')) {const str = url.split('?')[1]const arr = str.split('&')arr.forEach(item => {const key = item.split('=')[0]const val = item.split('=')[1]res[key] = decodeURIComponent(val) // 解码})}return res}// 测试const user = getParams('http://www.baidu.com?user=%E9%98%BF%E9%A3%9E&age=16')console.log(user) // { user: '阿飞', age: '16' }
slice
String.prototype.sx_slice = function (start = 0, end) {start = start < 0 ? this.length + start : startend = !end && end !== 0 ? this.length : endif (start >= end) return ''let str = ''for (let i = start; i < end; i++) {str += this[i]}return str}console.log(str.sx_slice(2)) // nshine_linconsole.log(str.sx_slice(-2)) // inconsole.log(str.sx_slice(-9, 10)) // shine_lconsole.log(str.sx_slice(5, 1)) // ''
substr
String.prototype.sx_substr = function (start = 0, length) {if (length < 0) return ''start = start < 0 ? this.length + start : startlength = (!length && length !== 0) || length > this.length - start ? this.length : start + lengthlet str = ''for (let i = start; i < length; i++) {str += this[i]}return str}console.log(str.sx_substr(3)) // shine_linconsole.log(str.sx_substr(3, 3)) // shiconsole.log(str.sx_substr(5, 300)) // ine_lin
substring
String.prototype.sx_sunstring = function (start = 0, end) {start = start < 0 ? this.length + start : startend = !end && end !== 0 ? this.length : endif (start >= end) [start, end] = [end, start]let str = ''for (let i = start; i < end; i++) {str += this[i]}return str}console.log(str.sx_sunstring(2)) // nshine_linconsole.log(str.sx_sunstring(-2)) // inconsole.log(str.sx_sunstring(-9, 10)) // shine_lconsole.log(str.sx_sunstring(5, 1)) // unsh
其他
发布订阅模式 Event.EventEmitter
// 发布订阅模式class EventEmitter {constructor() {this._events = {} // 初始化events事件对象}/*** 触发事件* 原理:将该事件增加到该事件类型的队列中* 状态:未执行* @param event 事件名称* @param cb 回调函数*/on(eventName, callback) {!this._events[eventName] && (this._events[eventName]=[]) // 是否需要初始化方法队列this._events.push(callback) // 队列中追加cbreturn this // 保证链式调用}/*** 取消事件* 原理:将所有该事件类型的事件从队列中删除* 状态:取消执行* @param event 事件名称* @param cb 回调函数*/remove(eventName, callback) {const target = this._events[eventName] // 先获取一下原事件队列target && (this._events[eventName] = target.filter(f => f != callback)) // 过滤掉事件队列中的待移除callback函数return this}/*** 触发事件* 原理:执行该事件类型的所有事件,按照队列顺序执行* 状态:准备执行 | 执行中* 使用方式:xx.emit(eventName, args)* @param args*/emit(eventName, ...args) {const target = this._events[eventName] // 获取事件队列// 执行事件队列中的回调函数数组target && target.forEach(fn => {fn.apply(this, args) // fn.call(this, ...args)})return this}/*** 单次触发事件* 原理:执行一次该事件* @param event* @param cb*/once(eventName, callback) {// 封装一个单次执行函数const fn = (...args) => {callback.apply(this, args) // 执行回调函数this.remove(eventName, fn) // 移除事件队列中所有的该类型的回调函数}this.on(eventName, fn) // 将单次执行函数添加到事件队列return this}}
手写一个观察者模式?
var events = (function() {var topics = {};return {// 注册监听函数subscribe: function(topic, handler) {if (!topics.hasOwnProperty(topic)) {topics[topic] = [];}topics[topic].push(handler);},// 发布事件,触发观察者回调事件publish: function(topic, info) {if (topics.hasOwnProperty(topic)) {topics[topic].forEach(function(handler) {handler(info);});}},// 移除主题的一个观察者的回调事件remove: function(topic, handler) {if (!topics.hasOwnProperty(topic)) return;var handlerIndex = -1;topics[topic].forEach(function(item, index) {if (item === handler) {handlerIndex = index;}});if (handlerIndex >= 0) {topics[topic].splice(handlerIndex, 1);}},// 移除主题的所有观察者的回调事件removeAll: function(topic) {if (topics.hasOwnProperty(topic)) {topics[topic] = [];}}};})();
详细资料可以参考:《JS 事件模型》
JSON
JSON.stringify()
JSON.stringify(value[, replacer [, space]]):
Boolean | Number| String类型会自动转换成对应的原始值。undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成null(出现在数组中时)。- 不可枚举的属性会被忽略
- 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
let str = {name:"dwadwad1",age:null,money:[{name:"odk"},2,3],child:{name:"dwadwa"}}function stringify(da) {let fn = function (data) {let res = []; // 定义存储的数组,循环里面添加key:value的字符串,然后用逗号连接let isArr = data instanceof Array; // 判断当前数据是否为数组for (let item in data){ // 循环遍历该数据if(data[item] instanceof Array){ // 如果该数据的value是数组 就插入 key:"[递归调用遍历数组]"res.push(`"${item}":"[${fn(data[item])}]"`);}else if(data[item] instanceof Object){ // 如果该数据是Object 那么就插入key:"{递归调用遍历object}" 如果当前数据是个数组 就返回数组的递归res.push(isArr?`{${fn(data[item])}}`:`"${item}":"{${fn(data[item])}}"`)}else{ // 都是不直接返回key:value 如果是数组的话直接插入keyres.push(isArr?data[item]:`"${item}":"${data[item]}"`);}}return res.join(',');}return '{'+fn(da)+"}"}console.log(stringify(str))// orfunction jsonStringify(obj) {let type = typeof obj;if (type !== "object") {if (/string|undefined|function/.test(type)) {obj = '"' + obj + '"';}return String(obj);} else {let json = []let arr = Array.isArray(obj)for (let k in obj) {let v = obj[k];let type = typeof v;if (/string|undefined|function/.test(type)) {v = '"' + v + '"';} else if (type === "object") {v = jsonStringify(v);}json.push((arr ? "" : '"' + k + '":') + String(v));}return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")}}jsonStringify({x : 5}) // "{"x":5}"jsonStringify([1, "false", false]) // "[1,"false",false]"jsonStringify({b: undefined}) // "{"b":"undefined"}"
JSON.parse()
JSON.parse(text[, reviver]) 用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。
// 直接调用 evalfunction jsonParse(opt) {return eval('(' + opt + ')');}jsonParse(jsonStringify({x : 5}))// Object { x: 5}jsonParse(jsonStringify([1, "false", false]))// [1, "false", falsr]jsonParse(jsonStringify({b: undefined}))// Object { b: "undefined"}// 避免在不必要的情况下使用 eval,eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。// Function : Function与eval有相同的字符串参数特性。var jsonStr = '{ "age": 20, "name": "jack" }'var json = (new Function('return ' + jsonStr))();//eval 与 Function 都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用
其他
简单路由实现
class Router {constructor() {this.routes = {};this.currentHash = '';this.changeRoute = this.changeRoute.bind(this);window.addEventListener('load', this.changeRoute, false);window.addEventListener('hashchange', this.changeRoute, false);}addRoute(path, fn) {this.routes[path] = fn || (() => {});}changeRoute() {this.changeRoute = location.hash.slice(1) || '/';const fn = this.routes[this.changeRoute];if (fn) {fn(); // 触发路由更新}}changePath(path) {window.location.hash = path;}}const router = new Router();router.addRoute('/path', () => {console.log('触发 /path 渲染对应的页面');});router.changePath('/path');
ajax
function ajax(requestConfig = {}) {const { method, url, async, data } = requestConfig;const xhr = new XMLHttpRequest();xhr.open(method, url, async);xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText);requestConfig.success(xhr.responseText);} else {requestConfig.error && requestConfig.error(xhr.responseText);}};xhr.onerror = function error(err) {console.err(err);requestConfig.error && requestConfig.error(err);};xhr.send(data);}
function ajax(requestConfig = {}) {const { method, url, async, data } = requestConfig;const xhr = new XMLHttpRequest();xhr.open(method, url, async);xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText);requestConfig.success(xhr.responseText);} else {requestConfig.error && requestConfig.error(xhr.responseText);}};xhr.onerror = function error(err) {console.err(err);requestConfig.error && requestConfig.error(err);};xhr.send(data);}
如何实现一个可设置过期时间的 localStorage
(function () {const getItem = localStorage.getItem.bind(localStorage)const setItem = localStorage.setItem.bind(localStorage)const removeItem = localStorage.removeItem.bind(localStorage)localStorage.getItem = function (key) {const expires = getItem(key + '_expires')if (expires && new Date() > new Date(Number(expires))) {removeItem(key)removeItem(key + '_expires')}return getItem(key)}localStorage.setItem = function (key, value, time) {if (typeof time !== 'undefined') {setItem(key + '_expires', new Date().getTime() + Number(time))}return setItem(key, value)}})()
