四、js手写函数第四批


16、寄生式组合继承

<script>
  function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
  }
  Animal.prototype.getName = function () {
    return this.name
  }
  function Dog(name, age) {
    Animal.call(this, name)
    this.age = age
  }
  
  Dog.prototype = Object.create(Animal.prototype)
  Dog.prototype.constructor = Dog

  let dog1 = new Dog('奶昔', 2)
  debugger
  dog1.colors.push('brown')
  let dog2 = new Dog('哈赤', 1)
  debugger
  console.log(dog2)
</script>

17、手写new

<script>
  // 首先需要理解的是,构造函数和类是不一样的,我们的构造函数在new的时候,内部的代码会全部执行的
  // 而类不是的,类只会执行内部的构造器(constructor)的代码
  // 一、先来看一个例子,理解一下new
  function Foo(name, age) {
    this.name = name;
    this.age = age;
    // 以下代码用于测试构造函数是否执行了内部的所有代码
    console.log('我执行!!');
    say = function () {
      console.log('我也执行了!!!');
    }
    say();
  }
  Foo.prototype.sayhi = function () {
    console.log(this.name);
  }
  // 直接调用构造函数
  const foo = new Foo('哈哈', 123); // 使用new操作符
  console.log(foo); //Foo {name: '哈哈', age: 123, __proto__:{sayhi: ƒ ()}}
  foo.sayhi(); //  '哈哈'
  console.log(foo.name); // '哈哈'

  // 二、new的过程发生了什么
  // 1、在内存中创建一个新对象
  // 2、这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性
  // 3、构造函数内部的this被赋值为这个新对象(即this指向新对象)
  // 4、执行构造函数内部的代码(给新对象添加属性)
  // 5、如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

  // 三、那我们怎么理解第五点呢,如下例子
  function Foo1(name) {
    this.name = name;
    return {
      height: 180
    }
  }
  const foo1 = new Foo('哈哈');
  // 在这里,构造函数中用了return,如果构造函数返回非空对象,则返回该对象,所以这里返回了{height:180}
  console.log(foo); // {height: 180}

  // 四、好的,说了这么多接下来我们来看下怎么重写new
  function myNew(fn, ...args) {
    // 1 获取除fn以外的所有arguments 
    // 使用slice删除arguments第一个元素就得到其他arguments
    const args = Array.prototype.slice.call(arguments, 1); // ['哈哈',123]
    // 新建一个对象 用于函数改变对象
    const newObj = {};
    // 原型链被赋值为原型对象
    newObj.__proto__ = fn.prototype;
    // this 指向新对象 
    fn.apply(newObj, args);
    // 返回这个新对象
    return newObj;
  }

  // 五、我们把第五点兼容进来,然后精简一下代码(这个太强了)
  function _new(fn, ...args) {
    // 这里可以去看下Object.create的用法,效果和new fn是一样的
    // 注意这里的newObj是一个空对象
    const newObj = Object.create(fn.prototype);
    // 这里再改变this后给对象装属性和值
    const value = fn.apply(newObj, args);
    // 如果函数返回非空并是对象返回 value 否则 返回 newObj
    return value instanceof Object ? value : newObj;
  }

  // 解释一下Object.create的作用
  // 比如obj = Object.create(fn)
  // 最终实现的效果是obj.__proto__ = fn

  // 顺便实现一下Object.create()
  // 很简单:
  function myCreate(fn) {
    // 返回的是一个空对象哦!!!!
    function F() { }
    F.prototype = fn
    return new F()
  }
</script>

18、数组扁平化

  <script>
    // [1, [2, [3]]].flat(2)  >>>>>>  输出的是[1, 2, 3]
    // 1、先看下ES5的递归实现
    function myFlat1(arr) {
      var result = [];
      for (var i = 0, len = arr.length; i < len; i++) {
        if (Array.isArray(arr[i])) {
          result = result.concat(flatten(arr[i]))
        } else {
          result.push(arr[i])
        }
      }
      return result;
    }

    // 2、 再看看ES6的实现
    function myFlat2(arr) {
      while (arr.some(item => Array.isArray(item))) {
        console.log(arr)
        arr = [].concat(...arr);
      }
      return arr;
    }
    const arr = [1, [2, [3]]];
    myFlat2(arr)


    // 要明白concat的作用:
    // const newArr = [1, 2].myConcat([1, 2], [4, 5, 9],7,5,6)
    // 输出结果[ 1, 2, 1, 2, 4, 5, 9, 7, 5, 6 ]

/* 在 JavaScript 中, arr = [].concat(...arr); 这一语句实现数组的扁平化处理,涉及到两个关键部分:concat 方法和扩展运算符 ... (或称为三点运算符)。

扩展运算符 ...:
扩展运算符用于将数组或类数组对象展开成一系列单独的元素。例如,如果 arr = [1, [2, 3], 4],使用 ...arr 会展开 arr 的顶层元素,变成 1, [2, 3], 4。

concat 方法:
concat 方法用于合并多个数组,或者将值添加到数组中。它不修改原始数组,而是返回一个新数组,这个新数组包含原始数组合并后的元素。在你的示例中,使用 [].concat(...arr) 是通过在一个新的空数组上调用 concat 方法,并传入展开后的元素,效果上是向这个新数组中添加了 arr 中的所有顶层元素。

结合上面所讲的,arr = [].concat(...arr) 中:

扩展运算符 起到的作用是展开数组的最顶层元素。
concat 方法 起到的作用是将展开后的元素合并到一个新的数组中。
如果 arr 的元素中存在嵌套数组(仅一层嵌套,不是多层),这行代码将只能扁平化一层,因为 concat 只合并顶层展开的元素。如果需要完全扁平化多层嵌套的数组,则需要使用递归方法或者 Array.prototype.flat()。 */
  </script>

19、浅拷贝

<script>
  function myShallowCopy(obj) {
    if (typeof obj !== 'object') return;
    // 这里用instanceof是因为type对于obj合array都是返回object
    let newObj = obj instanceof Array ? [] : {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        newObj[key] = obj[key]
      }
    }
    return newObj
  }

  const obj = {
    name: 'wujunjie',
    age: 18,
    say: {
      name: 'junjie'
    },
    arr: [1, 2]
  }
  const tempObj = myShallowCopy(obj)
  tempObj.say.newAge = 19;
  tempObj.age = 20;
  tempObj.name = 'xiaorong';
  tempObj.arr.push(3);
  console.log(obj)

// 自己测试可以知道,这种拷贝后简单类型(undefined、null、string、number、boolean)会重新给地址,
//但是对象类型(object),拷贝的是引用地址,newObject中改了say的内容,obj中也会改掉
</script>

20、深拷贝

<script>
  // 这里有意思的是typeof null的结果是object
  const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

  // 注意这个map参数的用法,这里的意思就是如果你在调用这个函数的时候,你自己传了map参数那就用你传的,如果你没传,那就用new WeakMap()
  function myDeepClone(target, map = new WeakMap()) {
    // 消除循环引用,用于后续递归
    if (map.get(target)) {
      return target;
    }
    // 获取当前值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测当前对象target是否与正则、日期格式对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {
      // 创建一个新的特殊对象(正则类/日期类)的实例
      // 注意这种写法挺有意思,利用构造器创建新的对象,并且内容一样
      return new constructor(target);
    }
    if (isObject(target)) {
      map.set(target, true);  // 为循环引用的对象做标记
      const cloneTarget = Array.isArray(target) ? [] : {};
      for (let prop in target) {
        // 数组也有hahasOwnProperty属性
        if (target.hasOwnProperty(prop)) {
          cloneTarget[prop] = myDeepClone(target[prop], map);
        }
      }
      return cloneTarget;
    } else {
      return target;
    }
  }

  const obj = {
    name: 'wujunjie',
    like: {
      name: 'yuanxiaorong'
    },
    date: new Date()
  }

  const newObj = myDeepClone(obj);

  newObj.like.age = 18;

  // 注意conconstructor的用法:
  const arr1 = []; console.log(arr1.constructor.name)   // Array
  const arr2 = {}; console.log(arr2.constructor.name)   // Object
  const arr3 = function () { }; console.log(arr3.constructor.name)   // Function
  const arr4 = new Date(); console.log(arr4.constructor.name)   // Date
  const arr5 = new RegExp(); console.log(arr5.constructor.name)   // RegExp
  const arr6 = function () { }; const newArr = new arr6(); console.log(newArr.constructor.name)   // arr6
  const arr7 = 7; console.log(arr7.constructor.name)   // Number
  const arr8= 'nice'; console.log(arr8.constructor.name)   // String
</script>

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录