android object数组赋值_图解JavaScript——代码实现(Object.create()、flat()等十四种代码原理实现不香吗?)...

本文通过思维导图和代码实例,详细解释了JavaScript中的new、instanceof、Object.create()、Object.assign()、map()、filter()、reduce()、flat()、call()、apply()、bind()、防抖和节流的原理,并提供深拷贝的实现。适合前端开发者提升技能,后续还将探讨Promise、Async、Axios和发布订阅等内容。
摘要由CSDN通过智能技术生成

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

8562cc2c7be0272541cbe4f4109f265c.png

一、new

9452200630a934ab302d5f550f395345.png
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

98a4c534ae7c62dd8a0d0459b1a7e53e.png
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()

eddf0699d43c64ddd6f77a02d4abd640.png
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 = {};
// 将该对象的原型设置为proto
Object.setPrototypeOf(result, proto);
// 将属性赋值给该对象
Object.defineProperties(result, propertiesObject);
// 返回该对象
return result;
}

3.2 Object assign()

1ddcfee2008a16dbb5911edb76a7d97e.png
function ObjectAssign(target, ...sources) {
// 对第一个参数的判断,不能为undefined和null
if (target === undefined || target === null) {
throw new TypeError('cannot convert first argument to object');
}

// 将第一个参数转换为对象(不是对象转换为对象)
const targetObj = Object(target);
// 将源对象(source)自身的所有可枚举属性复制到目标对象(target)
for (let i = 0; i < sources.length; i++) {
let source = sources[i];
// 对于undefined和null在源角色中不会报错,会直接跳过
if (source !== undefined && source !== null) {
// 将源角色转换成对象
// 需要将源角色自身的可枚举属性(包含Symbol值的属性)进行复制
// Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有属性,不管属性名是Symbol还是字符串,也不管是否可枚举
const keysArray = Reflect.ownKeys(Object(source));
for (let nextIndex = 0; nextIndex < keysArray.length; nextIndex ++) {
const nextKey = keysArray[nextIndex];
// 去除不可枚举属性
const desc = Object.getOwnPropertyDescriptor(source, nextKey);
if (desc !== undefined && desc.enumerable) {
// 后面的属性会覆盖前面的属性
targetObj[nextKey] = source[nextKey];
}
}
}
}

return targetObj;
}

// 由于挂载到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

da022b04d3b80fba39047d35a7f27eb3.png
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 < len; i++) {
// 获取第二个参数,改变this指向
let result = fn.call(arguments[1], arr[i], i, arr);
temp[i] = result;
}
// 返回新的结果
return temp;
}

4.2 filter

b0a8e59802cdba2546c50a90be658a73.png
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 < len; i++) {
// 处理时注意this指向
const result = fn.call(arguments[1], arr[i], i, arr);
result && temp.push(arr[i]);
}

return temp;
}

4.3 reduce

465afad73b77bd97d78f9761b67d19b4.png
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 && !( k in arr)) {
k++;
}
// 如果数组为空且初始值不存在则报错
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
value = arr[k++];
}
while (k < len) {
if (k in arr) {
value = fn(value, arr[k], k, arr);
}
k++;
}

return value;
}

4.4 flat

f3cf17557567e47bf2d8d022bd7fe78c.png
// 使用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

c46f4cb62f61d4f6cfbfa845313ead33.png
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

30d9238cf8056a6e9bd64903912583dd.png
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

15532b35cce55337326278a62f42c698.png
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 结果为 true
return 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 防抖

07c06c103f940c79f077b592335b9e64.png
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 节流

3fa57de93d178b8bacc6c8f95007799b.png
// 时间戳版本
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)
}
}
}

七、深拷贝

0db24449b0a3e75ad2efe09c55e1fa93.png
// 乞巧版
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值