都是自己以前总结的知识点,复习一下顺便发出来加深印象,仅供参考有错误感谢指出。
1. 手写简单Symbol
主要是实现了 Symbol 的几个特征,用对象模拟Symbol,但是并不完善
function SymbolPollify(description) {
// 不能用 new 构造
if(new.target !== SymbolPollify) {
throw new Error("Symbol 不能用 new 构造")
}
let symbol = Object.create({
toString() {
return `Symbol(${this.__description__})`
}
})
// 无论传入什么 description 都转化为 字符串
let __description__ = String(description);
Object.defineProperty(symbol, "__description__", {
value: __description__,
writable: false,
configurable: false,
enumerable: false
});
return symbol;
}
let symbol_ = new SymbolPollify("111");
console.log(symbol_.toString());
2. 用 Object.prototype.toString 封装一个通用 type 检测数据类型的工具
let obj2type = {};
let types = 'Number BigInt String Boolean Null Undefined Symbol Object Array Date Error RegExp Function';
types.split(' ').forEach(item => {
obj2type[`[object ${item}]`] = item.toLowerCase();
})
// typeof 能直接判断的用typeof 否则用 toString 做 key
let TypeChecker = type => typeof type !== 'object' ? typeof type : obj2type[Object.prototype.toString.call(type)]
// test
console.log(TypeChecker(123)); // number
console.log(TypeChecker([1, 2, 3])); // array
console.log(TypeChecker(() => {})); // function
3. 手写一个 instanceof 操作符
function InstanceofPollify(left, right) {
const isObject = typeof left === 'object' || typeof left === 'function';
// 左操作符是基本数据类型,直接返回 false
if(!isObject || left === null) return false;
if(typeof right !== 'function') throw new Error('右操作法必须是可调用的函数');
// 考虑 右操作符 有 [Symbol.hasInstance] 的情况,只适用于 class 类
if(right.hasOwnProperty(Symbol.hasInstance)) {
return right[Symbol.hasInstance](left);
}
let proto = Object.getPrototypeOf(left);
while(proto) {
if(proto === right.prototype) return true;
if(proto === null) return false;
proto = Object.getPrototypeOf(left);
}
}
// 对于 instanceof 操作法无法检测基本操作类型的 缺陷
var a = 1;
console.log(a instanceof Number); // false
// 利用 [Symbol.hasInstance] 属性
class PrimitiveNumber {
static [Symbol.hasInstance](left) {
return typeof left === 'number';
}
}
console.log(a instanceof PrimitiveNumber); // false
4. 手写深拷贝
const toBeCloneObj = {
firstname: '飞行员',
lastname: 'Pilot',
address: {
province: 'Beijing',
attachnumber: [1, { number: 1 }, 0, {testmap: new Map(), testset: new Set()}],
}
}
toBeCloneObj.address.myself = toBeCloneObj; // 循环引用
// 最简单版 区分普通对象和 Array
function deepClone(sourceObj) {
if(typeof sourceObj === 'object' && sourceObj !== null) {
let cloneObj = Array.isArray(sourceObj) ? [] : {};
for(let key in sourceObj) {
if(sourceObj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(sourceObj[key]);
}
}
return cloneObj;
}
else return sourceObj;
}
// v2: 解决循环引用问题
function deepCloneV2(sourceObj, map = new WeakMap()) {
if(typeof sourceObj === 'object' && sourceObj !== null) {
let cloneObj = Array.isArray(sourceObj) ? [] : {};
if(map.get(sourceObj)) return map.get(sourceObj);
map.set(sourceObj, cloneObj);
for(let key in sourceObj) {
if(sourceObj.hasOwnProperty(key)) {
cloneObj[key] = deepCloneV2(sourceObj[key], map);
}
}
return cloneObj;
}
else return sourceObj;
}
// v3:解决常见的如 Set Map 数据结构问题
const MapType = '[object Map]';
const SetType = '[object Set]';
const isObject = obj => typeof obj === 'object' && obj !== null;
const Type = obj => Object.prototype.toString.call(obj);
function deepCloneV3(sourceObj, map = new WeakMap()) {
if(isObject(sourceObj)) {
const ctor = sourceObj.constructor;
let cloneObj = new ctor();
if(map.get(sourceObj)) return map.get(sourceObj);
map.set(sourceObj, cloneObj);
if(Type(cloneObj) === MapType) {
for([key, value] of sourceObj) cloneObj.set(key, value)
}
if(Type(cloneObj) === SetType) {
for(value of sourceObj) cloneObj.add(value)
}
for(let key in sourceObj) {
if(sourceObj.hasOwnProperty(key)) {
cloneObj[key] = deepCloneV3(sourceObj[key], map);
}
}
return cloneObj;
}
else return sourceObj;
}
console.log(deepCloneV3(toBeCloneObj));
5. 手写一个 new 操作符
// new 是操作符,所以我们只能够用函数来模拟
function objectFatory() {
var obj = new Object();
// 第一个参数是构造函数,其余参数要继续参与运算 shift一举两得
var Constructor = [].shift.apply(arguments);
if(typeof Constructor !== 'function'){
throw 'newOperator function the first param must be a function';
}
// 设置 obj 的 __proto__ 指向 Constructor.prototype
Object.setPrototypeOf(obj, Constructor.prototype);
// new 的返回值不是 引用值,则返回 obj
// 继承方法。借用构造函数(经典继承)
var ret = Constructor.apply(obj, arguments);
// 必须为对象,考虑isObject 考虑 typeof的特殊性 函数会返回 function
let isObject = typeof ret === "object" && ret !== null;
let isFunction = typeof ret === "function";
return isObject || isFunction ? ret : obj;
}
6. 实现 Object.create
Object.creat 和 new 的区别在于前者只是将对象的原型链连接好,并不是执行构造函数。
// 手动实现一个 Object.create
const _create = function (o) {
let F = function () {}
F.prototype = o
return new F()
}
7. 手写 apply call bind
/**
* 所以我们模拟的步骤可以分为:
* 将函数设为对象的属性,若考虑对象属性可能会重写,可以用Symbol
* 执行该函数
* 删除该函数
*/
Function.prototype.myCall = function (context) {
// 非严格模式下
// null 和 undefined 指向全局对象,原始值进行 Object 操作顺便可以装箱
context = context == null ? globalThis : Object(context);
context.fun = this; // 可以用 Symbol 做属性名
let args = []; // 将 arguments 类数组转换为数组
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
let result = context.fun(...args);
delete context.fun;
return result;
};
function a(name, age) {
console.log(this.a);
console.log(name);
console.log(age);
}
var obj = {
a: "dwad",
};
a.myCall(obj, "ddd", 18);
/**
* 所以我们模拟的步骤可以分为:
* 将函数设为对象的属性,若考虑对象属性可能会重写,可以用Symbol
* 执行该函数
* 删除该函数
*/
Function.prototype.myApply = function (context, args) {
//null 和 undefined 指向全局对象,否则进行 Object 操作顺便可以装箱
context = context == null ? globalThis : Object(context);
context.fun = this; // 可以用 Symbol 做属性名
let result = undefined;
if (!args) {
result = context.fun();
} else {
result = context.fun(args);
}
delete context.fun;
return result;
};
function a(obj) {
console.log(this.a);
console.log(obj);
}
var obj = {
a: "dwad",
};
a.myApply(obj, ["ddd", 18]);
/**
* bind 函数的特点:
* 1. 返回一个函数
* 2. 可以给函数预定传入参数,别的参数依次排到预定参数后面
* 难点 3. bind 后的函数可以用 new 操作符当做构造函数使用,但是绑定的this
* 值会失效(因为new操作符的原因,this会指向新生成的对象),但预先传递的参数会生效
*/
// 实现 1 2
Function.prototype.myBind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 注意两个 arguments 不一样
// 这里 转为 args2 真正数组的原因是因为 concat的参数必须要数组或者值
var args2 = Array.prototype.slice.call(arguments);
return self.apply(context == null ? globalThis : Object(context),args.concat(args2)); // args 要放在 args2 前面
};
};
// 实现 3
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply(
this instanceof fBound ? this : context,
args.concat(bindArgs)
);
};
// 修改返回函数的 prototype 为绑定函数的实例,实例就可以继承绑定函数的原型中的值
fBound.prototype = Object.create(this.prototype);
return fBound;
};
#面试##前端#