写点 js 基础代码,我感觉我又行了。

179 阅读2分钟

手写代码应该是很多像我这样的小白比较头疼的一个环节,吹牛可以,一手写真的是无从下笔,小弟翻了几页书,巩固巩固了 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.继承

  1. 原型继承: 父类私有和公有的属性和方法,都会成为子类的公有属性和方法(多个实例对引用类型的操作会被篡改)
  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}`
  }
  
  1. 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}`
    }
  1. 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.深拷贝

  1. 丐版深拷贝
	//测试案例
    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;
}
   
  1. 解决循环引用,栈溢出问题(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;
    }
  1. 栈结构来处理深递归带来的栈溢出问题
// 造数据
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)

有错误的地方望指出,还有需要手写的欢迎留言提示我,争取做到手写代码不再慌张。