1. 为什么要手写代码?
我们在日常开发过程中,往往都是取出来直接用,从来不思考代码的底层实现逻辑,但当我开始研究一些底层的东西的时候,才开始理解了JavaScript每个方法和函数的底层实现思路,我认为这可以很好的提高我们的代码水平和逻辑思维。
2. 手写代码
2.1 函数柯里化
2.1.1 基本使用
函数柯里化指的是一种将多个参数的一个函数转换成一系列使用一个参数的函数的技术
// 正常使用 function sum(a, b, c) { return a + b + c; } console.log(sum(1, 2, 3)); // 函数柯里化实现上面的操作 function sum1(a) { return function (b) { return function (c) { return a + b + c; }; }; } console.log(sum1(1)(2)(3));
函数柯里化其实就是利用了闭包,大家想了解闭包可以转到我的博文 闭包
2.1.2 手写实现
function curry(fn, ...args) { let length = fn.length; args = args || []; return function () { let subArgs = args.slice(0); // 合并所有的参数 subArgs = [...subArgs, ...arguments]; // 判断参数的长度是否已经满足函数所需要的长度 if (subArgs.length >= length) { return fn.apply(this, subArgs); } else { // 返回柯里化化函数,等待继续传递参数 return curry.call(this, fn, ...subArgs); } }; }
可以进行测试
传入方法sum
function sum(a, b, c) { return a + b + c; }
发现可以正常打印出结果,成功实现 函数柯里化
let add = curry(sum); console.log(add(1)(1, 2, 3)); // 6 console.log(add(1)(2)(3)); // 6 console.log(add(1, 2, 3)); console.log(add()(1, 2, 3));
函数柯里化的简单实现方法
一行代码搞定
function curry1(fn, ...args) { return fn.length <= args.length ? fn(...args) : curry1.bind(null, fn, ...args); }
主要利用了bind在改变this指向的同时,也可以传递参数
案例如下就可以理解了
function fn(a, b, c) { console.log(a, b, c); } let fn1 = fn.bind(null, 1, 2); fn1(3); // 1,2,3
正常打印出了 1,2,3
2.2 sleep函数
2.2.1 简单使用
sleep是一种函数,作用是延时,程序暂停若干时间,在执行时要抛出一个中断异常,必须对其进行捕获并处理才可以使用这个函数。
js 中是没有这个函数的,需要我们手写实现使用
2.2.2 手写实现
方法一 利用了promise异步处理和定时器
function sleep(delay) { return new Promise((resolve) => { setTimeout(resolve, delay); }); } sleep(1000).then(() => { console.log('111'); });
一秒后打印出111
方法二 利用了定时器setTimeout和回调函数写法
function sleep(delay, callback) { setTimeout(callback, delay); } sleep(1000, () => { console.log(1000); });
方法三 利用了Date函数时间戳写法。精确计算。
function sleep(delay) { let startTime = new Date().getTime(); while (new Date().getTime() - startTime < delay) { // 只要还没有到特定事件,就一直在循环内, // 到了规定延迟后,跳出循环,指向下面的方法 continue; } } console.log('1111'); sleep(5000); console.log('2222');
2.3 Object.assign() 方法
2.3.1 基本使用
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(target, …sources)
第一个参数target: 即目标对象,目标对象的值会发生改变。
第二个参数souce:源对象(可多个)源对象不会发生改变
2.3.2 具体示例
let obj = { name: 'zs' }; let obj1 = { name: 'lisi' }; let obj2 = { age: 20 }; let newObj = Object.assign(obj, obj1, obj2); console.log(newObj); // {name: 'lisi', age: 20} console.log(obj); // {name: 'lisi', age: 20} console.log(obj1); // {name: 'lisi'} console.log(obj2); // {age: 20}
细心的大家可以发现,目标对象里面的值被源对象里面的值覆盖了。
注意:如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性
2.3.3 具体思路
大家通过上面的示例不能发现 Objec.assign() 到底干了什么,通过 遍历 sources 里面的对象属性,依次添加到 目标对象上,遇到相同的属性,目标对象的属性值直接被覆盖,最后返回目标对象合并后的值,并且 目标对象的值 也会被改变。
2.3.4 具体实现
小知识: Object.assign()方法在Object 对象上,并不是在它的原型对象上,myAssign()加粗,表示是我们新增的方法,而 assign 是自带的方法
Object.myAssign = function (target, ...sources) { if (target == null) { throw new TypeError('Cannot convert undefind or null to object'); } sources.forEach((source) => { if (source != null) { // 遍历每一个对象里面的属性,会遍历到原型上面的属性 for (let key in source) { // 判断该属性是不是对象自身的属性 if (source.hasOwnProperty(key)) { target[key] = source[key]; } } } }); // 将合并后的对象返回即可 return target; };
测试上面实现的代码
let newObj = Object.myAssign(obj, obj1, obj2); console.log(newObj); // {name: 'lisi', age: 20} console.log(obj); // {name: 'lisi', age: 20} console.log(obj1); // {name: 'lisi'} console.log(obj2); // {age: 20}
打印输出结果完成,成功实现。