手写instanceof instanceof作用 判断一个实例是否是其父类或者祖先类型的实例。
instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype 查找失败,返回 false
1 2 3 4 5 6 7 8 9 10 11 12 let myInstanceof = (target,origin ) => { while (target) { if (target.__proto__ === origin.prototype ) { return true } target = target.__proto__ } return false } let a = [1 ,2 ,3 ]console .log (myInstanceof (a, Array )); console .log (myInstanceof (a, Object ));
实现数组的map方法 1 2 3 4 5 6 7 8 9 Array .prototype .myMap = (fn, thisValue ) => { let res = [] thisValue = thisValue || [] let arr = this for (let i in arr) { res.push (fn (arr[i])) } return res }
reduce实现数组的map方法 1 2 3 4 5 6 7 8 9 10 11 12 13 Array .prototype .myMap = (fn,thisValue ) => { var res = []; thisValue = thisValue||[]; this .reduce (function (pre, cur, index, arr ){ return res.push (fn.call (thisValue, cur, index, arr)); },[]); return res; } var arr = [2 ,3 ,1 ,5 ];arr.myMap ((item, index, arr ) => { console .log (item, index, arr); })
手写数组的reduce方法 reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法
参数 callback(一个在数组中每一项上调用的函数,接受四个函数:) previousValue(上一次调用回调函数时的返回值,或者初始值) currentValue(当前正在处理的数组元素) currentIndex(当前正在处理的数组元素下标) array(调用reduce()方法的数组) initialValue(可选的初始值。作为第一次调用回调函数时传给previousValue的值) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function reduce (arr, cb, initialValue ) { var num = initValue == undefined ? num = arr[0 ] : initValue; var i = initValue == undefined ? 1 : 0 for (i; i < arr.length ; i++) { num = cb (num, arr[i], i) } return num } function fn (result, currentValue, index ) { return result + currentValue } var arr = [2 ,3 ,4 ,5 ]var b = reduce (arr, fn, 10 ) var c = reduce (arr, fn)console .log (b)
数组扁平化 数组扁平化就是把多维数组转化成一维数组
es6提供的新方法 flat(depth)
1 2 3 let a = [1 , [2 , 3 ]]; a.flat (); a.flat (1 );
其实还有一种更简单的办法,无需知道数组的维度,直接将目标数组变成1维数组。depth的值设置为Infinity
1 2 let a = [1 , [2 , 3 , [4 , [5 ]]]]; a.flat (Infinity );
利用cancat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var arr1 = [1 , 2 , 3 , [1 , 2 , 3 , 4 , [2 , 3 , 4 ]]];function flatten (arr ) { var res = []; for (let i = 0 , length = arr.length ; i < length; i++) { if (Array .isArray (arr[i])) { res = res.concat (flatten (arr[i])); } else { res.push (arr[i]); } } return res; } flatten (arr1);
函数柯里化 柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。
当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?
有两种思路:
1.通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
2.在调用柯里化工具函数时,手动指定所需的参数个数
将这两点结合一下,实现一个简单 curry 函数:
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 function curry (fn, len = fn.length ) { return _curry.call (this , fn, len) } function _curry (fn, len, ...args ) { return function (...params ) { let _args = [...args, ...params]; if (_args.length >= len) { return fn.apply (this , _args); } else { return _curry.call (this , fn, len, ..._args) } } }
我们来验证一下:
1 2 3 4 5 6 7 8 let _fn = curry (function (a, b, c, d, e ){ console .log (a, b, c, d, e) }); _fn (1 , 2 , 3 , 4 , 5 ); _fn (1 )(2 )(3 , 4 , 5 ); _fn (1 , 2 )(3 , 4 )(5 ); _fn (1 )(2 )(3 )(4 )(5 );
我们常用的工具库 lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通过占位符的方式来改变传入参数的顺序。
比如说,我们传入一个占位符,本次调用传递的参数略过占位符, 占位符所在的位置由下次调用的参数来填充,比如这样:
直接看一下官网的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var abc = function (a, b, c ) { return [a, b, c]; }; var curried = _.curry (abc);curried (1 )(2 )(3 );curried (1 , 2 )(3 );curried (1 , 2 , 3 );curried (1 )(_, 3 )(2 );
接下来我们来思考,如何实现占位符的功能。
对于 lodash 的 curry 函数来说,curry 函数挂载在 lodash 对象上,所以将 lodash 对象当做默认占位符来使用。
而我们的自己实现的 curry 函数,本身并没有挂载在任何对象上,所以将 curry 函数当做默认占位符
使用占位符,目的是改变参数传递的顺序,所以在 curry 函数实现中,每次需要记录是否使用了占位符,并且记录占位符所代表的参数位置。
直接上代码:
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 function curry (fn, length = fn.length, holder = curry ) { return _curry.call (this , fn, length, holder, [], []) } function _curry (fn, length, holder, args, holders ) { return function (..._args ) { let params = args.slice (); let _holders = holders.slice (); _args.forEach ((arg,i ) => { if (arg !== holder && holders.length ) { let index = holders.shift (); _holders.splice (_holders.indexOf (index), 1 ); params[index] = arg; } else if (arg !== holder && !holders.length ) { params.push (arg); } else if (arg === holder && !holders.length ) { params.push (arg); _holders.push (params.length - 1 ); } else if (arg === holder && holders.length ) { holders.shift (); } }); if (params.length >= length && params.slice (0 , length).every (i => i !== holder)) { return fn.apply (this , params); } else { return _curry.call (this , fn, length, holder, params, _holders) } } }
验证一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 let fn = function (a, b, c, d, e ) { console .log ([a, b, c, d, e]); } let _ = {}; let _fn = curry (fn, 5 , _); _fn (1 , 2 , 3 , 4 , 5 ); _fn (_, 2 , 3 , 4 , 5 )(1 ); _fn (1 , _, 3 , 4 , 5 )(2 ); _fn (1 , _, 3 )(_, 4 ,_)(2 )(5 ); _fn (1 , _, _, 4 )(_, 3 )(2 )(5 ); _fn (_, 2 )(_, _, 4 )(1 )(3 )(5 );
至此,我们已经完整实现了一个 curry 函数
实现深拷贝 浅拷贝和深拷贝的区别:
浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用
深拷贝:拷贝多层,每一级别的数据都会拷贝。这样更改拷贝值就不影响另外的对象
ES6浅拷贝方法:Object.assign(target, …sources)
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 let obj = { id : 1 , name : 'Tom' , msg : { age : 18 } } let o = {}function deepCopy (newObj, oldObj ) { for (let k in oldObj) { let item = oldObj[k] if (item instanceof Array ) { newObj[k] = [] deepCopy (newObj[k], item) } else if (item instanceof Object ) { newObj[k] = {} deepCopy (newObj[k], item) } else { newObj[k] = item } } }
手写call, apply, bind 手写call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Function .prototype .myCall = function (context = window ) { if (typeof this !== "function" ) { throw new Error ("不是函数" ) } const obj = context || window obj.fn = this const arg = [...arguments ].slice (1 ) res = obj.fn (...arg) delete obj.fn return res }
1 2 3 4 5 6 7 8 9 10 11 function f (a, b ) { console .log (a + b) console .log (this .name ) } let obj = { name : 1 } f.myCall (obj, 1 , 2 ) obj.greet .call ({name : 'Spike' })
手写apply(arguments[this, [参数1,参数2…..] ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Function .prototype .myApply = function (context ) { let obj = context || window obj.fn = this const arg = arguments [1 ] || [] let res = obj.fn (...arg) delete obj.fn return res } function f (a, b ) { console .log (a, b) console .log (this .name ) } let obj = { name : '张三' } f.myApply (obj, [1 , 2 ])
手写bind
1 2 3 4 5 6 7 8 9 10 11 this .value = 2 var foo = { value : 1 }; var bar = function (name, age, school ) { console .log (name) console .log (age) console .log (school) } var result = bar.bind (foo, 'An' ) result (22 , '家里蹲大学' )
简单版本
1 2 3 4 5 6 7 Function .prototype .bind = function (context, ...outerArgs ) { var fn = this ; return function (...innerArgs ) { return fn.apply (context, [...outerArgs, ...innerArgs]); } }
new失败的原因:
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let thovino = { name : 'thovino' } let eat = function (food ) { this .food = food console .log (`${this .name} eat ${this .food} ` ) } eat.prototype .sayFuncName = function ( ) { console .log ('func name : eat' ) } let thovinoEat = eat.bind (thovino)let instance = new thovinoEat ('orange' ) console .log ('instance:' , instance)
生成的实例是个空对象
在new操作符执行时,我们的thovinoEat函数可以看作是这样:
1 2 3 function thovinoEat (...innerArgs) { eat.call (thovino, ...outerArgs, ...innerArgs) }
在new操作符进行到第三步的操作thovinoEat.call(obj, …args)时,这里的obj是new操作符自己创建的那个简单空对象{},但它其实并没有替换掉thovinoEat函数内部的那个上下文对象thovino。这已经超出了call的能力范围,因为这个时候要替换的已经不是thovinoEat函数内部的this指向,而应该是thovino对象。
换句话说,我们希望的是new操作符将eat内的this指向操作符自己创建的那个空对象。但是实际上指向了thovino,new操作符的第三步动作并没有成功!
可new可继承版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Function .prototype .bind = function (context, ...outerArgs ) { let that = this ; function res (...innerArgs) { if (this instanceof res) { that.call (this , ...outerArgs, ...innerArgs) } else { that.call (context, ...outerArgs, ...innerArgs) } } res.prototype = this .prototype return res }
手动实现new new的过程文字描述:
创建一个空对象 obj; 将空对象的隐式原型(proto)指向构造函数的prototype。 使用 call 改变 this 的指向 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Person (name, age ) { this .name = name this .age = age } Person .prototype .sayHi = function ( ) { console .log ('Hi!我是' + this .name ) } let p1 = new Person ('张三' , 18 )function create ( ) { let obj = {} let fn = [].shift .call (arguments ) obj.__proto__ = fn.prototype let res = fn.apply (obj, arguments ) return typeof res === 'object' ? res : obj } let p2 = create (Person , '李四' , 19 )p2.sayHi ()
细节:
1 2 3 4 5 6 [].shift .call (arguments ) let arg = [...arguments ]let fn = arg.shift () obj.__proto__ = fn.prototype let res = fn.apply (obj, arg)
手写promise(常见promise.all, promise.race) 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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 const STATUS = { PENDING : 'pending' , FULFILLED : 'fulfilled' , REJECTED : 'rejected' } class MyPromise { constructor (executor ) { this ._status = STATUS .PENDING this ._value = undefined this ._resolveQueue = [] this ._rejectQueue = [] const resolve = value => { const run = ( ) => { if (this ._status === STATUS .PENDING ) { this ._status = STATUS .FULFILLED this ._value = value while (this ._resolveQueue .length ) { const callback = this ._resolveQueue .shift () callback (value) } } } setTimeout (run) } const reject = value => { const run = ( ) => { if (this ._status === STATUS .PENDING ) { this ._status = STATUS .REJECTED this ._value = value while (this ._rejectQueue .length ) { const callback = this ._rejectQueue .shift () callback (value) } } } setTimeout (run) } executor (resolve, reject) } function then (onFulfilled, onRejected ) { typeof onFulfilled !== 'function' ? onFulfilled = value => value : null typeof onRejected !== 'function' ? onRejected = error => error : null return new MyPromise ((resolve, reject ) => { const resolveFn = value => { try { const x = onFulfilled (value) x instanceof MyPromise ? x.then (resolve, reject) : resolve (x) } catch (error) { reject (error) } } } } const rejectFn = error => { try { const x = onRejected (error) x instanceof MyPromise ? x.then (resolve, reject) : resolve (x) } catch (error) { reject (error) } } switch (this ._status ) { case STATUS .PENDING : this ._resolveQueue .push (resolveFn) this ._rejectQueue .push (rejectFn) break ; case STATUS .FULFILLED : resolveFn (this ._value ) break ; case STATUS .REJECTED : rejectFn (this ._value ) break ; } }) } catch (rejectFn) { return this .then (undefined , rejectFn) } finally (callback ) { return this .then (value => MyPromise .resolve (callback ()).then (() => value), error => { MyPromise .resolve (callback ()).then (() => error) }) } static resolve (value ) { return value instanceof MyPromise ? value : new MyPromise (resolve => resolve (value)) } static reject (error ) { return new MyPromise ((resolve, reject ) => reject (error)) } static all (promiseArr ) { let count = 0 let result = [] return new MyPromise ((resolve, reject ) => { if (!promiseArr.length ) { return resolve (result) } promiseArr.forEach ((p, i ) => { MyPromise .resolve (p).then (value => { count++ result[i] = value if (count === promiseArr.length ) { resolve (result) } }, error => { reject (error) }) }) }) } static race (promiseArr ) { return new MyPromise ((resolve, reject ) => { promiseArr.forEach (p => { MyPromise .resolve (p).then (value => { resolve (value) }, error => { reject (error) }) }) }) }
手写原生AJAX 步骤
创建 XMLHttpRequest 实例 发出 HTTP 请求 服务器返回 XML 格式的字符串 JS 解析 XML,并更新局部页面 不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。 version 1.0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 myButton.addEventListener ('click' , function ( ) { ajax () }) function ajax ( ) { let xhr = new XMLHttpRequest () xhr.open ('get' , 'https://www.google.com' ) xhr.onreadystatechange = () => { if (xhr.readyState === 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { let string = request.responseText let object = JSON .parse (string) } } } request.send () }
promise实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function ajax (url ) { const p = new Promise ((resolve, reject ) => { let xhr = new XMLHttpRequest () xhr.open ('get' , url) xhr.onreadystatechange = () => { if (xhr.readyState == 4 ) { if (xhr.status >= 200 && xhr.status <= 300 ) { resolve (JSON .parse (xhr.responseText )) } else { reject ('请求出错' ) } } } xhr.send () }) return p } let url = '/data.json' ajax (url).then (res => console .log (res)) .catch (reason => console .log (reason))
手写节流防抖函数 防抖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function debounce (fn, delay ) { if (typeof fn!=='function' ) { throw new TypeError ('fn不是函数' ) } let timer; return function ( ) { var _this = this ; var args = arguments ; if (timer) { clearTimeout (timer); } timer = setTimeout (function ( ) { fn.apply (_this, args); }, delay); }; } input1.addEventListener ('keyup' , debounce (() => { console .log (input1.value ) }), 600 )
节流:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function throttle (fn, delay ) { let timer; return function ( ) { var _this = this ; var args = arguments ; if (timer) { return ; } timer = setTimeout (function ( ) { fn.apply (_this, args); timer = null ; }, delay) } } div1.addEventListener ('drag' , throttle ((e ) => { console .log (e.offsetX , e.offsetY ) }, 100 ))
手写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 function getData (url ) { return new Promise ((resolve, reject ) => { $.ajax ({ url, success (data ) { resolve (data) }, error (err ) { reject (err) } }) }) } const url1 = './data1.json' const url2 = './data2.json' const url3 = './data3.json' getData (url1).then (data1 => { console .log (data1) return getData (url2) }) .then (data2 => { console .log (data2) return getData (url3) }) .then (data3 => { console .log (data3) }) .catch (err => { console .error (err) })
函数实现一秒钟输出一个数 1 2 3 4 5 for (let i = 0 ; i <= 10 ; i++){ setTimeout (() => { console .log (i); }, 1000 *i) }
创建10个标签,点击的时候弹出来对应的序号 1 2 3 4 5 6 7 8 9 10 11 12 13 var afor (let i = 0 ; i < 10 ; i++) { a = document .createElement ('a' ) a.innerHTML = i + '<br>' a.addEventListener ('click' , function (e ) { console .log (this ) e.preventDefault () alert (i) }) const d = document .querySelector ('div' ) d.appendChild (a) }