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 为 true
let next
try {
// 如果 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 3
async(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.prototype
Object.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) {
// 查到原型链顶端,仍未查到,返回false
if (CaseProto == null) return false;
// 找到相同的原型
if (CaseProto === Constructor.prototype) return true;
CaseProto = Object.getPrototypeOf(CaseProto);
}
}
// or
function 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 和 +0
return x !== 0 || 1 / x === 1 / y
}
// 防止NaN
return 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 = proto
const 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
}
// or
function create(targetObj) {
function F() {}
F.prototype = targetObj || {};
const result = new F();
result.__proto__.__proto__ = Object.prototype.__proto__;
return result;
}
// or
function create(obj) {
function F() {}
F.prototype = obj
return 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)) {
// 递归调用, 深度克隆对象, 且传入缓存容器map
cloneTarget[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 >>> 0
let 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,最终结果为false
var 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,最终结果为undefined
for (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.length
for (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 + start
const 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)) // true
console.log([1, 2, 3, NaN].sx_includes(NaN)) // true
console.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,3
console.log([1, 2, 3].sx_join('*')) // 1*2*3
flat
Array.prototype.sx_flat = function () {
let arr = this
while (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 || 0
if (begin >= array.length) {
return []
}
// 如果end不大于begin, 直接返回[]
end = end || array.length
if (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;
}
// or
Function.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与args2
return 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;
}
// or
Function.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;
};
// or
Function.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;
}
// or
Function.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 || 1
switch (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 : start
end = !end && end !== 0 ? this.length : end
if (start >= end) return ''
let str = ''
for (let i = start; i < end; i++) {
str += this[i]
}
return str
}
console.log(str.sx_slice(2)) // nshine_lin
console.log(str.sx_slice(-2)) // in
console.log(str.sx_slice(-9, 10)) // shine_l
console.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 : start
length = (!length && length !== 0) || length > this.length - start ? this.length : start + length
let str = ''
for (let i = start; i < length; i++) {
str += this[i]
}
return str
}
console.log(str.sx_substr(3)) // shine_lin
console.log(str.sx_substr(3, 3)) // shi
console.log(str.sx_substr(5, 300)) // ine_lin
substring
String.prototype.sx_sunstring = function (start = 0, end) {
start = start < 0 ? this.length + start : start
end = !end && end !== 0 ? this.length : end
if (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_lin
console.log(str.sx_sunstring(-2)) // in
console.log(str.sx_sunstring(-9, 10)) // shine_l
console.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) // 队列中追加cb
return 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 如果是数组的话直接插入key
res.push(isArr?data[item]:`"${item}":"${data[item]}"`);
}
}
return res.join(',');
}
return '{'+fn(da)+"}"
}
console.log(stringify(str))
// or
function 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函数用以在返回之前对所得到的对象执行变换(操作)。
// 直接调用 eval
function 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)
}
})()