js手写算法
深浅拷贝
浅拷贝-object.assign
1.Object.assign
思路分析:
object.assign实际上实现了浅拷贝,参数target,把若干个source的值复制给target
遍历参数,获取source的值,对于每一个source然后for … in 遍历传入的参数的属性,复制给新的属性
if (typeof Object.assign2 != 'function') {
// Attention 1
Object.defineProperty(Object, "assign2", {
value: function (target) {
'use strict';
if (target == null) { // Attention 2
throw new TypeError('Cannot convert undefined or null to object');
}
// Attention 3
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Attention 2
// Attention 4
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
深拷贝
思路分析:对于深拷贝实际上对于需要复制的对象,进行递归浅拷贝,如果属性是对象,则再次深拷贝,然后对于传入的边界值进行处理,更完善的需要处理递归保栈问题和循环引用
function cloneDeep1(source) {
var target = {};
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === 'object') {
target[key] = cloneDeep1(source[key]); // 注意这里
} else {
target[key] = source[key];
}
}
}
return target;
}
解决数组复制问题:
function cloneDeep2(source) {
if (!isObject(source)) return source; // 非对象返回自身
var target = Array.isArray(source) ? [] : {};
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep2(source[key]); // 注意这里
} else {
target[key] = source[key];
}
}
}
return target;
}
解决循环引用问题:
function cloneDeep3(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
var target = Array.isArray(source) ? [] : {};
hash.set(source, target); // 新增代码,哈希表设值
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
} else {
target[key] = source[key];
}
}
}
return target;
}
解决无法复制symbol值的问题:
function cloneDeep4(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source);
let target = Array.isArray(source) ? [...source] : { ...source }; // 改动 1
hash.set(source, target);
Reflect.ownKeys(target).forEach(key => { // 改动 2
if (isObject(source[key])) {
target[key] = cloneDeep4(source[key], hash);
} else {
target[key] = source[key];
}
});
return target;
}
解决递归爆栈问题:
function cloneDeep5(x) {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 广度优先
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] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
call apply bind new实现
call实现
4.手写call
思路:call实际上是改变了this的指向,关键是获取this和参数的值
Function.prototype.call2 = function (context) {
context = context ? Object(context) : window; // 实现细节 1 和 2
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result; // 实现细节 2
}
apply实现
4.手写apply
思路:基本思路和call一样,但是区别在于处理参数,apply的参数是个数组
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
delete context.fn
return result;
}
bind实现
5.手写bind
思路:bind与call和apply的区别在于,bind返回了一个绑定上下文的函数,而apply和call是直接执行
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
new实现
3.手写new
简易实现:
function create() {
let obj=new Object()
Con=[].shift.call(arguments)
obj.__proto__=Con.prototype
Con.apply(obj,arguments)
return obj
}
第二版:
// 第二版
function create() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
var obj = Object.create(Con.prototype);
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
节流防抖实现
节流
节流:一定时间间隔内函数只执行一次
基础思路:通过时间戳来判断函数是否需要执行,记录上次执行的时间戳,每次触发事件的时候,判断当前时间和上次执行的差值是否达到等待时间
// fn 是需要执行的函数
// wait 是时间间隔
const throttle = (fn, wait = 50) => {
// 上一次执行 fn 的时间
let previous = 0
// 将 throttle 处理结果当作函数返回
return function(...args) {
// 获取当前时间,转换成时间戳,单位毫秒
let now = +new Date()
// 将当前时间和上一次执行函数的时间进行对比
// 大于等待时间就把 previous 设置为当前时间并执行函数 fn
if (now - previous > wait) {
previous = now
fn.apply(this, args)
}
}
}
// DEMO
// 执行 throttle 函数返回新函数
const betterFn = throttle(() => console.log('fn 函数执行了'), 1000)
// 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn
setInterval(betterFn, 10)
防抖
防抖:一定时间间隔内,只执行最后一次
基础思路:利用定时器,设定一定时间的定时器,
// 实现 2
// immediate 表示第一次是否立即执行
function debounce(fn, wait = 50, immediate) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
// ------ 新增部分 start ------
// immediate 为 true 表示第一次触发后执行
// timer 为空表示首次触发
if (immediate && !timer) {
fn.apply(this, args)
}
// ------ 新增部分 end ------
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
// DEMO
// 执行 debounce 函数返回新函数
const betterFn = debounce(() => console.log('fn 防抖执行了'), 1000, true)
// 第一次触发 scroll 执行一次 fn,后续只有在停止滑动 1 秒后才执行函数 fn
document.addEventListener('scroll', betterFn)
promise
核心思路:基础的promise,包含对于三种状态 pending fulfilled rejected 的转换,以及then函数的编写,成功调用成功回调函数,失败调用失败回调函数
第二步解决异步问题,在then函数中保存失败和成功的回调函数,一旦reject或者fulfill,立刻执行
第三步解决链式调用问题
class Promise {
constructor (executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
let resolve = value => {
if (this.state == 'pedding') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
let reject = reason => {
if (this.state == 'pending') {
this.state = 'rejected'
this.value = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then (onFulfilled, onRejected) {
onFulfilled=typeof onFulfilled==="function"?onFulfilled:value=>value
onRejected=typeof onRejected==='function'?onRejected:error=>{throw error}
let promise2=new Promise((resolve,reject)=>{
if (this.state == 'fulfilled') {
setTimeout(()=>{
try{
let x = onFulfilled(this.value)
reslovePromise(promise2,x,resolve,reject)
}catch (e) {
reject(e)
}
},0)
}
if (this.state == 'rejected') {
setTimeout(()=>{
try{
onRejected(this.reason)
reslovePromise(promise2,x,resolve,reject)
}catch (e) {
reject(e)
}
})
}
if (this.state == 'pending') {
setTimeout(()=>{
try{
let x=this.onFulfilledCallbacks.push(() => { onFulfilled(this.value) })
reslovePromise(promise2,x,resolve,reject)
let x=this.onRejectedCallbacks.push(() => { onRejected(this.reason) })
reslovePromise(promise2,x,resolve,reject)
}catch (e) {
reject(e)
}
},0)
}
})
return promise2
}
}
function reslovePromise (promise2, x, reslove, reject) {
if (x === Promise) {
return reject(new TypeError('chaning cycle for promise'))
}
let called
if (x != null && (typeof x === 'function')) {
try{
let then =x.then
if(typeof then==="function") {
then.call(x,y=>{
if called return;
called=true
reslovePromise(promise2,y,reslove,reject)
},err=>{
if called return
called=true
reject(err)
})
}
else{
reslove(x)
}
}
catch (e) {
if (called) return
called=true
reject(e)
}
}
else{
reslove(x)
}
}
手写es6使用proxy实现arr[-1]访问
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c