- Object.create 和 new 的相互实现,原型链和继承的原理
<!-- 通过Object.create(null)创建的对象是非常纯净的,原型链的属性和方法都不会携带。这就非常适合数组对象开发的时候,从对象中取值,提高循环效率 -->
<!-- 首先要明白原型链的原理,每一个构造函数都有一个 prototype 属性(一个原型对象)这个对象上所有的属性和方法都会被构造函数拥有 -->
<!-- 对象都有一个 __proto__ 属性指向构造函数的 prototype 原型对象 -->
<!-- 对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身 -->
function Phone(system) {
this.system = system,
}
const xiaomi = new Phone(android);
console.log(xiaomi.__proto__ === Phone.prototype) // true
Object.create 可以创建空(构造)函数,关联它的原型(实现继承)
<!-- 其本质是不用 new 的特性,而是用显式原型继承的法子,这样就不用因使用 new 而产生副作用 -->
Object.create= function (proto){
function Fun () {};
Fun.prototype = proto;
return new Fun();
}
Object.create({name: "steven"})
function _new(Constructor, ...args) {
const obj = Object.create(null);
obj.__proto__ = Constructor.prototype;
<!-- 构造函数内部 this 被赋值为新对象后,执行构造函数内部代码 -->
const result = Constructor.apply(obj, args);
<!-- 如果构造函数返回非空对象,则返回该对象,否则返回创建的对象 -->
return typeof result === "object" ? result : obj;
}
function User(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
const user = _new_(User, 'johnny', 'joestar')
// new带来的原型链关系是:实例.__proto__ === 构造函数.prototype
// Object.create 的则是:实例.__proto__ === 传入的对象
// 1. 原型链继承
function Person () {
this.brain = 'smart'
}
Person.prototype.getBrain = function () {
console.log("brain is: " + this.brain)
}
Person.prototype.age = 25;
function JoestarFamily (name) {
this.name = name
this.sayName = function () {
console.log("say: " + this.name)
}
}
JoestarFamily.prototype = new Person()
// 等同于 JoestarFamily.prototype.__proto__ === 实例.__proto__ === Person.prototype,但要注意 Person 没有实例化的话,内部的 brain 就不会生成
JoestarFamily.prototype.constructor = JoestarFamily; // 原型的 constructor 指回原来的构造函数
var johnny = new JoestarFamily('johnny')
// 等同于 johnny.__proto__ === JoestarFamily.prototype
// 也就是说 johnny.__proto__.__proto__ === Person.prototype
console.log(johnny.name)
console.log(johnny.age)
console.log(johnny.brain)
console.log(johnny.sayName())
console.log(johnny.getBrain())
// 继承思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
// 优点:父类/父类原型新增属性和方法,子类实例可访问
// 缺点 1. 实现所有属性方法共享,但无法做到属性、方法独享(例如Sub1修改了父类的函数,其他所有的子类Sub2、Sub3...想调用旧的函数就无法实现了);2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数
// 2. 构造函数继承
function SuperType (name) {
this.name = name;
}
function SubType () {
SuperType.call(this, "SubType-name");
this.age = 20;
}
const _instance = new SubType();
console.log(_instance.name);
console.log(_instance.age);
// 继承思想:在子类型的构造函数的内部调用超类型构造函数,通过 apply,call 方法实现,原理是 this 的应用
// 优点:1. 可以在在类型构造函数中向超类型构造函数传递参数;2. 解决了原型链中子类实例共享父类引用属性的问题;3. 可以实现多继承(call 多个父类对象)
// 缺点:1. 实现所有属性方法独享,但无法做到属性、方法共享((例如,Sub1新增了一个函数,然后想让Sub2、Sub3...都可以用的话就无法实现了,只能Sub2、Sub3...各自在构造函数中新增);2. 占用内存,每个子类都有父类的属性和方法(一模一样),影响性能
// 3. 显式原型继承分别是Object.create、Object.setPrototypeOf,隐式原型继承则是 new、对象字面量
// 4. ES6 的类继承,是模拟类继承而出现的一语法糖 ,它的底层实现还是基于 prototype
// 笔试例子
// 1. Student 继承 Person
// 2. Person 包含一个实例变量 name, 包含一个实例方法 printName
// 3. Student 包含一个实例变量 score, 包含一个实例方法 printScore
// 4. Person 和 Student 之间共享一个方法 printCommon
function Person (name) {
this.name = name;
this.printName = function () {
console.log(this.name);
}
}
Person.prototype.commonMethods = function () {
console.log("共享方法")
}
function Student (name, scope) {
this.score = score;
this.printScore = function () {
console.log(this.score);
}
Person.call(this, name)
}
// 这里是为了避免调用两次构造函数,生成两份实例(造成不必要地内存开销)
// 个方法就是引用了 Object.create 的核心代码,其本质是不用 new 的特性,而是用显式原型继承的法子,这样就不用因使用 new 而产生副作用
// new 是会有副作用的,它不仅会建立原型链关系,而且会执行构造函数中的代码,将其赋予内存中生成的一个对象,并返回它成为实例,而像显式原型继承则只做关系(原型链)的链接
const F = function () { }
F.prototype = Person.prototype;
Student.prototype = new F();
var johnny = new Person('johnny')
var elaine = new Student('elaine', 99)
console.log(johnny.commonMethods === elaine.commonMethods)
- 请写一个通用的 JavaScript 函数,来找出某个对象身上的某个属性继承自哪个对象
<!-- 利用 hasOwnProperty 检测属性是否是对象自有属性 -->
function findPrototypeByProperty(obj, propertyName) {
while(obj) {
if(obj.hasOwnProperty(propertyName)) return obj;
else obj = obj.__proto__;
}
}
var b = 123;
console.log(Object.getPrototypeOf(b) === Number.prototype); // true
console.log(Number.prototype === b.__proto__); // true
const obj0 = { a: 1 }
const obj1 = Object.create(obj0)
obj1.b = 2
const obj2 = Object.create(obj1)
obj2.c = 3
console.log(findPrototypeByProperty(obj2, "c") === obj2) // true
console.log(findPrototypeByProperty(obj1, "b") === obj1) // true
console.log(findPrototypeByProperty(obj0, "a") === obj0) // true
- 实现 instanceof
<!-- instanceof 是判断左侧对象是否是右侧构造函数的实例化对象 -->
const _instanceof = (target, origin) => {
while(target) {
if(target.__proto__ === origin.prototype) return true;
target === target.__proto__
}
return false
}
- 实现对象代理
<!-- vue2 是用 Object.defineProperty 实现数据相应 -->
const obj = { count: 0 };
let count = 0;
Object.defineProperty(obj, "count", {
get: function () {
return count++;
}
})
console.log(obj.count);
console.log(obj.count);
<!-- vue3采用的是 proxy 实现数据响应 -->
<!-- proxy 是对整个对象的代理,对象上新增属性 proxy 可以监听到,defineProperty不行,数组新增修改也是一样;若对象内部属性要全部递归代理,Proxy可以只在调用的时候递归,而 Object.definePropery 需要一次完成所有递归,性能比 Proxy 差 -->
// proxy 实现链式调用
const pipe = (val) => {
const funStack = [];
const proxy = new Proxy({}, {
get: (obj, fnName) => {
if (fnName === "getRes") {
return funStack.reduce((prevVal, fn) => fn(prevVal), val);
}
funStack.push(window[fnName]);
return proxy;
}
})
return proxy
}
const window = {
double: n => n * 2,
reverseInt: n => n.toString().split("").reverse().join("") | 0
}
console.log(pipe(8).double.reverseInt.getRes) // 61
- 防抖+节流
<!-- 防抖主要应用在搜索联想,窗口变化的情况 -->
function debounce (func, delay) {
let timer = null;
return function (...args) {
if (timer) clearTimout(timer);
timer = setTimeout(() => func.call(this, ...args), delay);
}
}
<!-- 节流主要应用在监听滚动事件上 -->
function throttle (func, delay) {
let timer = null;
return function (...args) {
if (timer) return;
timer = setTimout(() => {
func.apply(this, args);
timer = null;
}, delay)
}
}
<!-- 利用定时器是首次不执行的写法,如果需要首次执行则可以利用时间戳 -->
function _throttle (func, delay) {
let prev = 0;
return function (...args) {
const curr = new Date().valueof();
if (curr - prev > delay) {
func.apply(this, args);
prev = curr;
}
}
}
<!-- 时间戳 + 定时器 的节流写法 -->
function throttle (func, wait) {
let prev = 0;
let timer = null;
return function (...args) {
const curr = new Date().valueof();
// 第一次点击和间隔内的点击都靠时间戳判定触发
if (curr - prev > wait) {
clearTimeout(timer);
timer = null;
prev = curr;
func.apply(this, args);
}
// 最后一次点击
else {
timer = setTimeout(() => {
func.apply(this, args)
})
}
}
}
- 实现发布者,订阅者
class PubSub {
constructor() {
this.subscribles = [];
}
subscrible (subscribleName, callback) {
this.subscribles.push(callback);
}
unSubscrible (subscribleName, callback) {
if (this.subscribles[subscribleName]) {
const index = this.subscribles.indexOf(callback);
this, this.subscribles.splice(index, 1);
}
}
publish (subscribleName, ...args) {
this, this.subscribles.forEach((callback) => callback(...args))
}
}
const [sub1, sub2] = [(...arg) => console.log("sub1", ...arg), (...arg) => console.log("sub2", ...arg)];
const pubSub = new PubSub();
pubSub.subscrible("SUB", sub1)
pubSub.subscrible("SUB", sub2)
pubSub.publish("SUB", 1, 2, 3) // sub1 1 2 3 \n sub2 1 2 3
- 实现 map 方法
Array.prototype._map = function (callback, thisVal) {
const arr = this;
const res = [];
for (const [i, item] of arr.entries()) {
res[i] = callback.call(thisVal, item, i, arr);
}
return res;
}
<!-- [0,2,4] -->
console.log([0, 1, 2].map((num, i) => num + i))
console.log([0, 1, 2]._map((num, i) => num + i))
- 实现 reduce
Array.prototype._reduce = function (callback, accmulate) {
const arr = this;
const len = arr.length;
let i = accmulate ? 0 : 1;
for (; i < len; i++) {
accmulate = callback(accmulate || arr[0], arr[i], i, arr);
}
return accmulate;
}
// 11/0/1*2/1/2*3/2/3*
console.log([1, 2, 3]._reduce((prev, curr, i, arr) => prev + curr + "/" + String(i) + "/" + String(arr[i]) + "*", 10));
console.log([1, 2, 3].reduce((prev, curr, i, arr) => prev + curr + "/" + String(i) + "/" + String(arr[i]) + "*", 10));
- 实现数组拍平
const multiArr = [1, 2, [3, 4, [5], [[[6]]], [[7], 8], 9], 10];
const flat = (arr) => {
let res = [];
const len = arr.length;
for (let i = 0; i < len; i++) {
if (Array.isArray(arr[i])) res = res.concat(flat(arr[i]));
else res.push(arr[i]);
}
return res
}
// [1,2,3,4,5,6,7,8,9,10]
console.log(multiArr.flat(Infinity));
console.log(flat(multiArr))
- 实现函数柯理化
// 参数个数
if (args.length >= fn.length) return fn(...args);
return function (..._args) {
// 利用闭包保存 fn 和之前参数 args,递归生成新函数
return carry(fn, ...args, ..._args);
}
}
const add = (a, b, c) => a + b + c;
console.log(carry(add, 1, 2, 3))
console.log(carry(add)(2, 3, 4))
console.log(carry(add)(1, 2))
console.log(carry(add)(1)(2)(3))
- 实现 promise.all 和 promise.race
function promiseAll (promiseQuene = []) {
return new Promise((resolve, reject) => {
const len = promiseQuene.length;
const result = [];
for (let i = 0; i < len; i++) {
// 防止队列里不是promise,从而缺失then方法后报错
Promise.resolve(promiseQuene[i]).then(res => {
result.push(res);
if (result.length === len) resolve(result);
}).catch(err => reject(err));
}
})
}
function promiseRace (promiseQuene = []) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseQuene.length; i++) {
Promise.resolve(promiseQuene[i]).then(res => resolve(res)).catch(err => reject(err))
}
})
}
const [p1, p2] = [
new Promise((resolve) => setTimeout(() => resolve("p1-1000"), 1000)),
new Promise((resolve) => setTimeout(() => resolve("p2-4000"), 4000))
]
// [ 'p1-1000', 'p2-4000' ]
promiseAll([p1, p2]).then(result => console.log(result))
- 实现深拷贝、浅拷贝
<!-- 浅拷贝是按位拷贝对象,他会创建一个新对象,这个对象有原始对象属性值的一份精准拷贝 -->
<!-- 如果属性值是基本类型,拷贝的是值,如果属性值是引用类型,拷贝的就是内存地址 -->
<!-- 浅拷贝有:Object.assign, slice, concat -->
function shallowClone (obj) {
const newObj = Object.create(null);
for (const item in obj) newObj[item] = obj[item];
}
<!-- JSON.stringfy 实现深拷贝的缺点有:循环引用对象执行方法会抛错,以 Symbol 类型为属性值的属性都会被忽略掉,缺少针对其他的内置构造函数的兼容,如 Function、RegExp、Date、Set、Map -->
function deeepClone (obj) {
const newObj = Object.create(null);
for (const item in obj) {
const type = Object.prototype.toString.call(obj[item]).slice(8, -1);
if (type === "Array") newObj[item] = obj[item].map((_item) => deeepClone(_item))
if (type === "Object") newObj[item] = deeepClone(obj[item])
else newObj[item] = obj[item];
}
return newObj;
}
- 实现 promise
<!-- promise 是一个对象,保存着未来将要结束的事件,代表一个异步操作,有三种状态 -->
class _Promise {
constructor(callback) {
// promise 回调只接受一个参数,多个可采用数组或对象形式
this.msg = null;
this.status = "pending";
callback(this.resolve.bind(this), this.reject.bind(this))
}
resolve (val) {
this.status = "fulfilled";
this.msg = val;
}
reject (err) {
this.status = "rejected";
this.msg = err
}
then (fulfilled, rejected) {
this.status === "fulfilled" && fulfilled(this.msg)
this.status === "rejected" && rejected(this.msg)
}
}
const newPromise = new _Promise((resolve, reject) => {
try {
// resolve("promise done")
throw new Error("fake error")
} catch (err) {
reject(err)
}
});
newPromise.then((res) => console.log(res), (err) => console.log(err))
- promise 时序打印
setTimeout(() => {
console.log("setTimeout1");
new Promise((resolve) => {
console.log("promise1_in_setTimeout1");
resolve();
}).then(() => {
setTimeout(() => console.log("setTimeout2_in_then_of_promise1"), 1000);
console.log("then_in_promise1_of_setTimeout1")
})
}, 1000);
async function async1() {
// 函数内-宏任务
console.log('async1_start');
// 注意这里生成一个promise
await async2();
// promise内是宏任务,then内是微任务
console.log('async1_end');
}
async function async2() {
console.log('async2');
}
async1();
console.log("script_start");
new Promise((resolve) => {
console.log("promise2");
resolve();
console.log("promise2_end");
}).then(() => {
console.log("then_in_promise2")
})
setTimeout(() => console.log("setTimeout2"), 1000);
console.log("script_end");
// JS是单线程执行的语言,在异步模式下,创建异步任务主要分为宏任务与微任务两种
// 不进入主线程,而是进入任务队列,当主线程中的任务执行完毕,
// 就从任务队列中取出任务放进主线程中来进行执行。由于主线程不断重复的获得任务、
// 执行任务、再获取再执行,所以者种机制被叫做事件循环
// 宏任务一般由宿主(node,浏览器)发起如:script,setTimeout,微任务是由js本身发起如:promise,nectTick,先同步任务再微任务后宏任务
// async1_start => async2 => script_start => promise2 => promise2_end => script_end
// ...开始第一轮循环机制里的 微任务 async1_end => then_in_promise2
// ...开始第一轮循环机制里 宏任务 setTimeout1
// ...开始第二轮循环机制 即第一轮循环机制里的 第一个宏任务里的微任务 promise1_in_setTimeout1 => then_in_promise1_of_setTimeout1
// ...开始第二轮循环机制 即第一轮循环机制里的 第二个宏任务 setTimeout2
// ...开始第三轮循环机制 即第二轮循环机制里的 第一个微任务里的 第一个宏任务 setTimeout2_in_then_of_promise1