热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

图解JavaScript——代码实现【1】(new、Object.create()、Object.assign()、flat()等十四种代码原理实现不香吗?)

关注公众号“执鸢者”,回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频,回复“代码实现”获取本节整体思维导图。使用思维导图来对ne

关注公众号“执鸢者”,回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频,回复“代码实现”获取本节整体思维导图。

使用思维导图来对new、instanceof、Object.create()、Object.assign()、map()、filter()、reduce()、flat()、call()、apply()、bind()、防抖、节流、深拷贝的实现原理进行阐述,然后利用js代码进行实现,为前端切图仔在求职工作中再添一门武功秘籍,提升自身内功。本节为第一节,后面将继续探索Promise、Async、Axios、发布订阅等的实现,请各位大佬关注指正。

一、new

function New (Fn, ...arg) {// 一个新的对象被创建const result = {};// 该对象的__proto__属性指向该构造函数的原型if (Fn.prototype !== null) {Object.setPrototypeOf(result, Fn.prototype);}// 将执行上下文(this)绑定到新创建的对象中const returnResult = Fn.apply(result, arg);// 如果构造函数有返回值,那么这个返回值将取代第一步中新创建的对象。否则返回该对象if ((typeof returnResult === "object" || typeof returnResult === "function") && returnResult !== null) {return returnResult;}return result;
}

二、instanceof

function Instanceof(left, right) {let leftVal = Object.getPrototypeOf(left);const rightVal = right.prototype;while (leftVal !== null) {if (leftVal === rightVal)return true;leftVal = Object.getPrototypeOf(leftVal);}return false;
}

三、Object


Object上有很多静态方法,本次只实现Object.create()和Object.assign(),有兴趣的可以下载思维导图进行完善。


3.1 Object.create()

Object.ObjectCreate = (proto, propertiesObject)=> {// 对输入进行检测if (typeof proto !== 'object' && typeof proto !== 'function' && proto !== null) {throw new Error(`Object prototype may only be an Object or null:${proto}`);}// 新建一个对象const result = {};// 将该对象的原型设置为protoObject.setPrototypeOf(result, proto);// 将属性赋值给该对象Object.defineProperties(result, propertiesObject);// 返回该对象return result;
}

3.2 Object assign()

function ObjectAssign(target, ...sources) {// 对第一个参数的判断,不能为undefined和nullif (target === undefined || target === null) {throw new TypeError('cannot convert first argument to object');}// 将第一个参数转换为对象(不是对象转换为对象)const targetObj = Object(target);// 将源对象(source)自身的所有可枚举属性复制到目标对象(target)for (let i = 0; i }// 由于挂载到Object的assign是不可枚举的,直接挂载上去是可枚举的,所以采用这种方式
if (typeof Object.myAssign !== 'function') {Object.defineProperty(Object, "myAssign", {value : ObjectAssign,writable: true,enumerable: false,configurable: true});
}

四、数组原理


数组有很多方法,我们此处只实现了比较常见的map()、filter()、reduce()、flat(),有兴趣的童鞋可以继续补充。


4.1 map

Array.prototype.myMap = function(fn) {// 判断输入的第一个参数是不是函数if (typeof fn !== 'function') {throw new TypeError(fn + 'is not a function');}// 获取需要处理的数组内容const arr = this;const len = arr.length;// 新建一个空数组用于装载新的内容const temp = new Array(len);// 对数组中每个值进行处理for (let i = 0; i }

4.2 filter

Array.prototype.myFilter = function (fn) {if (typeof fn !== 'function') {throw new TypeError(`${fn} is not a function`);}// 获取该数组const arr = this;// 获取该数组长度const len = this.length >>> 0;// 新建一个新的数组用于放置该内容const temp = [];// 对数组中每个值进行处理for (let i = 0; i }

4.3 reduce

Array.prototype.myReduce = function(fn) {if (typeof fn !== 'function') {throw new TypeError(`${fn} is not a function`);}const arr = this;const len = arr.length >>> 0;let value;// 最终返回的值let k = 0;// 当前索引if (arguments.length >= 2) {value = arguments[1];} else {// 当数组为稀疏数组时,判断数组当前是否有元素,如果没有索引加一while (k = len) {throw new TypeError('Reduce of empty array with no initial value');}value = arr[k++];}while (k }

4.4 flat

// 使用reduce和concat
Array.prototype.flat1 = function () {return this.reduce((acc, val) => acc.concat(val), []);
}

// 使用reduce + concat + isArray +recursivity
Array.prototype.flat2 = function (deep = 1) {const flatDeep = (arr, deep = 1) => {// return arr.reduce((acc, val) => Array.isArray(val) && deep > 0 ? [...acc, ...flatDeep(val, deep - 1)] : [...acc, val], []);return deep > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, deep - 1) : val), []) : arr.slice();}return flatDeep(this, deep);
}

// 使用forEach + concat + isArray +recursivity
// forEach 遍历数组会自动跳过空元素
Array.prototype.flat3 = function (deep = 1) {const result = [];(function flat(arr, deep) {arr.forEach((item) => {if (Array.isArray(item) && deep > 0) {flat(item, deep - 1);} else {result.push(item);}})})(this, deep);return result;
}

// 使用for of + concat + isArray +recursivity
// for of 遍历数组会自动跳过空元素
Array.prototype.flat4 = function (deep = 1) {const result = [];(function flat(arr, deep) {for(let item of arr) {if (Array.isArray(item) && deep > 0) {flat(item, deep - 1);} else {// 去除空元素,因为void 表达式返回的都是undefined,不适用undefined是因为undefined在局部变量会被重写item !== void 0 && result.push(item);}}})(this, deep);return result;
}

// 使用堆栈stack
Array.prototype.flat5 = function(deep = 1) {const stack = [...this];const result = [];while (stack.length > 0) {const next = stack.pop();if (Array.isArray(next)) {stack.push(...next);} else {result.push(next);}}// 反转恢复原来顺序return result.reverse();
}

五、改变this指向


js中有三种方式改变this指向,分别是call、apply和bind。


5.1 call

Function.prototype.call1 = function(context, ...args) {// 获取第一个参数(注意第一个参数为null或undefined是,this指向window),构建对象context = context ? Object(context) : window;// 将对应函数传入该对象中context.fn = this;// 获取参数并执行相应函数let result = context.fn(...args);delete context.fn;

5.2 apply

Function.prototype.apply1 = function(context, arr) {context = context ? Object(context) : window;context.fn = this;let result = arr ? context.fn(...arr) : context.fn();delete context.fn;return result;
}

5.3 bind

Function.prototype.bind1 = function (context, ...args) {if (typeof this !== 'function') {throw new TypeError('The bound object needs to be a function');}const self = this;const fNOP = function() {};const fBound = function(...fBoundArgs) {// 指定this// 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 truereturn self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]);}// 修改返回函数的 prototype 为绑定函数的 prototype,为了避免直接修改this的原型,所以新建了一个fNOP函数作为中介if (this.prototype) {fNOP.prototype = this.prototype;}fBound.prototype = new fNOP();return fBound;
}

六、优化


防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。


6.1 防抖

function debounce(fn, wait, immediate) {let timer = null;return function(...args) {// 立即执行的功能(timer为空表示首次触发)if (immediate && !timer) {fn.apply(this, args);}// 有新的触发,则把定时器清空timer && clearTimeout(timer);// 重新计时timer = setTimeout(() => {fn.apply(this, args);}, wait)}
}

6.2 节流

// 时间戳版本
function throttle(fn, wait) {// 上一次执行时间let previous = 0;return function(...args) {// 当前时间let now = +new Date();if (now - previous > wait) {previous = now;fn.apply(this, args);}}
}

// 定时器版本
function throttle(fn, wait) {let timer = null;return function(...args) {if (!timer) {timer = setTimeout(() => {fn.apply(this, args);timer = null;}, wait)}}
}

七、深拷贝

// 乞巧版
function cloneDeep1(source) {return JSON.parse(JSON.stringify(source));
}

// 递归版
function cloneDeep2(source) {// 如果输入的为基本类型,直接返回if (!(typeof source === 'object' && source !== null)) {return source;}// 判断输入的为数组函数对象,进行相应的构建const target = Array.isArray(source) ? [] : {};for (let key in source) {// 判断是否是自身属性if (Object.prototype.hasOwnProperty.call(source, key)) {if (typeof source === 'object' && source !== null) {target[key] = cloneDeep2(source[key]);} else {target[key] = source[key];}}}return target;
}

// 循环方式
function cloneDeep3(source) {if (!(typeof source === 'object' && source !== null)) {return source;}const root = Array.isArray(source) ? [] : {};// 定义一个栈const loopList = [{parent: root,key: undefined,data: source,}];while (loopList.length > 0) {// 深度优先const node = loopList.pop();const parent = node.parent;const key = node.key;const data = node.data;// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素let res = parent;if (typeof key !== 'undefined') {res = parent[key] = Array.isArray(data) ? [] : {};}for (let key in data) {if (data.hasOwnProperty(key)) {if (typeof data[key] === 'object' && data !== null) {loopList.push({parent: res,key: key,data: data[key],});} else {res[key] = data[key];}}}}return root;
}

相关章节

图解Javascript————基础篇

图解Javascript————进阶篇

图解23种设计模式(TypeScript版)


欢迎大家关注公众号(回复“代码实现”获取本节的思维导图,回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频)


推荐阅读
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 本文介绍了在实现了System.Collections.Generic.IDictionary接口的泛型字典类中如何使用foreach循环来枚举字典中的键值对。同时还讨论了非泛型字典类和泛型字典类在foreach循环中使用的不同类型,以及使用KeyValuePair类型在foreach循环中枚举泛型字典类的优势。阅读本文可以帮助您更好地理解泛型字典类的使用和性能优化。 ... [详细]
author-avatar
我从不在乎O心痛
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有