JavaScript-面试手写代码整理(不完整)
目录以下按照个人记忆一级收获程度排序
-
√实现call、apply、bind
-
√实现new
-
√实现instanceof
-
√实现继承
-
√实现深拷贝和深度比较
-
√手写防抖和节流
-
实现Promise(⭐)
-
实现generator的自动执行器
-
手写通用事件绑定和事件代理
-
手写ajax
-
手写JsonP
-
实现函数柯里化
-
路由Hash
-
路由History5
其它
-
实现Array.reduce()
-
实现Array.flat() 以及数组展平
-
数组去重
-
Object.is()
-
实现JSon.stringfy和JSON.parse
-
JavaScript中的拷贝函数(不断更新)
实现call、apply、bind
说白了,就是为这个对象添加一个函数属性,这个函数和外面那个函数一个样(注意传参方式),再执行它并返回结果就行了。用完就删掉这个属性,假装没来过~
需要注意的是,原生js在使用这三个函数的时候会将基础类型的值(number、string、boolean、undefined、Symbol、null)转为包装类型(Number、String、Boolean、Symbol)以下代码没有写出这点😛
还有注意的点就是它们三个的返回值,MDN上原话:
call和apply:使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
bind:返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。
call
Function.prototype.mycall = function () {
const args = [...arguments]
const thisArg = args.shift() || window // 1 取出作用域
let fn = Symbol() //2.添加fn属性
thisArg[fn] = this //这里的this是函数的实例对象func
let result = thisArg[fn](...args) //3.执行函数
delete thisArg[fn] //4.删除新加的属性
return result //5 返回结果
}
//func.mycall(object,参数2,参数3,参数4)
apply
和call仅仅是传参方式不同而已
Function.prototype.myapply = function () {
const args = [...arguments]
const thisArg = args.shift() || window
let fn = Symbol()
thisArg[fn] = this
let result = thisArg[fn](...args[0])
delete thisArg[fn]
return result
}
//func.apply(object,[参数2,参数3,参数4])
bind
Function.prototype.mybind = function () {
const args = [...arguments]
const thisArg = args.shift()
const self = this
return function () {
return self.apply(thisArg, args)
}
}
//const func1 =func.bind(object,...)
实现new
1.创建新对象
2.空对象的__proto__指向构造函数的prototype成员对象
3.使用apply调用构造器函数,属性和方法被添加到this引用的对象
function _new(Con, ...args) {
//1,2两步可以合并成如下
//let obj = Object.create(Con.prototype)或者Object.setPrototypeOf(obj, Con.prototype)
let obj = {} //1
obj.__proto__ = Con.prototype //2
let result = Con.apply(obj, ...args) //3
// 4这里注意,如果说构造函数有return(假如假如),那么使用new返回的就是这个return的结果也就是Con.applly...的执行结果。如果说没有return,那返回我们新建的这个obj对象
return result instanceof Object ? result : obj
}
实现instanceof
instanceof运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置。
1.左边是对象,右边是构造函数(可以查看原型链,其实Object也是一个构造函数)
2.迭代。左侧对象的__proto__不等于构造函数的prototype时,沿着原型链重新赋值左侧
function instance_of(L, R) {
// 1.1如果是基本数据类型
const baseType = ['string', 'number', 'boolean', 'undefined', 'symblo']
if (baseType.includes(typeof (L))) return false
// 1.2如果是引用类型
let RP = R.prototype //取R的原型对象
L = L.__proto__
//2.迭代
while (true) {
if (L == null) { //找到最后一个了,__proto__为null(Object.prptptype.__prpto__ = null)
return false
}
if (L === RP) {
return true
}
L = L.__proto__ //该层未找到,继续往上找
}
}
实现继承
寄生组合式继承,目标就是实现如下的原型链。不建议看《JavaScript高级程序设计》中这一讲,里面的疑惑会指出,也只是个人理解,如有觉得不对请指出哈。
以下代码看了《你不知道的JavaScript 上卷》继承这块内容,简洁明了。重点代码就两行:
-
保证SubType.prototype.__proto__ = SuperType.prototype
SubType.prototype = Object.create(SuperType.prototype)
或者Object.setPrototypeOf(SubType.prototype, SuperType.prototype)
,前者要手动修复原型对象的constructor属性。 -
SuperType.call(this, name) //取了SuperType的实例属性
// 父类
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
// 子类
function SubType(name, age) {
SuperType.call(this, name) //取了SuperType的实例属性
this.age = age
}
SubType.prototype = Object.create(SuperType.prototype) //保证SubType.prototype.__proto__ = SuperType.prototype
SubType.prototype.construct = SubType//手动修复SubType.prototype.construct
//或者Object.setPrototypeOf(SubType.prototype, SuperType.prototype)
SubType.prototype.sayAge = function(){
console.log(this.age)
}
var instance = new SubType("Adagio",18)
console.log(instance)
常见错误做法
SubType.prototype = SuperType.prototype //和我们想要的机制不一样
SubType.prototype = new SuperType() //缺点JavaScript高级程序设计里有讲
《JavaScript高级程序设计》中关于寄生组合式继承主要函数是这么写的,但个人觉得使用Object不妥,代码中标出了
function inheritPrototype(subType, superType) {
var prototype = Object(superType.prototype) //寄生
//prototype === superType.prototype //true,这就是问题所在。
prototype.constructor = subType //也修改了super.prototype的constructor
subType.prototype = prototype
}
inheritPrototype(SubType, SuperType) //其实目的就是SubType.prototype = SuperType.prototype,也不是不行,只是做不到图里的那种效果,机制不同,理解起来就很费劲。
实现深拷贝和深度比较
深拷贝
function deepClone(obj) {
let cloneObj
// 1是不是对象
if (obj && typeof obj === 'object') {
//1.1对象确定类型
if (Array.isArray(obj)) {
cloneObj = []
} else {
cloneObj = {}
}
//1.2确定对象类型后取属性赋值
for (let key in obj) {
if (obj.hasOwnProperty(key)) { //保证不是原型上的属性
cloneObj[key] = deepClone(obj[key])
}
}
} else { //2.不是对象,直接返回
return obj
}
return cloneObj
}
深拷贝JSON版本
function deepClone(obj) {
let cloneObj = JSON.stringify(obj)
cloneObj = JSON.parse(cloneObj)
return cloneObj
}
深度比较
// 判断是否为对象
function isObject(obj) {
return typeof obj === 'object' && obj != null
}
function isEqual(obj1, obj2) {
// 1.有一个或都不是对象
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2
}
// 2.是同一个对象
if (obj1 === obj2) {
return true
}
// 3.不是同一个对象
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
// 3.1如果键的数量不一样
if (obj1Keys.length != obj2Keys.length)
return false
// 3.2如果键的数量一样,继续
for (let key in obj1) {
const res = isEqual(obj1[key], obj2[key])
if (!res) {
return false
}
}
// 所有都比完了,且没有false
return true
}
手写防抖和节流
防抖
函数防抖,这里的抖动就是执行的意思,而一般的抖动都是持续的,多次的。假设函数持续多次执行,我们希望让它冷静下来再执行。也就是当持续触发事件的时候,函数是完全不执行的,等最后一次触发结束的一段时间之后,再去执行。重点就是规定时间内一直按的话不会执行,冷静下来后才会执行。
-
持续触发不执行
-
在规定时间内未触发第二次,则执行
//思考题:this1,this2,this3,this4部分的this都指向什么
function debounce(func, delay = 500) {
//this1
// 利用闭包保存定时器
let timeout = null
return function () {
//this2
const context = this
const arg = arguments
if (timeout)
clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timeout = setTimeout(() => { //思考:这里如果改成function函数,this3又是什么?
//this3
func.apply(context, arguments)
}, delay)
}
}
/* const input1 = document.getElementById("input1")
input1.addEventListener('keyup', debounce(function () {
console.log(this.value) //this4
}, 1000), false) */
节流
节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。一直按的话会每隔给定时间执行。
-
持续触发并不会执行多次,在规定时间内只触发一次
-
到一定时间再去执行
// 时间戳版
function throttle(fn, wait = 500) {
// 利用闭包保存定时间
let previous = 0
return function () {
const context = this
const arg = arguments
let now = Date.now()
if (now - previous > wait) {
fn.apply(context, arg)
previous = now
}
}
}
// 定时器版
function throttle1(fn, wait = 500) {
let timer = null
return function () {
const context = this
const arg = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(context, arg)
}, wait)
}
}
}
/* <div id="box" draggable="true"></div> */
/* const box = document.getElementById('box')
box.addEventListener('drag', throttle(function (event) {
console.log(event.offsetX, event.offsetY)
}, 2000)) */
总结:throttle和debounce均是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。比如说,我搜索时,onkeyup该几次还是几次,只是我的请求变少了,处理的逻辑少了,从而提高了性能。
手写事件绑定和事件代理
实现Promise
推荐文章:掘金 离秋 前端面试考点之---手写Promise
JavaScript中的拷贝函数
以下函数请自行敲一遍。
-
直接赋值(引用地址):
obj2 = obj1
,obj2 = Object(obj1)
-
Object.assign():拷贝的也是引用地址,且不可枚举属性和继承属性不拷贝。
-
String.prototype.slice():浅拷贝,二级属性还有联系
-
String.prototype.concat():浅拷贝,二级属性还有联系
-
Array.from():浅拷贝
let f = [1, 2, [3,4,5]]
let g = Array.from(f)
g[2][0] = 0
console.log(f) // [ 1, 2, [ 0, 4, 5 ] ]