六、js手写函数第六批


26、图片懒加载

<!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>
  <style>
    img {
      width: 200px;
      height: 200px;
      display: block;
      margin-bottom: 20px;
    }
  </style>
</head>

<body>
  <img id="1" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="2" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="3" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="4" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="5" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="6" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="7" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="8" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="9" src="../img/default.jpg" data-src="../img/1.jpg" />
  <img id="10" src="../img/default.jpg" data-src="../img/1.jpg" />

  <script>
    let imgList = [...document.querySelectorAll('img')];

    function imglazyLoad() {
      imgList.forEach((item, index) => {
        if (item.dataset.src === '') return;
        // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置,比如rect.top是图片上边距离电脑浏览器当前视口的距离
        // 我们一般是不直接给图片写src的因为这样会直接渲染
        // 而是一般通过dataset设置自定义的属性src,把图片地址先存在这里,懒加载的时候,把值赋值过去
        let rect = item.getBoundingClientRect();
        debugger
        // window.innerHeight指当前浏览器视口的高度,当图片距离可是窗口顶部的距离小于可是窗口高度,说明图片已经从下面滚出来了
        if (rect.top < window.innerHeight) {
          item.src = item.dataset.src;
        }
      })
    }

    // 图片懒加载一般搭配节流处理
    function throttle(fn, delay) {
      let timer;
      let prevTime;
      return function (...args) {
        const currTime = Date.now();
        const context = this;
        if (!prevTime) prevTime = currTime;
        clearTimeout(timer);

        if (currTime - prevTime > delay) {
          prevTime = currTime;
          fn.apply(context, args);
          clearTimeout(timer);
          return
        }

        timer = setTimeout(function () {
          prevTime = Date.now();
          timer = null;
          fn.apply(context, args);
        }, delay)
      }
    }

    // 监听时搭配节流处理
    document.addEventListener('scroll', throttle(imglazyLoad, 200));
  </script>
</body>

</html>

27、事件总线

<script>
  class EventEmitter {
    constructor() {
      this._event = {}
    }
    on(name, fn) {
      if (this._event[name]) {
        this._event[name].push(fn);
      } else {
        this._event[name] = [fn];
      }
    }

    off(name, fn) {
      let tasks = this._event[name];
      if (tasks) {
        const index = tasks.findIndex(f => f === fn);
        if (index >= 0) {
          tasks.splice(index, 1);
        }
      }
    }

    emit(name, once = false, ...args) {
      if (this._event[name]) {
        // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
        let tasks = this._event[name].slice();
        for (let fn of tasks) {
          fn(...args);
        }
        if (once) {
          delete this._event[name];
        }
      }
    }
  }

  // 测试
  let eventBus = new EventEmitter();
  let fn1 = function (name, age) {
    console.log(`${name} ${age}`);
  }
  let fn2 = function (name, age) {
    console.log(`hello, ${name} ${age}`);
  }
  debugger
  eventBus.on('aaa', fn1);
  eventBus.on('aaa', fn2);
  eventBus.emit('aaa', false, '布兰', 12);
  // '布兰 12'
  // 'hello, 布兰 12'
</script>

28、数组forEach

<script>
  // 第一个参数是函数,对应forEach的第一个参数一般都是箭头函数
  // 当你用原型方法是,这里的thisArg就是要调用forEach的数组,如果你是直接用数组调用的forEach,那么thisArg可以不传
  // 因为它本身就是代表他自己
  Array.prototype.myForEach = function (callback, thisArg) {
    debugger
    if (this == null) {
      throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== "function") {
      throw new TypeError(callback + ' is not a function');
    }
    // 这里可能是不能直接用this,length吧,所以先浅拷贝一下
    // 这里谁调用的函数谁就是this,所以这里this值得是arr
    const O = Object(this); // this 就是当前的数组
    const len = O.length >>> 0; // 后面有解释
    let k = 0;
    while (k < len) {
      // 这种写法可以判断k是否在O数组的索引范围内, k in O就是指判断k这个索引在不在数组的下标范围内;
      // 对象也可以这么判断,判断一个对象是否有某个属性
      /* let obj = {name: 'wujunjie'};
      if('name' in obj) {
        console.log('obj包含属性name');
      } */
      if (k in O) {
        callback.call(thisArg, O[k], k, O);
      }
      k++;
    }
  }

  debugger
  const arr = ['name','age','gender'];
  const fruits = ['apple', 'banane', 'butttler'];
  arr.myForEach((item ,index) => {
    item = item + index;
  })

  // 需要注意的是,这里的第二个参数fruits,其实就是js中的forEach函数的第二个参数,他是用来改变回调函数this指向的,但是目前
  // 还没有发现有什么用,这里们虽然穿了参数fruits,但是实际上在回调函数里没有用到this,所以这里用不上。所以一般不传thisArg
  // 这个参数,也就是undefined,但是实际上并没有什么影响。
</script>

29、Object.create实现

<script>
  // 根据oldObj创建newObj然后使newObj的__prto__属性指向oldObj
  // 首先Object的原型对象是null所以直接写
  // propertyObject是指你想在newObj里添加什么属性,不添加那么newObj本身就是空对象
  Object.create2 = function (proto, propertyObject) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
      throw new TypeError('Object prototype may only be an Object or null.');
    }
    if (propertyObject === null) {
      throw new TypeError('Cannot convert undefined or null to object');
    }
    function F() { };
    F.prototype = proto;
    const obj = new F();
    if (propertyObject !== undefined) {
      // 下面这个方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
      Object.defineProperties(obj, propertyObject);
    }

    // 这里为什么要多余写这个,看下chatGPT的回答
    //     你的疑问是非常合理的。首先,让我们正确理解 F.prototype 和实例的 __proto__(或称为内部原型)之间的关系。在JavaScript中,当你创建一个新对象时,比如通过 new F(),这个新对象的内部原型 (__proto__) 会被自动设置为函数的 prototype 属性。如果 F.prototype 是 null,根据 JavaScript 的规范,这种设置应该是可行的,应该使得新对象的 __proto__ 也是 null。

    // 然而,实际在一些JavaScript环境中,如果你尝试将构造函数的 prototype 属性设置为 null,然后通过这个构造函数创建一个对象,这个对象的 __proto__ 并不会是 null,而是会回退到 Object.prototype。这是因为大多数JavaScript环境实现了保护机制,预防 __proto__ 被设置为 null 导致后续操作出现问题(例如,一些基础的方法和属性无法继承)。
    if (proto === null) {
      // 创建一个没有原型对象的对象,Object.create(null)
      obj.__proto__ = null;
    }
    return obj;
  }
  </script>

// 现在流行这么写
Object.create2 = function (proto, propertyObject) {
  if (typeof proto !== 'object' && typeof proto !== 'function') {
    throw new TypeError('Object prototype may only be an Object or null.');
  }
  if (propertyObject === null) {
    throw new TypeError('Cannot convert undefined or null to object');
  }
  const obj = {};
  // Object.setPrototypeOf(obj, proto);的效果就是obj.__proto__ = proto
  Object.setPrototypeOf(obj, proto);
  if(propertyObject !== undefined) {
    Object.defineProperty(obj, propertyObject);
  }
  return obj;
}

/* Object.defineProperty 和 Object.defineProperties 都可以用于在一个对象上定义新的属性或修改原有的属性及其特征。它们的主要区别在于它们的应用范围和参数的不同。

方法应用的对象数量:

Object.defineProperty 是用来定义或修改单个属性的。
Object.defineProperties 是用来定义或修改多个属性的。
参数:

Object.defineProperty(obj, prop, descriptor) 接受三个参数:
obj: 要在其上定义属性的对象。
prop: 要定义或修改的属性的名称。
descriptor: 属性的描述符。
Object.defineProperties(obj, props) 接受两个参数:
obj: 要在其上定义属性的对象。
props: 一个对象,其键是要定义的属性的名称,值是相应的属性描述符。
使用示例:

使用 Object.defineProperty:
const object1 = {};
Object.defineProperty(object1, 'property1', {
  value: 42,
  writable: false
});
console.log(object1.property1); // 输出: 42
使用 Object.defineProperties:
const object2 = {};
Object.defineProperties(object2, {
  'property1': {
    value: 42,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
});
console.log(object2.property1, object2.property2); // 输出: 42 "Hello"
总结:

如果你需要在一个对象上同时定义多个属性,使用 Object.defineProperties 更为高效。
对 */

30、Object.assgin实现

<script>
  // 首先需要注意的是,Object.assign是一种浅拷贝,引用类型只会拷贝地址
  Object.assign2 = function (target, ...source) {
    if (target == null) {
      throw new TypeError('Cannot convert undefined or null to object')
    }
    // 一直不知道这个Object(obj)的意义在那里,因为改变ret也会改变target,他们的地址是一样的,写这一句话干嘛呢?
    // 这个疑问留在这里,我再这个库里的第一篇里已经做了解释,还是挺有意思的
    // 其实有了这个就不需要上面的类型判断了,因为Object(this)会把null和undefined转为空对象
    let ret = Object(target);
    source.forEach(function (obj) {
      if (obj != null) {
        // 有意思的是,for in 是遍历所有属性包括原型链上的属性
        // 所以我们需要用hasProperty过滤原型链上的属性
        for (let key in obj) {
          if (obj.hasOwnProperty(key)) {
            ret[key] = obj[key]
          }
        }
      }
    })
    return ret
  }

  const man = { name: 'jjwu' }
  Object.assign2(man, {});
</script>

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