前言
这篇主要是一些js手写的实现汇总,忘了写,写了忘,主要是复习一下吧。
new的实现
假如我们new一个构造函数Fn。(看下红宝书怎么说的吧)
- 在内存中创建一个新对象
- 这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
function newFn(fn) {
let obj = {}
obj.__proto__ = fn.prototype
//let obj = Object.create(fn.prototype) 前面两步,可以直接用这个替代
let res = fn.call(obj)
return typeof res === 'object' && res !== null ? res : obj
}
Object.create实现
- 传入一个对象obj,在内部创建一个新的对象obj2,obj2的原型指向对象obj
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
instanceof的实现
- 概念:a instanceof B,a为实例,B为构造函数,一般是检查a实例是否被B构造函数所创建
- 实现:获取a的隐式原型,判断是否与B的显式原型是同一个,不是的话继续在a的原型链上找到下一个原型与B的原型比较,如果遍历完所有原型链还没找到相同,返回false
// right.prototype 在 left.__proto__ 原型链上
function instanceof(left, right) {
if(typeof left !== 'object' || left === null) return false
let proto = Object.getPrototypeOf(left)
while(proto) {
if(right.prototype === proto) {
return true
}
proto = Object.getPrototypeOf(left)
}
return false
}
call、apply、bind的实现
call、apply的实现
call 和apply的API唯一的区别是,apply第2个参数只能是一个数组,数组里面接收函数的所有参数,call使用普通方式传参
- call应该由函数调用,否则抛出错误
- 当传入的需要绑定的参数是一个null或者undefined值,会默认绑定window;传入的是一个基本类型的值,会通过Object处理一下
Function.prototype._call = function(context, ...args) {
if(typeof this !== 'function') {
throw new TypeError('this is not function')
}
context = typeof context == null ? window : Object(context)
let caller = Symbol('caller')
context[caller] = this
let res = context[caller](...args)
delete context[caller]
return res
}
Function.prototype._apply = function(context, arr) {
if(typeof this !== 'function') {
throw new TypeError('this is not function')
}
context = typeof context == null ? window : Object(context)
let caller = Symbol('caller')
context[caller] = this
let res = context[caller](...arr)
delete context[caller]
return res
}
bind实现
- 绑定传入的参数作为this,返回一个未执行的函数
- 当返回的函数作为构造函数来使用,传入的参数作为this将失效
Function.prototype._bind = function(context, ...args1) {
if(typeof this !== 'function') {
throw new TypeError('this is not function')
}
let fn = this
let boundFunc = function(...args2) {
if(new.target) {
let res = fn.call(this, ...args1, ...args2)
return typeof res === 'object' && res !== null ? res : this
} else {
let res = fn.call(context, ...args1, ...args2)
return res
}
}
if(fn.prototype) {
boundFunc.prototype = Object.create(fn.prototype)
}
return boundFunc
}
原型继承的6种方式
原型链继承
- 实现:将父类的实例赋值给子类的原型上
- 缺点:1.父类实例上的属性将会被多个实例共享2.不能实现传参给父类
function Super() {
this.colors = ['red', 'blue', 'green']
}
function Sub() {}
Sub.prototype = new Super()
let instance1 = new Sub()
instance1.colors.push('black')
let instance2 = new Sub()
console.log(instance2.colors) // 'red', 'blue', 'green', 'black'
借用构造函数继承
- 实现:在子类里面,通过call调用父类方法
- 优点:解决了向父类传参的问题
- 缺点:父类原型上的方法和属性不能继承
// 父类
function Super(name) {
this.name = name;
this.color = ['red', 'blue', 'green']
}
Super.prototype.sayName = function(){
console.log('say name')
}
// 子类
function Sub(name) {
Super.call(this, name)
}
let instance1 = new Sub('jack')
instance1.sayName // undefined
组合继承
- 实现:综合了原型链继承和借用构造函数继承;先在子类里面call调用父类方法,再将父类的实例赋值给子类构造函数的显示原型上
- 缺点:父类构造函数被执行了2次(父类的属性,会同时存在子类实例的属性和原型上)
// 父类
function Super(name) {
this.name = name;
this.color = ['red', 'blue', 'green']
}
Super.prototype.sayName = function(){
console.log('say name')
}
// 子类
function Sub(name) {
// 第2次调用父类的构造函数
Super.call(this, name)
}
// 第1次调用父类构造函数
Sub.prototype = new Super()
let instance1 = new Sub('Jack')
原型式继承
- 实现:主要实现继承对象的属性和方法,将该对象赋值给空对象的原型
- 缺点:多个实例会共享父类引用类型的属性,无法传参
let person = {
name: 'jack',
friends: ['Van']
}
let son1 = Object.create(person);
son1.friends.push('Rob')
let son2 = Object.create(person);
son2.friends.push('Rob1')
console.log(son1.friends) // 'Van', 'Rob', 'Rob1'
寄生式继承
和原型式继承差不多,缺点也是一样,在对象增加了属性和方法
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
组合寄生式继承
目前是引用类型继承的最佳模式
- 实现:综合了借用构造函数继承和寄生式继承
// 父类
function Super(name) {
this.name = name;
this.color = ['red', 'blue', 'green']
}
Super.prototype.sayName = function(){
console.log('say name')
}
// 子类
function Sub(name) {
// 借用构造函数
Super.call(this, name)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
let instance = new Sub('jack')
Promise实现
基本版promise实现
原理也是基于发布订阅模式,先将回调函数push到一个数组中,然后在异步触发的时候,遍历执行回调函数
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(exector) {
this.value = undefinde;
this.reason = undefinde;
this.status = PENDING;
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
let resolve = function(value) {
if(this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
let reject = function(value) {
if(this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
exector(resolve, reject)
} catch(e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if(this.status === PENDING) {
this.onFulfilledCallbacks.push(()=>{
setTimeout(()=>{
onFulfilled(this.value)
}, 0)
})
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
onRejected(this.reason)
})
})
}
if(this.status === FULFILLED) {
setTimeout(()=>{
onFulfilled(this.value)
}, 0)
}
if(this.status === REJECTED) {
setTimeout(()=>{
onRejected(this.reason)
})
}
}
}
Promise.all实现
- 接收一个数组,数组里面可以传入值或者promise实例,所有实例成功后,将返回结果,有一个失败就返回失败
- 实现:通过计数来计算所有异步是否请求成功,判断是否返回结果
Promise.all = function(arr) {
if(!Array.isArray(arr)) {
throw new TypeError('... is not iterable')
}
const result = []
let count = 0
return new Promise((resolve, reject)=>{
let processData = (value, i) => {
count++;
result[i] = value;
if(count === arr.length) {
resolve(result)
}
}
for(let i = 0; i < arr.length; i++) {
let value = arr[i]
if(value && typeof value.then === 'function') {
value.then((val)=>{
processData(val, i)
}, reject)
} else {
processData(value, i)
}
}
})
}
Promise.prototype.finally实现
- 不管promise返回的是成功的结果,还是失败的结果,都会执行传入的回调函数
- 最后再将成功的结果或者失败的结果返回,可以继续调用then方法
Promise.prototype.finally = function(onFinshed) {
return this.then((res)=>{
onFinshed()
return res
})
.catch((err)=>{
onFinshed()
return err
})
}
深拷贝
- 传入的对象做遍历,判断对象的类型,从而创建一个空对象,对其赋值
function deepClone(obj, map = new WeakMap()) {
// 不是对象直接返回
if(typeof obj !== 'object' || obj === null) {
return obj;
}
// 避免循环引用
if(map.has(obj)) {
return map.get(obj)
}
// 如果是这几种类型,可以直接创建对象返回
if([RegExp, Date, Map, Set, WeakMap, WeakSet].includes(obj.constructor)) {
return new obj.constructor(obj)
}
const result = Array.isArray(obj) ? [] : {};
map.set(result, true)
Object.keys(obj).forEach(key=>{
const value = obj[key]
if(typeof value === 'object' && value !== null) {
result[key] = deepClone(value, map);
} else {
result[key] = value
}
})
return result
}
防抖
function debounce(func, time = 17 , options = { leading: true, context: null }) {
let timer = null;
const _debounce = (...args) => {
if(timer) {
clearTimeout(timer)
timer = null
}
// 第一次触发会执行
if(options.leading && !timer) {
timer = setTimeout(null, time)
func.apply(options.context, args)
} else {
timer = setTimeout(()=>{
func.apply(options.context, args)
timer = null
}, time)
}
}
return _debounce
}
节流
// trailing 表示最后一次执行
function throttle(func, time = 17, options = {context: null, trailing: false }) {
let timer = null;
let prev = 0;
const _throttle = (...args) => {
let now = Date.now()
if(now - prev > time) {
func.apply(options.context, args)
prev = now
} else if(options.trailing) {
clearTimeout(timer)
timer = setTimeout(()=>{
func.apply(options.context, args)
timer = null
}, time)
}
}
return _throttle;
}
最后
js的一些基础知识还是很简单的,只是工作重复性的工作导致我们忘记,需要偶尔的复习一下。这些很多红宝书都会讲的很详细,大家有时间可以多翻翻。