面试|前端手写代码(持续更新)

  1. 按照自己的学习进度进行更新;
  2. 主要分为以下几个部分:(1)JS基础,(2)CSS,(3)数据处理,(4)场景应用,…

JS基础

1. 手写new操作符?

(1)new?

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例

(2)在new的过程中干了什么?

基本四步走

  1. 新建一个空对象obj
  2. 链接到原型:将obj[[prototype]]属性指向构造函数的原型,即obj.[[prototype]] = construc.prototype (不懂的可以看图理解一下【图开源于b站大佬】)
  3. 绑定this:把构造函数内部的this绑定到新建的obj上,执行构造函数【显然这里需要用call、apply、bind来更改this指向】
  4. 返回新的对象
    图源自b站
function create() {
   
	// 1. 新建空对象
	let obj = new Object();
	let constructor = Array.prototype.shift.call(arguments); // 获取构造函数
	// 2. 链接到原型
	obj.__proto__ = constructor.prototype;
	// 3. 绑定this
	let result = constructor.apply(obj, arguments);
	// 4. 返回新对象
	return typeof result === 'object' ? result : obj;
}

(3)几个小疑问?

  • arguments是什么?
    arguments是一个对应于传递给函数的参数的类数组对象,且arguments对象只能在函数内使用
  • [].shift.call()干了什么?
    获取arguments的第一个参数,改变arguments的length

2. 手写instanceof

instanceof 可以用于正确判断对象的类型,其内部机制为通过判断对象的原型链中是否含有该类型的原型prototype
所以不难想到instanceof的实现步骤:

  1. 获取类型的原型 && 获取对象的原型
  2. 循环判断对象的原型是否等于类型的原型,直到对象的原型为null【原型链最终为null,具体可以看下上面的图】
function isInstanceOf(left, right) {
   
	let proto = Object.hasPrototypeOf(left), // 获取对象的原型
		prototype = right.prototype; // 获取构造函数的prototype对象
	
	// 判断构造函数的prototype对象是否在对象的原型链上
	while (true) {
   
		if (!proto) return false;
		if (proto === prototype) return true;
		proto = Object.getPrototypeOf(proto);   // 一直循环
	};
}

3. 实现深浅拷贝

关于深浅拷贝,引用来自:浅拷贝与深拷贝的区别
在这里插入图片描述

(1) 深拷贝

  • JSON.stringify()

用于解决浅拷贝拷贝第一层的问题
其基本原理为:是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象

let a = {
   
	age:1,
	job:{
   
		first:'FE',
	}
};

let b = JSON.parse(JSON.stringify(a));
a.jobs.first = 'native';
console.log(b.job.first) // => 'FE'

但是这个方法存在局限性:

  1. 会忽略undefined
  2. 会忽略symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
  • 函数库lodash的_.cloneDeep方法
let _ = require('lodash');
let obj1 = {
   
	a: 1,
	b: {
   
		f: {
   
			g: 1
		},
	},
	c:[1,2,3]
};
let obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f) // => false
  • MessageChannel_针对要拷贝的对象含有内置类型且不包含函数的情况

此方法是异步的,可以处理undefined和循环引用对象

function structuralClone(obj) {
   
	return new Promise(resolve => {
   
		const {
   prot1, port2} = new MessageChannel();
		prot2.onmessage = ev => resolve(ev.data);
		prot1.postMessage(obj);
	});
};

let obj = {
   
	a:1,
	b:{
   
		c:b
	}
};


(
	async() => {
   
		const clone = await structuralClone(obj);
	}
)();
  • 手写深拷贝
	function deepCopy(object) {
   
  if (!object || typeof object !== "object") return;

  let newObject = Array.isArray(object) ? [] : {
   };

  for (let key in object) {
   
    if (object.hasOwnProperty(key)) {
   
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  return newObject;
}

(2) 浅拷贝

一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化

  • Object.assign()_根据assign分发的含义通俗理解为把sources分发到target中去
    在这里插入图片描述
let a = {
   
	age:1,
};
let b = Object.assign({
   }, a);
a.age = 2;
console.log(b.age);  // => 1
  • 扩展运算符…
let a = {
   
	age:1,
};
let b = {
   ...a};
a.age = 2;
console.log(b.age);  // => 1
  • 手写实现浅拷贝
// 浅拷贝的实现;

function shallowCopy(object) {
   
  // 只拷贝对象
  if (!object || typeof object !== "object") return;

  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {
   };

  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
   
    if (object.hasOwnProperty(key)) {
   
      newObject[key] = object[key];
    }
  }

  return newObject;
}// 浅拷贝的实现;

function shallowCopy(object) {
   
  // 只拷贝对象
  if (!object || typeof object !== "object") return;

  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {
   };

  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
   
    if (object.hasOwnProperty(key)) {
   
      newObject[key] = object[key];
    }
  }

  return newObject;
}// 浅拷贝的实现;
function shallowCopy(object) {
   
  // 只拷贝对象
  if (!object || typeof object !== "object") return;
  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {
   };
  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
   
    if (object.hasOwnProperty(key)) {
   
      newObject[key] = object[key];
    }
  }
  return newObject;
}

4. 防抖 & 节流

  • 防抖: 在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时
  • 节流:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效
  • 防抖和节流都是为了防止函数多次调用,区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况下会间隔一定的时间(wait)调用函数

(1)防抖

  • test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./index.js", defer="defer"></script>
</head>
<body>
    <h1>防抖</h1>
    <input type="text" id="input">
</body>
</html>
  • index.js
const inputDOM = document.getElementById('input');
console.log(inputDOM);

// 用户输入完毕的时候,才发送一次HTTP请求
function debounce(fn, delay) {
    
    let timer = null;
    return function(event) {
   
        console.log('e',event);
        if (timer) {
   
            clearTimeout(timer);
        };
        timer = setTimeout(() => {
   
            fn.apply(this, arguments);
        }, delay);
    }
}

inputDOM.addEventListener('input', debounce(function(event) {
   
    console.log('event', event);
    console.log('发送搜索请求');
}, 500));

在这里插入图片描述
在这里插入图片描述

(2)节流

  • test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./index.js", defer="defer"></script>
    <style>
        #box{
     
            width: 200px;
            height: 200px;
            background-color:orange;
        }
    </style>
</head>
<body>
    <h1></
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值