手写代码应该是很多像我这样的小白比较头疼的一个环节,吹牛可以,一手写真的是无从下笔,小弟翻了几页书,巩固巩固了 js 基础,写了部分代码,望笑纳。
1.实现 Object.create
// Object.create 的核心思想是创建一个空实例对象,将实例的原型链指向类的原型
Object.create = function(prototype){
if(prototype === null || typeof prototype !== "object" ){
throw new TypeError('Object prototype may only be an Object:'+ prototype);
}
// 精华在这,结合原型原型链悟一下,有不明白的我可以后期补个图
function Temp(){};
Temp.prototype = prototype;
return new Temp;
}
2.实现 new
function _new(func,...args){
// 创建一个实例(实例.__proto__ => 类.prototype)
let obj = Object.create(func.prototype);
// 执行 func ,并且将 func 中的 this 指向实例
let result = func.call(obj,...args);
// 分析 result 的返回值(没有返回值或者返回值是原始数据类型,则返回创建的实例,否者返回执行的结果)
if(result !== null && /^(object|function)$/.test(typeof result)) return result
return obj;
}
3.实现 instanceof
/**
*实现思路: 判断 实例原型链指向的原型对象和 右侧类的原型是否相等,如果相等则返回 true,如果不等,则实例
* 延着原型链继续查找,知道找到 null 为止,则返回 false
* IE 老版本不支持 left.__proto__ ,则可以使用 Object.getPrototypeOf 来获取 原型链指向的原型对象
*/
function _instanceof(left,right){
let proto = left.__proto__;
let prototype = right.prototype;
while(true){
if(proto === null) return false;
if(proto === prototype) return true;
proto = proto.__proto__
}
}
function _instanceof(left,right){
let proto = Object.getPrototypeOf(left) ;
let prototype = right.prototype;
while(true){
if(proto === null) return false;
if(proto === prototype) return true;
proto = Object.getPrototypeOf(proto)
}
}
4.实现 call,apply bind
/**
** call,apply.bind 本质都是用来改变this 的指向
** call apply 在于传参形式的不同,bind 则是返回个函数,需要调用才会执行
**/
Function.prototype._call = function(context,...args){
context = context == null ? window : context;
// 必须保证 context 是一个对象
if(!/^(object|function)$/i.test(typeof context)){
// 如果传入的是数字呢,通过 new 基本类型的构造函数,但是 Symbol,Bigint 又不支持 new ,
//所以 Object (基本数据类型解决一切烦恼)
context = Object(context)
}
// 防止创建的方法与 Obj 的原始对象结构值重复
let key = Symbol('Key')
// 把函数作为对象的某个成员值, 这样函数中的 this 就是对象
context[key] = this;
let result = context[key](...args);
// 方法执行完就可以删除了,我们仅仅是需要个执行的结果而已
delete context[key];
return result;
}
Function.prototype._apply = function(context,args){
context = context == null ? window : context;
if(!/^(object|function)$/i.test(typeof context)){
context = Object(context)
}
let key = Symbol('Key')
context[key] = this;
let result = context[key](...args);
delete context[key];
return result;
}
Function.prototype._bind = function(context,...args){
let _this = this;
return function(...params){
_this.call(context,...args.concat(params))
}
}
5.继承
- 原型继承: 父类私有和公有的属性和方法,都会成为子类的公有属性和方法(多个实例对引用类型的操作会被篡改)
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function(){
return `父元素原型上的方法,并返回 x = ${this.x}`
}
function Child(){
this.y = 200;
}
Child.prototype = new Parent;// 原型继承
Child.prototype.getY = function(){
return `子元素原型上的方法,并返回 y = ${this.y}`
}
- call 继承: 相当于强制把父类的私有属性变成了子类的私有属性,但是无法继承父类原型上的属性和方法且无法实现复用,每个子类都有父类实例函数的副本,影响性能
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function(){
return `父元素原型上的方法,并返回 x = ${this.x}`
}
function Child(){
Parent.call(this)
this.y = 200;
}
Child.prototype.getY = function(){
return `子元素原型上的方法,并返回 y = ${this.y}`
}
- call 继承: Parent.call(this) 第一次执行,给子类的私有属性强加了 x 这个属性;new Parent() 第二次执行,在子类的原型上再次加上 x 这个属性,所以会存在重复的属性
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function(){
return `父元素原型上的方法,并返回 x = ${this.x}`
}
function Child(){
Parent.call(this)
this.y = 200;
}
Child.prototype = new Parent()// 原型继承
Child.prototype.getY = function(){
return `子元素原型上的方法,并返回 y = ${this.y}`
}
4.寄生组合继承(近乎完美的继承)
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function(){
return `父元素原型上的方法,并返回 x = ${this.x}`
}
function Child(){
Parent.call(this)
this.y = 200;
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.getY = function(){
return `子元素原型上的方法,并返回 y = ${this.y}`
}
6.实现 防抖和节流
/**
*防抖,规定的时间内重复的操作只执行一次
*immediate:false:默认执行最后一次操作, true: 执行第一次操作
*
**/
function debounce(func, wait = 500, immediate = false) {
let timer = null;
return function anonymous(...params) {
let now = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
// 执行函数:注意保持THIS和参数的完整度
!immediate ? func.call(this, ...params) : null;
}, wait);
now ? func.call(this, ...params) : null;
};
}
/**
*节流,减少触发的频率
**/
function throttle(func, wait = 500){
let timer = null,
previous = 0; // 记录上一次执行的时间
return function(...params){
let now = new Date(),
remianing = wait -(now-previous);
if(remianing <= 0){
clearTimeout(timer);
timer = null;
previous = now;
func.call(this,...params)
}else if(!timer){
timer = setTimeout(()=>{
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this,...params)
},remaining)
}
}
}
7.深拷贝
- 丐版深拷贝
//测试案例
let cObj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
o: {
name: '我是一个对象',
id: 1
},
arr: [1,2,3],
func: function() {
console.log('我是一个函数')
},
date: new Date(0),
reg: /^\d$/g,
err: new Error('我是一个错误'),
map:new Map(),
set:new Set()
}
// 简单深拷贝
function deepClone(cObj){
// 处理边界问题
if(cObj === null) return null;
if(typeof cObj !== "object") return cObj;
const constructor = cObj.constructor;
if(/^(RegExp|Date|Map|Set)$/i.test(constructor.name)) return new constructor(cObj);
// 处理数组或者是 object 对象
let clone = new constructor();
for(let key in cObj){
if(!cObj.hasOwnProperty(key)) continue;
clone[key] = deepClone(cObj[key])
}
return clone;
}
- 解决循环引用,栈溢出问题(WeakMap)
cObj.cObj = cObj;
function deepClone(cObj,map = new WeakMap()){
// 处理边界问题
if(cObj === null) return null;
if(typeof cObj !== "object") return cObj;
const constructor = cObj.constructor;
if(/^(RegExp|Date|Map|Set|Error)$/i.test(constructor.name)) return new constructor(cObj);
// 处理数组或者是 object 对象
if(map.get(cObj)) return map.get(cObj)
let clone = new constructor();
map.set(cObj,clone)
for(let key in cObj){
if(!cObj.hasOwnProperty(key)) continue;
clone[key] = deepClone(cObj[key],map)
}
return clone;
}
- 栈结构来处理深递归带来的栈溢出问题
// 造数据
function createData(deep, breadth) {
var data = {};
var temp = data;
for (var i = 0; i < deep; i++) {
temp = temp['data'] = {};
for (var j = 0; j < breadth; j++) {
temp[j] = j;
}
}
return data;
}
cObj.cData = createData(1000, 1000);
function cloneForce(x) {
const uniqueList = []; // 用来去重
let root = {};
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
// 循环数组
while(loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
// 数据已经存在
let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
break; // 中断本次循环
}
// 数据不存在
// 保存源数据,在拷贝数据中对应的引用
uniqueList.push({
source: data,
target: res,
});
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object' && data[k] !== null ) {
if(Object.prototype.toString.call(data[k]) === '[object Array]'){
if(!data[k].filter(item => typeof item === 'object').length){
res[k] = data[k]
}else{
// 处理 JSONArray
}
}else{
// 处理边界问题,拿到值的构造函数
let constructor = data[k].constructor;
if(/(Set|Map|Error|Date|RegExp)/.test(constructor.name)){
res[k] = new data[k].constructor(data[k])
}else{
//下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
}
}
} else {
res[k] = data[k]
}
}
}
}
return root;
}
function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}
let cloneData = cloneForce(cObj);
console.log(cloneData)
有错误的地方望指出,还有需要手写的欢迎留言提示我,争取做到手写代码不再慌张。