ES6

实现 async/await

利用 generator() 实现 async/await 主要就是用一个函数(自动执行器)来包装 generator(),从而实现自动执行 generator()。 每次执行 next() 返回的 { value, done } 中的 value 是一个 Promise,所以要等它执行完毕后再执行下一次 next()。 即在它的后面加一个 then() 函数,并且在 then() 函数中执行 next()。

  1. function t(data) {
  2. return new Promise(r => setTimeout(() => r(data), 100))
  3. }
  4. function *test() {
  5. const data1 = yield t(1)
  6. console.log(data1)
  7. const data2 = yield t(2)
  8. console.log(data2)
  9. return 3
  10. }
  11. function async(generator) {
  12. return new Promise((resolve, reject) => {
  13. const gen = generator()
  14. function step(nextFun) {
  15. // 每一次 next() 都是返回这样的数据 { value: xx, done: false },结束时 done 为 true
  16. let next
  17. try {
  18. // 如果 generator() 执行报错,需要 reject 给外面的 catch 函数
  19. next = nextFun()
  20. } catch(e) {
  21. return reject(e)
  22. }
  23. // done: true 代表 generator() 结束了
  24. if (next.done) {
  25. return resolve(next.value)
  26. }
  27. Promise.resolve(next.value).then(
  28. (val) => step(() => gen.next(val)), // 通过 next(val) 将 val 传给 yield 后面的变量
  29. (err) => step(() => gen.trhow(err)),
  30. )
  31. }
  32. step(() => gen.next())
  33. })
  34. }
  35. // 1 2 3
  36. async(test).then(val => console.log(val))

Object 对象

new操作符

  • new做了什么?
    • 创建一个新对象;
    • 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
    • 执行构造函数中的代码(为这个新对象添加属性) ;
    • 返回新对象
  1. // 方法一
  2. function myNew() {
  3. // 通过arguments类数组打印出的结果,我们可以看到其中包含了构造函数以及我们调用objectfactory时传入的其他参数
  4. // 接下来就是要想如何得到其中这个构造函数和其他的参数
  5. // 由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:
  6. // 1. Array.from(arguments).shift(); //转换成数组 使用数组的方法shift将第一项弹出
  7. // 2.[].shift().call(arguments); // 通过call() 让arguments能够借用shift方法
  8. const Constructor = [].shift.call(arguments);
  9. const args = arguments;
  10. // 新建一个空对象 纯洁无邪
  11. let obj = new Object();
  12. // 接下来的想法 给obj这个新生对象的原型指向它的构造函数的原型
  13. // 给构造函数传入属性,注意:构造函数的this属性
  14. // 参数传进Constructor对obj的属性赋值,this要指向obj对象
  15. // 在Coustructor内部手动指定函数执行时的this 使用call、apply实现
  16. Constructor.call(obj, ...args);
  17. return obj;
  18. }
  19. // 方法二
  20. function createNew() {
  21. //创建一个空对象
  22. let obj = new Object();
  23. //获取构造函数
  24. let Constructor = [].shift.call(arguments);
  25. //链接到原型
  26. obj.__proto__ = Constructor.prototype;
  27. //绑定this值
  28. let result = Constructor.apply(obj, arguments); //使用apply,将构造函数中的this指向新对象,这样新对象就可以访问构造函数中的属性和方法
  29. //返回新对象
  30. return typeof result === "object" ? result : obj; //如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象
  31. }
  1. function create(Con, ...args) {
  2. let obj = {}
  3. // 等同于 obj.__proto__ = Con.prototype
  4. Object.setPrototypeOf(obj, Con.prototype)
  5. let result = Con.apply(obj, args)
  6. return result instanceof Object ? result : obj
  7. }

entries

将对象中的可枚举属性转换为键值对数组

  1. Object.prototype.entries = function (obj) {
  2. const res = []
  3. for (let key in obj) {
  4. obj.hasOwnProperty(key) && res.push([key, obj[key]])
  5. }
  6. return res
  7. }
  8. console.log(Object.entries(obj))

fromEntries

将键值对数组转为对象

  1. Object.prototype.fromEntries = function (arr) {
  2. const obj = {}
  3. for (let i = 0; i < arr.length; i++) {
  4. const [key, value] = arr[i]
  5. obj[key] = value
  6. }
  7. return obj
  8. }

key

返回对象的key组成的数组

  1. Object.prototype.keys = function (obj) {
  2. const keys = []
  3. for (let key in obj) {
  4. obj.hasOwnProperty(key) && res.push(key)
  5. }
  6. return keys
  7. }
  8. console.log(Object.keys(obj))

instanceof

判断a是否是b的实例

  1. function instance_of(Case, Constructor) {
  2. // 基本数据类型返回false
  3. // 兼容一下函数对象
  4. if ((typeof(Case) != 'object' && typeof(Case) != 'function') || Case == 'null') return false;
  5. let CaseProto = Object.getPrototypeOf(Case);
  6. while (true) {
  7. // 查到原型链顶端,仍未查到,返回false
  8. if (CaseProto == null) return false;
  9. // 找到相同的原型
  10. if (CaseProto === Constructor.prototype) return true;
  11. CaseProto = Object.getPrototypeOf(CaseProto);
  12. }
  13. }
  14. // or
  15. function instanceOf(left, right) {
  16. let leftValue = left.__proto__;
  17. let rightValue = right.prototype;
  18. while (true) {
  19. if (leftValue === null) {
  20. return false;
  21. }
  22. if (leftValue === rightValue) return true;
  23. leftValue = leftValue.__proto__; // 疯狂往上找原型,找到顶层object的原型就是null,表示都没找到
  24. }
  25. }

is

Object.is(a, b) 判断a是否等于b

  1. Object.prototype.is = function (x, y) {
  2. if (x === y) {
  3. // 防止 -0 和 +0
  4. return x !== 0 || 1 / x === 1 / y
  5. }
  6. // 防止NaN
  7. return x !== x && y !== y
  8. }

Object.create

传入对象作为参数, 返回对象, 该对象的原型是传入的对象

  1. function newCreate(proto, propertiesObject) {
  2. if (typeof proto !== 'object' && typeof proto !== 'function') {
  3. throw TypeError('Object prototype may only be an Object: ' + proto)
  4. }
  5. function F() { }
  6. F.prototype = proto
  7. const o = new F()
  8. if (propertiesObject !== undefined) {
  9. Object.keys(propertiesObject).forEach(prop => {
  10. let desc = propertiesObject[prop]
  11. if (typeof desc !== 'object' || desc === null) {
  12. throw TypeError('Object prorotype may only be an Object: ' + desc)
  13. } else {
  14. Object.defineProperty(o, prop, desc)
  15. }
  16. })
  17. }
  18. return o
  19. }
  20. // or
  21. function create(targetObj) {
  22. function F() {}
  23. F.prototype = targetObj || {};
  24. const result = new F();
  25. result.__proto__.__proto__ = Object.prototype.__proto__;
  26. return result;
  27. }
  28. // or
  29. function create(obj) {
  30. function F() {}
  31. F.prototype = obj
  32. return new F()
  33. }

Object.assign

assign接受多个对象, 并将多个对象合成一个对象 这些对象如果有重名属性, 则后来的覆盖先前的 assign返回一个对象, 这个 对象===第一个对象 等于是对第一个对象的属性增加和覆盖

  1. Object.prototype.assign = function (target, ...args) {
  2. if (target === null || target === undefined) {
  3. throw new TypeError('Cannot convert undefined or null to object')
  4. }
  5. target = Object(target)
  6. for (let nextObj of args) {
  7. for (let key in nextObj) {
  8. nextObj.hasOwnProperty(key) && (target[key] = nextObj[key])
  9. }
  10. }
  11. return target
  12. }

深拷贝

  • 实现一: 大众乞丐版
    • 问题1: 函数属性会丢失
    • 问题2: 循环引用会出错
  • 实现二: 面试基础版
    • 解决问题1: 函数属性还没丢失
  • 实现三: 面试加强版本
    • 解决问题2: 循环引用正常
  • 实现四: 面试加强版本2(优化遍历性能)
    • 数组: while | for | forEach() 优于 for-in | keys()&forEach()
    • 对象: for-in 与 keys()&forEach() 差不多
  1. 1). 大众乞丐版
  2. 问题1: 函数属性会丢失
  3. 问题2: 循环引用会出错
  4. */
  5. export function deepClone1(target) {
  6. return JSON.parse(JSON.stringify(target))
  7. }
  8. /*
  9. 2). 面试基础版本
  10. 解决问题1: 函数属性还没丢失
  11. */
  12. export function deepClone2 (target) {
  13. if (target!==null && typeof target==='object') {
  14. const cloneTarget = target instanceof Array ? [] : {}
  15. for (const key in target) {
  16. if (target.hasOwnProperty(key)) {
  17. cloneTarget[key] = deepClone2(target[key])
  18. }
  19. }
  20. return cloneTarget
  21. }
  22. return target
  23. }
  24. /*
  25. 3). 面试加强版本
  26. 解决问题2: 循环引用正常
  27. */
  28. export function deepClone3 (target, map=new Map()) {
  29. if (target!==null && typeof target==='object') {
  30. // 从缓存容器中读取克隆对象
  31. let cloneTarget = map.get(target)
  32. // 如果存在, 返回前面缓存的克隆对象
  33. if (cloneTarget) {
  34. return cloneTarget
  35. }
  36. // 创建克隆对象(可能是{}或者[])
  37. cloneTarget = target instanceof Array ? [] : {}
  38. // 缓存到map中
  39. map.set(target, cloneTarget)
  40. for (const key in target) {
  41. if (target.hasOwnProperty(key)) {
  42. // 递归调用, 深度克隆对象, 且传入缓存容器map
  43. cloneTarget[key] = deepClone3(target[key], map)
  44. }
  45. }
  46. return cloneTarget
  47. }
  48. return target
  49. }

Array 数组

reverse

可以通过判断数组长度是奇数还是偶数来减少循环条件 譬如: 2只需要换一次, 3也只需要换一次 即 count = len%2 ===0? len/2 : len-1/2

  1. // 改变原数组
  2. Array.prototype.myReverse = function () {
  3. var len = this.length;
  4. for (var i = 0; i < len; i++) {
  5. var temp = this[i];
  6. this[i] = this[len - 1 - i];
  7. this[len - 1 - i] = temp;
  8. }
  9. return this;
  10. }

forEach

  1. Array.prototype.myForEach = function (func, obj) {
  2. var len = this.length;
  3. var _this = arguments[1] ? arguments[1] : window;
  4. // var _this=arguments[1]||window;
  5. for (var i = 0; i < len; i++) {
  6. func.call(_this, this[i], i, this)
  7. }
  8. }
  9. Array.prototype.myForEach = function (callbackFn) {
  10. // 判断this是否合法
  11. if (this === null || this === undefined) {
  12. throw new TypeError("Cannot read property 'myForEach' of null");
  13. }
  14. // 判断callbackFn是否合法
  15. if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {
  16. throw new TypeError(callbackFn + ' is not a function')
  17. }
  18. // 取到执行方法的数组对象和传入的this对象
  19. var _arr = this, thisArg = arguments[1] || window;
  20. for (var i = 0; i < _arr.length; i++) {
  21. // 执行回调函数
  22. callbackFn.call(thisArg, _arr[i], i, _arr);
  23. }
  24. }

??? map

  1. Array.prototype.map2 = function(callback, thisArg) {
  2. if (this == null) {
  3. throw new TypeError('this is null or not defined')
  4. }
  5. if (typeof callback !== "function") {
  6. throw new TypeError(callback + ' is not a function')
  7. }
  8. const O = Object(this)
  9. const len = O.length >>> 0
  10. let k = 0, res = []
  11. while (k < len) {
  12. if (k in O) {
  13. res[k] = callback.call(thisArg, O[k], k, O);
  14. }
  15. k++;
  16. }
  17. return res
  18. }

filter

  1. Array.prototype.myFilter = function (func, obj) {
  2. var len = this.length;
  3. var arr = [];
  4. var _this = arguments[1] || window;
  5. for (var i = 0; i < len; i++) {
  6. func.call(_this, this[i], i, this) && arr.push(this[i]);
  7. }
  8. return arr;
  9. }

reduce

  1. var selfReduce = function(fn, initialValue) {
  2. var arr = ([]).slice.call(this);
  3. // 通过判断入参长度,可以避免过滤initialValue传入的非法值(0,undifind,...)
  4. if(arguments.length === 2) {
  5. arr.unshift(initialValue);
  6. }
  7. var result = arr[0];
  8. for(var i = 1; i < arr.length; i++) {
  9. if(!arr.hasOwnProperty(i)) {
  10. continue;
  11. }
  12. // 将第一次的出参作为第二次的入参
  13. result = fn.call(null, result, arr[i], i, this);
  14. }
  15. return result;
  16. }

every

  1. Array.prototype.myEvery = function (func) {
  2. var flag = true;
  3. var len = this.length;
  4. var _this = arguments[1] || window;
  5. for (var i = 0; i < len; i++) {
  6. if (func.apply(_this, [this[i], i, this]) == false) {
  7. flag = false;
  8. break;
  9. }
  10. }
  11. return flag;
  12. }

some

  1. Array.prototype.mySome = function(callbackFn) {
  2. var _arr = this, thisArg = arguments[1] || window;
  3. // 开始标识值为false
  4. // 遇到回调返回true,直接返回true
  5. // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为false
  6. var flag = false;
  7. for (var i = 0; i<_arr.length; i++) {
  8. // 回调函数执行为false,函数中断
  9. if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
  10. return true;
  11. }
  12. }
  13. return flag;
  14. }

find / findIndex

  1. Array.prototype.myFind = function(callbackFn) {
  2. var _arr = this, thisArg = arguments[1] || window;
  3. // 遇到回调返回true,直接返回该数组元素
  4. // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为undefined
  5. for (var i = 0; i<_arr.length; i++) {
  6. // 回调函数执行为false,函数中断
  7. if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
  8. return _arr[i];
  9. }
  10. }
  11. return undefined;
  12. }

indexOf

  1. function indexOf(findVal, beginIndex = 0) {
  2. if (this.length < 1 || beginIndex > findVal.length) {
  3. return -1;
  4. }
  5. if (!findVal) {
  6. return 0;
  7. }
  8. beginIndex = beginIndex <= 0 ? 0 : beginIndex;
  9. for (let i = beginIndex; i < this.length; i++) {
  10. if (this[i] == findVal) return i;
  11. }
  12. return -1;
  13. }

fill

  1. Array.prototype.sx_fill = function (value, start = 0, end) {
  2. end = end || this.length
  3. for (let i = start; i < end; i++) {
  4. this[i] = value
  5. }
  6. return this
  7. }

includes

  1. Array.prototype.sx_includes = function (value, start = 0) {
  2. if (start < 0) start = this.length + start
  3. const isNaN = Number.isNaN(value)
  4. for (let i = start; i < this.length; i++) {
  5. if (this[i] === value || Number.isNaN(this[i]) === isNaN) {
  6. return true
  7. }
  8. }
  9. return false
  10. }
  11. console.log([1, 2, 3].sx_includes(2)) // true
  12. console.log([1, 2, 3, NaN].sx_includes(NaN)) // true
  13. console.log([1, 2, 3].sx_includes(1, 1)) // false

join

  1. Array.prototype.sx_join = function (s = ',') {
  2. let str = ''
  3. for(let i = 0; i < this.length; i++) {
  4. str = i === 0 ? `${str}${this[i]}` : `${str}${s}${this[i]}`
  5. }
  6. return str
  7. }
  8. console.log([1, 2, 3].sx_join()) // 1,2,3
  9. console.log([1, 2, 3].sx_join('*')) // 1*2*3

flat

  1. Array.prototype.sx_flat = function () {
  2. let arr = this
  3. while (arr.some(item => Array.isArray(item))) {
  4. arr = [].concat(...arr)
  5. }
  6. return arr
  7. }
  8. const testArr = [1, [2, 3, [4, 5]], [8, 9]]
  9. console.log(testArr.sx_flat())
  10. // [1, 2, 3, 4, 5, 8, 9]

push

  1. let arr = [];
  2. Array.prototype.push = function() {
  3. for( let i = 0 ; i < arguments.length ; i++){
  4. this[this.length] = arguments[i] ;
  5. }
  6. return this.length;
  7. }

pop

find / findIndex

  1. function find (array, callback) {
  2. for (let index = 0; index < array.length; index++) {
  3. if (callback(array[index], index)) {
  4. return array[index]
  5. }
  6. }
  7. return undefined
  8. }
  9. function findIndex (array, callback) {
  10. for (let index = 0; index < array.length; index++) {
  11. if (callback(array[index], index)) {
  12. return index
  13. }
  14. }
  15. return -1
  16. }

concat

  • 语法: var new_array = concat(array, value1[, value2[, …[, valueN]]])
  • 功能: 将n个数组或值与当前数组合并生成一个新数组, 原始数组不会被改变
  1. export function concat (array, ...values) {
  2. const arr = [...array]
  3. values.forEach(value => {
  4. if (Array.isArray(value)) {
  5. arr.push(...value)
  6. } else {
  7. arr.push(value)
  8. }
  9. })
  10. return arr
  11. }

slice

  • 语法: var new_array = slice(array, [begin[, end]])
  • 功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变
  1. function slice (array, begin, end) {
  2. // 如果当前数组是[], 直接返回[]
  3. if (array.length === 0) {
  4. return []
  5. }
  6. // 如果begin超过最大下标, 直接返回[]
  7. begin = begin || 0
  8. if (begin >= array.length) {
  9. return []
  10. }
  11. // 如果end不大于begin, 直接返回[]
  12. end = end || array.length
  13. if (end > array.length) {
  14. end = array.length
  15. }
  16. if (end <= begin) {
  17. return []
  18. }
  19. // 取出下标在[begin, end)区间的元素, 并保存到最终的数组中
  20. const arr = []
  21. for (let index = begin; index < end; index++) {
  22. arr.push(array[index])
  23. }
  24. return arr
  25. }

数组取差异

  • 语法: difference(arr1, arr2)
  • 功能: 得到当前数组中所有不在arr中的元素组成的数组(不改变原数组)
  • 例子: difference([1,3,5,7], [5, 8]) ==> [1, 3, 7]
  1. export function difference (arr1, arr2) {
  2. if (arr1.length===0) {
  3. return []
  4. } else if (arr2.length===0) {
  5. return arr1.slice()
  6. }
  7. return arr1.filter(item => arr2.indexOf(item)===-1)
  8. }

删除数组中部分元素

  • 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]
  1. export function pull (array, ...values) {
  2. if (array.length===0 || values.length===0) {
  3. return []
  4. }
  5. var result = []
  6. for (let index = 0; index < array.length; index++) {
  7. const item = array[index]
  8. if (values.indexOf(item)!==-1) {
  9. array.splice(index, 1)
  10. result.push(item)
  11. index--
  12. }
  13. }
  14. return result
  15. }
  16. export function pullAll (array, values) {
  17. if (!values || !Array.isArray(values)) {
  18. return []
  19. }
  20. return pull(array, ...values)
  21. }

得到数组的部分元素

  • 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]
  1. export function drop (array, count=1) {
  2. if (array.length === 0 || count >= array.length) {
  3. return []
  4. }
  5. return array.filter((item, index) => index>=count)
  6. }
  7. export function dropRight (array, count=1) {
  8. if (array.length === 0 || count >= array.length) {
  9. return []
  10. }
  11. 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) } }

  1. Function.prototype.myBind = function (target) {
  2. var target = target || window;
  3. var _args1 = [].slice.call(arguments, 1);
  4. var self = this;
  5. var temp = function () {};
  6. var F = function () {
  7. var _args2 = [].slice.call(arguments, 0);
  8. var parasArr = _args1.concat(_args2);
  9. return self.apply(this instanceof temp ? this : target, parasArr)
  10. }
  11. temp.prototype = self.prototype;
  12. F.prototype = new temp();
  13. return F;
  14. }
  15. // or
  16. Function.prototype.myBind = function (thisArg, ...args) {
  17. if (typeof this !== "function") {
  18. throw TypeError("Bind must be called on a function")
  19. }
  20. var self = this
  21. // new优先级
  22. var fbound = function () {
  23. self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
  24. }
  25. // 继承原型上的属性和方法
  26. fbound.prototype = Object.create(self.prototype);
  27. return fbound;
  28. }
  29. // 乞丐版
  30. import {call} from './call'
  31. /*
  32. 自定义函数对象的bind方法
  33. */
  34. function bind(fn, obj, ...args) {
  35. // 返回一个新函数
  36. return (... args2) => {
  37. // 通过call调用原函数, 并指定this为obj, 实参为args与args2
  38. return call(fn, obj, ...args, ...args2)
  39. }
  40. // 简洁有效版
  41. Function.prototype.bind = function( context ){
  42. var self = this; // 保存原函数
  43. return function(){ // 返回一个新的函数
  44. return self.apply( context, arguments ); // 执行新的函数的时候,会把之前传入的 context
  45. // 当作新函数体内的 this
  46. }
  47. };
  48. // 至尊版
  49. Function.prototype.bind = function(){
  50. var self = this, // 保存原函数
  51. context = [].shift.call( arguments ), // 需要绑定的 this 上下文
  52. args = [].slice.call( arguments ); // 剩余的参数转成数组
  53. return function(){ // 返回一个新的函数
  54. return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
  55. // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
  56. // 并且组合两次分别传入的参数,作为新函数的参数
  57. }
  58. };

call

使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

call(thisVal, params1, params2,…)

接受多个参数

  • 第一个参数作为this指向, 如果该值为null或者undefined, 则this指向全局this, 也就是window
  • 剩余参数单个形式传入, 作为绑定的函数参数

注意

  • 修改this指向并立即执行函数
  1. Function.prototype.myCall = function (thisVal, ...args) {
  2. var ctx = thisVal || window;
  3. ctx.fn = this;
  4. var result = ctx.fn(...args);
  5. delete ctx.fn;
  6. return result;
  7. }
  8. // or
  9. Function.prototype.call = function call(context, ...params) {
  10. if (context == null) context = window;
  11. if (!/^(object|function)$/.test(typeof context)) context = Object(context);
  12. let self = this,
  13. key = Symbol('KEY'),
  14. result;
  15. context[key] = self;
  16. result = context[key](...params);
  17. Reflect.deleteProperty(context, key);
  18. return result;
  19. };
  20. // or
  21. Function.prototype.myCall = function(thisArg, ...args) {
  22. thisArg.fn = this // this指向调用call的对象,即我们要改变this指向的函数
  23. return thisArg.fn(...args) // 执行函数并return其执行结果
  24. }
  25. // 细节优化
  26. Function.prototype.myCall = function(thisArg, ...args) {
  27. if(typeof this !== 'function') {
  28. throw new TypeError('error')
  29. }
  30. const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
  31. thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
  32. thisArg[fn] = this //thisArg.fn = this // this指向调用call的对象,即我们要改变this指向的函数
  33. const result = thisArg[fn](...args) // 执行当前函数
  34. delete thisArg[fn] // 删除我们声明的fn属性
  35. return result // 返回函数执行结果
  36. }

apply

call(thisVal, [params1, params2,…])

接受多个参数

  • 第一个参数作为this指向, 如果该值为null或者undefined, 则this指向全局this, 也就是window
  • 剩余参数以数组形式传入, 作为绑定的函数参数

注意

  • 修改this指向并立即执行函数
  1. Function.prototype.myApply = function (thisVal, args) {
  2. var ctx = thisVal || window;
  3. ctx.fn = this;
  4. if (!args) {
  5. var result = ctx.fn();
  6. delete ctx.fn;
  7. return result;
  8. }
  9. var result = ctx.fn(...args);
  10. delete ctx.fn;
  11. return result;
  12. }
  13. // or
  14. Function.prototype.myApply = function(thisArg, args) {
  15. if(typeof this !== 'function') {
  16. throw new TypeError('error')
  17. }
  18. const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
  19. thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
  20. thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数
  21. const result = thisArg[fn](...args) // 执行当前函数
  22. delete thisArg[fn] // 删除我们声明的fn属性
  23. return result // 返回函数执行结果
  24. }

String 字符串

trim

  1. const trim = function(str, type) { // 去除空格, type: 1-所有空格 2-前后空格 3-前空格 4-后空格
  2. type = type || 1
  3. switch (type) {
  4. case 1:
  5. return str.replace(/\s+/g, '')
  6. case 2:
  7. return str.replace(/(^\s*)|(\s*$)/g, '')
  8. case 3:
  9. return str.replace(/(^\s*)/g, '')
  10. case 4:
  11. return str.replace(/(\s*$)/g, '')
  12. default:
  13. return str
  14. }
  15. }

split

  1. function getParams(url) {
  2. const res = {}
  3. if (url.includes('?')) {
  4. const str = url.split('?')[1]
  5. const arr = str.split('&')
  6. arr.forEach(item => {
  7. const key = item.split('=')[0]
  8. const val = item.split('=')[1]
  9. res[key] = decodeURIComponent(val) // 解码
  10. })
  11. }
  12. return res
  13. }
  14. // 测试
  15. const user = getParams('http://www.baidu.com?user=%E9%98%BF%E9%A3%9E&age=16')
  16. console.log(user) // { user: '阿飞', age: '16' }

slice

  1. String.prototype.sx_slice = function (start = 0, end) {
  2. start = start < 0 ? this.length + start : start
  3. end = !end && end !== 0 ? this.length : end
  4. if (start >= end) return ''
  5. let str = ''
  6. for (let i = start; i < end; i++) {
  7. str += this[i]
  8. }
  9. return str
  10. }
  11. console.log(str.sx_slice(2)) // nshine_lin
  12. console.log(str.sx_slice(-2)) // in
  13. console.log(str.sx_slice(-9, 10)) // shine_l
  14. console.log(str.sx_slice(5, 1)) // ''

substr

  1. String.prototype.sx_substr = function (start = 0, length) {
  2. if (length < 0) return ''
  3. start = start < 0 ? this.length + start : start
  4. length = (!length && length !== 0) || length > this.length - start ? this.length : start + length
  5. let str = ''
  6. for (let i = start; i < length; i++) {
  7. str += this[i]
  8. }
  9. return str
  10. }
  11. console.log(str.sx_substr(3)) // shine_lin
  12. console.log(str.sx_substr(3, 3)) // shi
  13. console.log(str.sx_substr(5, 300)) // ine_lin

substring

  1. String.prototype.sx_sunstring = function (start = 0, end) {
  2. start = start < 0 ? this.length + start : start
  3. end = !end && end !== 0 ? this.length : end
  4. if (start >= end) [start, end] = [end, start]
  5. let str = ''
  6. for (let i = start; i < end; i++) {
  7. str += this[i]
  8. }
  9. return str
  10. }
  11. console.log(str.sx_sunstring(2)) // nshine_lin
  12. console.log(str.sx_sunstring(-2)) // in
  13. console.log(str.sx_sunstring(-9, 10)) // shine_l
  14. console.log(str.sx_sunstring(5, 1)) // unsh

其他

发布订阅模式 Event.EventEmitter

  1. // 发布订阅模式
  2. class EventEmitter {
  3. constructor() {
  4. this._events = {} // 初始化events事件对象
  5. }
  6. /**
  7. * 触发事件
  8. * 原理:将该事件增加到该事件类型的队列中
  9. * 状态:未执行
  10. * @param event 事件名称
  11. * @param cb 回调函数
  12. */
  13. on(eventName, callback) {
  14. !this._events[eventName] && (this._events[eventName]=[]) // 是否需要初始化方法队列
  15. this._events.push(callback) // 队列中追加cb
  16. return this // 保证链式调用
  17. }
  18. /**
  19. * 取消事件
  20. * 原理:将所有该事件类型的事件从队列中删除
  21. * 状态:取消执行
  22. * @param event 事件名称
  23. * @param cb 回调函数
  24. */
  25. remove(eventName, callback) {
  26. const target = this._events[eventName] // 先获取一下原事件队列
  27. target && (this._events[eventName] = target.filter(f => f != callback)) // 过滤掉事件队列中的待移除callback函数
  28. return this
  29. }
  30. /**
  31. * 触发事件
  32. * 原理:执行该事件类型的所有事件,按照队列顺序执行
  33. * 状态:准备执行 | 执行中
  34. * 使用方式:xx.emit(eventName, args)
  35. * @param args
  36. */
  37. emit(eventName, ...args) {
  38. const target = this._events[eventName] // 获取事件队列
  39. // 执行事件队列中的回调函数数组
  40. target && target.forEach(fn => {
  41. fn.apply(this, args) // fn.call(this, ...args)
  42. })
  43. return this
  44. }
  45. /**
  46. * 单次触发事件
  47. * 原理:执行一次该事件
  48. * @param event
  49. * @param cb
  50. */
  51. once(eventName, callback) {
  52. // 封装一个单次执行函数
  53. const fn = (...args) => {
  54. callback.apply(this, args) // 执行回调函数
  55. this.remove(eventName, fn) // 移除事件队列中所有的该类型的回调函数
  56. }
  57. this.on(eventName, fn) // 将单次执行函数添加到事件队列
  58. return this
  59. }
  60. }

手写一个观察者模式?

  1. var events = (function() {
  2. var topics = {};
  3. return {
  4. // 注册监听函数
  5. subscribe: function(topic, handler) {
  6. if (!topics.hasOwnProperty(topic)) {
  7. topics[topic] = [];
  8. }
  9. topics[topic].push(handler);
  10. },
  11. // 发布事件,触发观察者回调事件
  12. publish: function(topic, info) {
  13. if (topics.hasOwnProperty(topic)) {
  14. topics[topic].forEach(function(handler) {
  15. handler(info);
  16. });
  17. }
  18. },
  19. // 移除主题的一个观察者的回调事件
  20. remove: function(topic, handler) {
  21. if (!topics.hasOwnProperty(topic)) return;
  22. var handlerIndex = -1;
  23. topics[topic].forEach(function(item, index) {
  24. if (item === handler) {
  25. handlerIndex = index;
  26. }
  27. });
  28. if (handlerIndex >= 0) {
  29. topics[topic].splice(handlerIndex, 1);
  30. }
  31. },
  32. // 移除主题的所有观察者的回调事件
  33. removeAll: function(topic) {
  34. if (topics.hasOwnProperty(topic)) {
  35. topics[topic] = [];
  36. }
  37. }
  38. };
  39. })();

详细资料可以参考:《JS 事件模型》


JSON

JSON.stringify()

JSON.stringify(value[, replacer [, space]])

  • Boolean | Number| String 类型会自动转换成对应的原始值。
  • undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
  • 不可枚举的属性会被忽略
  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
  1. let str = {name:"dwadwad1",age:null,money:[{name:"odk"},2,3],child:{name:"dwadwa"}}
  2. function stringify(da) {
  3. let fn = function (data) {
  4. let res = []; // 定义存储的数组,循环里面添加key:value的字符串,然后用逗号连接
  5. let isArr = data instanceof Array; // 判断当前数据是否为数组
  6. for (let item in data){ // 循环遍历该数据
  7. if(data[item] instanceof Array){ // 如果该数据的value是数组 就插入 key:"[递归调用遍历数组]"
  8. res.push(`"${item}":"[${fn(data[item])}]"`);
  9. }else if(data[item] instanceof Object){ // 如果该数据是Object 那么就插入key:"{递归调用遍历object}" 如果当前数据是个数组 就返回数组的递归
  10. res.push(isArr?`{${fn(data[item])}}`:`"${item}":"{${fn(data[item])}}"`)
  11. }else{ // 都是不直接返回key:value 如果是数组的话直接插入key
  12. res.push(isArr?data[item]:`"${item}":"${data[item]}"`);
  13. }
  14. }
  15. return res.join(',');
  16. }
  17. return '{'+fn(da)+"}"
  18. }
  19. console.log(stringify(str))
  20. // or
  21. function jsonStringify(obj) {
  22. let type = typeof obj;
  23. if (type !== "object") {
  24. if (/string|undefined|function/.test(type)) {
  25. obj = '"' + obj + '"';
  26. }
  27. return String(obj);
  28. } else {
  29. let json = []
  30. let arr = Array.isArray(obj)
  31. for (let k in obj) {
  32. let v = obj[k];
  33. let type = typeof v;
  34. if (/string|undefined|function/.test(type)) {
  35. v = '"' + v + '"';
  36. } else if (type === "object") {
  37. v = jsonStringify(v);
  38. }
  39. json.push((arr ? "" : '"' + k + '":') + String(v));
  40. }
  41. return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
  42. }
  43. }
  44. jsonStringify({x : 5}) // "{"x":5}"
  45. jsonStringify([1, "false", false]) // "[1,"false",false]"
  46. jsonStringify({b: undefined}) // "{"b":"undefined"}"

JSON.parse()

JSON.parse(text[, reviver]) 用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。

  1. // 直接调用 eval
  2. function jsonParse(opt) {
  3. return eval('(' + opt + ')');
  4. }
  5. jsonParse(jsonStringify({x : 5}))
  6. // Object { x: 5}
  7. jsonParse(jsonStringify([1, "false", false]))
  8. // [1, "false", falsr]
  9. jsonParse(jsonStringify({b: undefined}))
  10. // Object { b: "undefined"}
  11. // 避免在不必要的情况下使用 eval,eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。
  12. // Function : Function与eval有相同的字符串参数特性。
  13. var jsonStr = '{ "age": 20, "name": "jack" }'
  14. var json = (new Function('return ' + jsonStr))();
  15. //eval 与 Function 都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用

其他

简单路由实现

  1. class Router {
  2. constructor() {
  3. this.routes = {};
  4. this.currentHash = '';
  5. this.changeRoute = this.changeRoute.bind(this);
  6. window.addEventListener('load', this.changeRoute, false);
  7. window.addEventListener('hashchange', this.changeRoute, false);
  8. }
  9. addRoute(path, fn) {
  10. this.routes[path] = fn || (() => {});
  11. }
  12. changeRoute() {
  13. this.changeRoute = location.hash.slice(1) || '/';
  14. const fn = this.routes[this.changeRoute];
  15. if (fn) {
  16. fn(); // 触发路由更新
  17. }
  18. }
  19. changePath(path) {
  20. window.location.hash = path;
  21. }
  22. }
  23. const router = new Router();
  24. router.addRoute('/path', () => {
  25. console.log('触发 /path 渲染对应的页面');
  26. });
  27. router.changePath('/path');

ajax

  1. function ajax(requestConfig = {}) {
  2. const { method, url, async, data } = requestConfig;
  3. const xhr = new XMLHttpRequest();
  4. xhr.open(method, url, async);
  5. xhr.onreadystatechange = function () {
  6. if (xhr.readyState === 4 && xhr.status === 200) {
  7. console.log(xhr.responseText);
  8. requestConfig.success(xhr.responseText);
  9. } else {
  10. requestConfig.error && requestConfig.error(xhr.responseText);
  11. }
  12. };
  13. xhr.onerror = function error(err) {
  14. console.err(err);
  15. requestConfig.error && requestConfig.error(err);
  16. };
  17. xhr.send(data);
  18. }
  1. function ajax(requestConfig = {}) {
  2. const { method, url, async, data } = requestConfig;
  3. const xhr = new XMLHttpRequest();
  4. xhr.open(method, url, async);
  5. xhr.onreadystatechange = function () {
  6. if (xhr.readyState === 4 && xhr.status === 200) {
  7. console.log(xhr.responseText);
  8. requestConfig.success(xhr.responseText);
  9. } else {
  10. requestConfig.error && requestConfig.error(xhr.responseText);
  11. }
  12. };
  13. xhr.onerror = function error(err) {
  14. console.err(err);
  15. requestConfig.error && requestConfig.error(err);
  16. };
  17. xhr.send(data);
  18. }

如何实现一个可设置过期时间的 localStorage

  1. (function () {
  2. const getItem = localStorage.getItem.bind(localStorage)
  3. const setItem = localStorage.setItem.bind(localStorage)
  4. const removeItem = localStorage.removeItem.bind(localStorage)
  5. localStorage.getItem = function (key) {
  6. const expires = getItem(key + '_expires')
  7. if (expires && new Date() > new Date(Number(expires))) {
  8. removeItem(key)
  9. removeItem(key + '_expires')
  10. }
  11. return getItem(key)
  12. }
  13. localStorage.setItem = function (key, value, time) {
  14. if (typeof time !== 'undefined') {
  15. setItem(key + '_expires', new Date().getTime() + Number(time))
  16. }
  17. return setItem(key, value)
  18. }
  19. })()