八股手写代码笔记第一部分

134 阅读11分钟

Object.create()

创建一个新对象,使用现有对象作为新对象的原型(prototype),properties为可选参数,指定要添加到新对象上的可选参数

  • 创建构造函数
  • 构造函数显示原型指向目标对象
  • 如果有参数就用defineProperties赋给fn
  • 创建fn实例
function _create(obj, properties) {
    function fn() {}
    fn.prototype = obj
    let f = new fn()
    if(properties) {
        Object.defineProperties(f, properties)
    }
    return f
}

instanceof

它的作用是测试它左边的对象是否是它右边的类的实例,如果left不为对象,直接返回false,如果右边没有prototype,直接抛出错误

function myInstanceOf(left, right) {
    if(typeof left !== 'object') return false
    if(!right.prototype) throw new error("sad")
    let proto = left.__proto__
    let prototype = right.prototype
    while(proto) {
        if(proto === prototype) return true
        proto = proto.__proto__
    }
    return false
}

new操作符

new操作符做了这几件事:

  1. 创建一个全新对象,这个对象会被__proto__链接
  2. 生成的新对象会绑定到函数调用的this
  3. 若未设返回值,则自动返回该对象,若返回值为引用类型,则返回该引用类型,否则返回该对象 那么实现需要以下几步:
  4. 创建一个新对象obj
  5. 将obj的隐式原型指向构造函数的显式原型,一二步直接用Object.create()
  6. 通过apply改变this指向,使其指向obj
  7. 若第三步不返回该引用类型,则仍然返回obj,为什么可以用instanceof Object判断引用类型呢,因为原型链终点是Object.prototype
let _new = function(fn, ...args) {
    let obj = Object.create(fn.prototype)
    let result = fn.apply(obj, args)
    return result instanceof Object ? result : obj
}

Promise.all()

  1. 接收一个Promise实例或具有iterator接口的对象作为参数
  2. 返回一个新的promise对象
  3. 参数所有回调成功才是成功,返回顺序与传入顺序一致
  4. 参数有一个失败则触发失败状态,第一个触发失败的作为失败信息
  5. Promise.resolve()返回解析后的Promise
  6. 注意,要用resolveRes[i] = value,因为这是异步任务,用let块级作用域的特点保证i不被渗透
function promiseAll(promises) {
    return new Promise((resolve, reject) => {
        if(!Array.isArray(promises)) {
            throw new Error('Type Error')
        }
        let resolveCount = 0
        let resolveRes = []
        for(let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(value => {
                resolveCount ++
                resolveRes[i] = value
                if(resolveCount === promises.length) {
                    return resolve(resolveRes)
                }
            })
            .catch(error => {
                return reject(error)
            }) 
        }
    })
}

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 1000);
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 2000);
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3)
    }, 3000);
})

promiseAll([p3,p2,p1]).then(res => {
    console.log(res);
})
.catch(error => {
    console.log(error);
})

Promse.race()

function promiseRace(promises) {
    return new Promise((resolve, reject) => {
        if(!Array.isArray(promises)) {
            throw new TypeError('Type Error')
        }
        const len = promises.length
        for(let i = 0;i < len; i++) {
            Promise.resolve(promises[i]).then(res => {
                return resolve(res)
            })
            .catch(err => {
                return reject(err)
            })
        }
    })
}

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 1000);
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 2000);
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3)
    }, 500);
})

promiseRace([p3,p2,p1]).then(res => {
    console.log(res);
})
.catch(error => {
    console.log(error);
})

防抖

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

function debounce(fn, delay) {
    let timer = null
    return function (...args) {
        let context = this
        if(timer) {
            clearTimeout(timer)
            timer = null
        }
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, delay);
    }
}

节流

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

function throttle(fn, delay) {
    let timer = null
    return function(...args) {
        if(!timer) {
            let context = this
            timer = setTimeout(() => {
                fn.apply(context, args)
                timer = null            
            }, delay);
        }
    }
}

typeof

就是用Object.prototype.toString.call判断

function getType(value) {
  // 判断数据是 null 的情况
  if (value === null) {
    return value + "";
  }
  // 判断数据是引用类型的情况
  if (typeof value === "object") {
    let valueClass = Object.prototype.toString.call(value),
      type = valueClass.split(" ")[1].split("");
    type.pop();
    return type.join("").toLowerCase();
  } else {
    // 判断数据是基本数据类型的情况和函数的情况
    return typeof value;
  }
}

CALL

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
    1. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  2. 处理传入的参数,截取第一个参数后的所有参数。
  3. 将函数作为上下文对象的一个属性。
  4. 使用上下文对象来调用这个方法,并保存返回结果。
  5. 删除刚才新增的属性。
  6. 返回结果。
Function.prototype.myCall = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError("Error")
    }
    let result = null
    context = context || window
    let args = [...arguments].slice(1)
    context.fn = this
    result = context.fn(...args)
    delete context.fn
    return result
}

Apply

参数为数组

Function.prototype.myApply = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError("Error")
    }
    let result = null
    context = context || window
    context.fn = this
    if(arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}

Bind

  • 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  • 保存当前函数的引用,获取其余传入参数值。

  • 创建一个函数返回

  • 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError("error")
    }
    let _this = this
    let args = [...arguments].slice(1)
    const newFn = function (...rest) {
        return _this.call(
            this instanceof newFn ? this : context,
            ...args, ...rest
        )
    }
    return newFn
}

浅拷贝

  1. 浅拷贝就是对目标对象的基本数据类型的数据进行复制(在栈中创建新的地址存放复制的数据,而对引用数据类型在栈中存放地址,该地址与目标对象存放着同一(指向堆的)地址。
  2. 浅拷贝导致在修改引用数据类型时,由于两者都指向同一地址,所以在修改引用数据类型的数据,一者更改,二者生效
  3. 浅拷贝与直接赋值的区别在于基本数据类型,直接赋值情况下两者存放基本数据类型的方式是指向同一个数据地址。换句人话说,赋值是指仅仅拷贝了原对象的指针,浅拷贝是创建一个对象,如果是基本数据类型,就拷贝原数据,如果是引用数据类型,就拷贝指针。

实现浅拷贝的方法

Object.assign

let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);  
console.log(target);  // {a: 1, b: 2, c: 3}

Array.prototype.slice

let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() == arr); //false

Array.prototype.concat

let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() == arr); //false

扩展运算符

let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

手写深拷贝

  • 首先确定函数参数,target为拷贝对象,map判断是否循环引用
  • 然后确定终止条件,如果target为空就终止
  • 然后确定单层递归逻辑,如果target为对象/数组,就创建一个新对象/数组,然后遍历target,递归其属性,并将target作为key,clonetarget作为value,确认target已经被clone过,防止循环引用
  • 如果不是对象、数组,则为基本数据类型,直接返回其值target
function deepclone(target, map = new Map()) {
    if(!target) return 
    if(typeof target === "object") {
        let cloneTarget = Array.isArray(target) ? [] : {}
        if(map.get(target)) {
            return map.get(target)
        }
        map.set(target, cloneTarget)
        for(let key in target) {
            cloneTarget[key] = deepclone(target[key], map)
        }
        return cloneTarget
    } else {
        return target
    }
}

const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
target.target = target;

深拷贝

深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败

实现深拷贝的方法

JSON.parse(JSON.stringify())

  • JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringifyjs对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。

  • 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。

image.png

let obj1 = {  a: 0,
              b: {
                 c: 0
                 }
            };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

lodash.cloneDeep

//  npm i -S lodash
var _ = require('lodash');

const obj1 = [
  1,
  'Hello!',
  { name: 'jsliang1' },
  [
    {
      name: 'LiangJunrong1',
    }
  ],
]
const obj2 = _.cloneDeep(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';

console.log(obj1);
// [
//   1,
//   'Hello!',
//   { name: 'jsliang1' },
//   [
//     { name: 'LiangJunrong1' },
//   ],
// ]

console.log(obj2);
// [
//   2,
//   'Hi!',
//   { name: 'jsliang2' }, 
//   [
//     { name: 'LiangJunrong2' },
//   ],
// ]

手写浅拷贝

待完善,可参考如何写出一个惊艳面试官的深拷贝?

const clone = function(target) {
    let obj = Array.isArray(target) ? [] : {}
    for(let key in target) {
        obj[key] = target[key]
    }
    return obj
}


AJAX

const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
  if (this.readyState !== 4) return;
  // 当请求成功时
  if (this.status === 200) {
    handle(this.response);
  } else {
    console.error(this.statusText);
  }
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
  console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);

Promise封装AJAX

// promise 封装实现:
function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}

日期格式化

const dateFormat = function(dataInput, format) {
    var day = dataInput.getDate()
    day = day < 10 ? '0' + day : day
    var month = dataInput.getMonth()
    month = month < 10 ? '0' + month : month
    var year = dataInput.getFullYear()
    format = format.replace(/yyyy/, year)
    format = format.replace(/MM/, month)
    format = format.replace(/dd/, day)
    return format
}

console.log(dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') );
console.log(dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') );
console.log(dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日')); 

交换a,b的值,不能用临时变量

a = a + b
b = a - b
a = a - b

实现数组的乱序输出

const arr = [1,2,3,4,5,6,7,8,9,10]
arr.sort(() => {
    return Math.random() - 0.5
})
console.log(arr);

实现数组元素求和

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
const arr = [1,2,3,4,5,6,7,8,9,10]
let res = arr.reduce((prev, next) => {
    return prev += next
}, 0)
console.log(res);
  • arr=[1,2,3,[[4,5],6],7,8,9],求和
var arr=[1,2,3,[[4,5],6],7,8,9]
let res = arr.toString().split(',').reduce((prev, next) => {
    return prev += Number(next)
}, 0)
let res2 = arr.flat(3).reduce((prev, next) => {
    return prev += next
})
console.log(res);
console.log(res2);

数组扁平化

let arr = [1, [2, [3, 4, 5]]]

function flatten(arr) {
    let result = []
    for(let item of arr) {
        if(Array.isArray(item)) {
            result = result.concat(flatten(item))
        } else {
            result.push(item)
        }
    }
    return result
}

function flatten2(arr) {
    return arr.reduce((prev, next) => {
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    },[])
}

function flatten3(arr) {
    while(arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr)
    }
    return arr
}

function flatten4(arr) {
    return arr.toString().split(',').map(item => Number(item))
}
console.log(flatten4(arr));

数组去重

  • ES6
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(array)]);
console.log(Array.from(new Set(array)))
  • ES5
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

function uniqueArray(array) {
    let map = {}
    let res = []
    for(var i = 0; i < array.length; i++) {
        if(!map.hasOwnProperty([array[i]])) {
            map[array[i]] = 1
            res.push(array[i])
        }
    }
    return res
}

console.log(uniqueArray(array));

手写flat

function _flat(arr, depth) {
    if(!Array.isArray(arr) || depth <= 0) {
        return arr
    }
    return arr.reduce((prev, next) => {
        if(Array.isArray(next)) {
            return prev.concat(_flat(next, depth - 1))
        } else {
            return prev.concat(next)
        }
    }, [])
}

const arr = [1,2,3,[4,5,[6,7]]]
console.log(_flat(arr,3));

手写push

let arr = [];
Array.prototype.push = function() {
	for( let i = 0 ; i < arguments.length ; i++){
		this[this.length] = arguments[i] ;
	}
	return this.length;
}

手写Filter

Array.prototype._filter = function(fn) {
    if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {
        fn(this[i]) && res.push(this[i]);
    }
    return res;
}

手写Array.prototype.map

Array.prototype._map = function(fn) {
   if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {
        res.push(fn(this[i]));
    }
    return res;
}

手写字符串的join方法

join就是在每个元素之间用传入的参数分割

function repeat(s, n) {
    return (new Array(n + 1)).join(s);
}

字符串反转

转为数组调用reverse

String.prototype._reverse = function(a){ return a.split("").reverse().join(""); }

千分位加入逗号

function format(target) {
    const arr2 = target.toString().split(".")
    const arr = arr2[0].split("")
    let flag = 0
    for(let i = arr.length - 1; i >= 0; i --) {
        flag ++
        if(flag % 3 === 0) {
            arr.splice(i, 0, ",")
        }
    }
    return arr.join('') + '.' +arr2[1] 
}

console.log(format(1112345.1234));

红绿灯

function fn(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(data.color)
        }, data.delay);
    })
}

async function loadData(arr) {
    while(1) {
        console.log(await fn(arr[0]))
        console.log(await fn(arr[2]))
        console.log(await fn(arr[1]))
    }
}

const arr = [
    {
        color: "red",
        delay: 3000
    },
    {
        color: "green",
        delay: 2000
    },
    {
        color: "yellow",
        delay: 1000
    }
]

loadData(arr)

每隔一秒打印1、2、3、4

for(var i = 1; i < 5; i++) {
    ;(function(i) {
        setTimeout(() => {
          console.log(i);  
        }, 1000 * (i - 1));
    })(i)
}

for(let i = 1; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, (i - 1) * 1000);
}

小孩报数

有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?

function childNum(num, count) {
    let allplayer = []
    for(let i = 0; i < num; i++) {
        allplayer[i] = i + 1
    }
    let exitCount = 0
    let counter = 0
    let curIndex = 0
    while(exitCount < num - 1) {
        if(allplayer[curIndex] !== 0) counter ++
        if(counter === count) {
            allplayer[curIndex] = 0
            exitCount ++
            counter = 0
        }
        curIndex ++ 
        if(curIndex === num) {
            curIndex = 0
        }
    }
    for(let i = 0; i < num; i++) {
        if(allplayer[i] !== 0) {
            return allplayer[i]
        }
    }
}

console.log(childNum(30, 3));

图片异步加载

let imageAsync = (url) => {
    return new Promise((resolve, reject) => {
        let img = new Image()
        img.src = url
        img.onload = () => {
            resolve(img)
        }
        img.onerror = (err) => {
            reject(err)
        }
    })
}

imageAsync("url")
  .then(() => {
    console.log("success");
  })
  .catch((err) => {
    console.log(err);
  })

setTimeout实现setInterval

let timer = null 
function interval(fn, delay = 0) {
    let interfunc = function() {
        fn.call(this)
        timer = setTimeout(interfunc, delay);
    }
    timer = setTimeout(interfunc, delay);
}

interval(() => {
    console.log('no way');
},1000)

setTimeout(() => {
    clearTimeout(timer)
}, 5000);

实现prototype继承

让子类的构造函数的显式原型 指向 父类的实例

//父方法
function SupperFunction(flag1){
    this.flag1 = flag1;
}

//子方法
function SubFunction(flag2){
    this.flag2 = flag2;
}

//父实例
var superInstance = new SupperFunction(true);

//子继承父
SubFunction.prototype = superInstance;

//子实例
var subInstance = new SubFunction(false);
//子调用自己和父的属性
subInstance.flag1;   // true
subInstance.flag2;   // false

函数柯里化

function curry(fn, ...args) {
    return function() { 
        args = [...args, ...arguments]
        if(args.length < fn.length) {
            return curry(fn, ...args)
        } else {
            return fn.apply(null, args)
        }
    }
}

function bar(a,b,c) {
    return a + b + c
}
var f = curry(bar)
console.log(f(1)(2,3));

类数组转化为数组

Array.prototype.slice.call(arrayLike);
Array.prototype.splice.call(arrayLike, 0);
Array.prototype.concat.apply([], arrayLike);

字符串转函数也可以用Array.from

Array.from(arrayLike);

reduce求和

arr = [1,2,3,4,5,6,7,8,9,10],求和

let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => { return prev + cur }, 0)

arr = [1,2,3,[[4,5],6],7,8,9],求和

let arr = [1,2,3,4,5,6,7,8,9,10]
arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)

arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和

let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] 
let res = arr.reduce((prev, next) => {
    for(let i in next) {
        prev += next[i]
    }
    return prev
}, 0)

js对象转树形结构

// 转换前:
source = [{
            id: 1,
            pid: 0,
            name: 'body'
          }, {
            id: 2,
            pid: 1,
            name: 'title'
          }, {
            id: 3,
            pid: 2,
            name: 'div'
          }]
// 转换为: 
tree = [{
          id: 1,
          pid: 0,
          name: 'body',
          children: [{
            id: 2,
            pid: 1,
            name: 'title',
            children: [{
              id: 3,
              pid: 1,
              name: 'div'
            }]
          }
        }]

非递归

function jsonToTree(data) {
    let result = []
    if(!Array.isArray(data)) {
        return result
    }

    let map = {}
    data.forEach( item => {
        map[item.id] = item
    })
    data.forEach(item => {
        let parent = map[item.pid]
        if(parent) {
            (parent.children = []).push(item)
        } else {
            result.push(item)
        }
    })
    return result
}

递归

        function jsonTree(target, pid) {
            function loop(pid) {
                return target.reduce((prev, cur) => {
                
                    if (cur.pid === pid) {
                        cur.children = loop(cur.id)
                        prev.push(cur)
                    }
                    return prev// pre就是每个[].push(cur)
                }, [])
            }
            return loop(pid)
        }
        console.log(jsonTree(source, 0));