当前位置: 首页 > 面试经验 >

「前端面试必会」手写篇|持续更新

优质
小牛编辑
153浏览
2023-03-28

「前端面试必会」手写篇|持续更新

都是自己以前总结的知识点,复习一下顺便发出来加深印象,仅供参考有错误感谢指出。
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;
};



#面试##前端#
 类似资料: