反复刷这些javascript手写题,我又变强了(二)

下面的promise有我敲写分析的过程视频

数组原型方法的实现

其实下面的这几个方法的写法差不多,参数也只有一些细微差别,但是要考虑到,如果是要返回一个新的数组的方法比如map,reduce都要用到深拷贝

forEach

这里主要需要注意的就是第一个参数是回调函数
,然后这个回调 函数也会有三个参数,分别是元素值,元素索引,遍历数组本身,第二个参数就是数组遍历时this的指向地址,下面的filter,map都是这样的参数类型

Array.prototype._forEach = function (cb, ...ret) {
  if (typeof cb !== 'function') {
    console.log('first param must be function');
  }

  if (!Array.isArray(this)) {
    throw new Error('is not a  array');
  }
  // console.log(cb);
  // console.log(ret[0]);
  let arr = this;
  let len = arr.length;
  let arg = ret[0] || window;
//fo循环每次都可以调用这个回调函数的一些逻辑处理
  for (let i = 0; i < len; i++) {
    cb.call(arg, arr[i], i, arr);
  }
};

const arr = [1, 2, 3];
const obj = { a: 1 };
// arr.forEach(function (item, index, array) {
//   console.log(this); //可以传第二个参数,如果第二个参数是obj,那么this就是
//   // 指向的是obj
//   console.log(item, index, array);
// }, obj);

arr._forEach(function (item, index, arr) {
  console.log(item, index, arr);
}, obj);

在这里插入图片描述

map

这里我觉得唯独需要注意的就是,返回一个新的数组,需要用到深拷贝

深拷贝与浅拷贝的理解和实现

Array.prototype._map = function (cb, ret) {
  let arr = this;

  let res = [];
  if (!Array.isArray(this)) {
    throw new Error('is not a  array');
  }

  for (let i = 0; i < arr.length; i++) {
    // 如果数据里面有对象等引用类型,那么就需要执行深拷贝

    //res.push(cb.call(ret, deepClone(arr[i]), i, arr));
    res.push(cb.call(ret, arr[i], i, arr));
  }
  return res;
};

const arr = [1, 2, 3];
const obj = { a: 1 };

let newArr = arr._map(function (item, index, arr) {
  console.log(item, index, arr);
  return item * 2;
}, obj);
console.log(newArr);

console.log(arr);

在这里插入图片描述

filter

逻辑上的处理差不多和上面的

Array.prototype._filter = function (cb) {
  let arr = this;
  let arg = arguments[1];
  let res = [];

  if (!Array.isArray(arr)) {
    throw new Error('is not a array');
  }

  for (let i = 0; i < arr.length; i++) {
    let flag = cb.call(arg, arr[i], i, arr);
    if (flag) {
      res.push(arr[i]);
    }
  }

  return res;
};

const arr = [1, 2, 3];
let newArr = arr._filter(function (item) {
  return item > 1;
});
console.log(newArr);

在这里插入图片描述

every

会写这个了肯定会写some方法了

Array.prototype._every = function (cb) {
  let arr = this;
  let arg = arguments[1] || window;
  for (let i = 0; i < arr.length; i++) {
    let flag = cb.call(arg, arr[i], i, arr);
    if (!flag) {
      return false;
    }
  }

  return true;
};

const arr = [1, 2, 3];

let res = arr._every(function (item, index, array) {
  return item > 0;
});

console.log(res);


在这里插入图片描述

reduce

这个也是需要深拷贝的

Array.prototype._reduce = function (cb, initialValue) {
  let arr = this;
  let item;

  for (let i = 0; i < arr.length; i++) {
    // item = deepClone(arr[i]); 这里是需要深拷贝的
    initialValue = cb.call(arr, initialValue, arr[i], i, arr);
  }
  return initialValue;
};

const arr = [1, 3, 3];

let newArr = arr._reduce(function (pres, cur) {
  return pres + cur;
}, 0);

console.log(newArr);

在这里插入图片描述

对象原型方法的实现

object.assign

对于Object.assign需要注意的一点就是,合并对象的属性时,后面合并的有相同属性会覆盖掉前面合并的相同属性,而且切记,它只能合并可枚举的属性,也就是enumable为true的属性,思路很简单,由于object.assgin是浅拷贝只用简单的复制语句就能复制指针,于是第一层遍历合并的一些对象,然后第二层遍历对象的各个属性然后进行合并

// 浅拷贝
Object._assign = function (target, ...source) {
  if (target == null) {
    throw new TypeError('Cannot convert undefined or null to object');
  }
  // console.log(source);

  let ret = Object(target);
  source.forEach((obj) => {
    if (obj != null) {
      for (let key in obj) {
        console.log(key);
        // 可枚举的属性才会拷贝
        if (obj.hasOwnProperty(key)) {
          ret[key] = obj[key];
        }
      }
    }
  });
};

const a = {
  x: 1,
  y: {
    z: 1,
  },
};

const b = {
  x: 2,
  name: 'af',
  hobbies: {
    sports: 'basketball',
    music: {
      style: ['fuck', 'blues'],
      instruments: ['guitar'],
    },
  },
};

// Object.assign(a, b);
Object._assign(a, b);
console.log(a);


附加一到关于Object.assign的题

// 面试题
const v1 = 123;
const v2 = '123';
const v3 = true;
const v4 = function test() {};

const v5 = Object.assign({}, v1, v2, v3, v4);
console.log(v5);
// 打印结果 :{0: "1", 1: "2", 2: "3"}
// 结果很巧妙:这是因为Object.assign会自动将不是对象的方法转化为对象
// 然后只会合并可枚举的属性,而上面拥有可枚举的属性的对象就只有String对象

const r1 = new Number(v1);
const r2 = new String(v2);
const r3 = new Boolean(v3);
const r4 = new Function(v4);
console.log(r1);
console.log(r2);
console.log(r3);
console.log(r4);

for (let key in r1) {
  console.log(key);
}

for (let key in r2) {
  console.log(key); //唯独只有这里可以打印
}

for (let key in r3) {
  console.log(key);
}

for (let key in r4) {
  console.log(key);
}

object.create

用这个方法能够非常简单的把组合继承变为寄生组合继承,由于它是利用了父构造函数的原型对象,创建一个新的原型对象来使子构造函数的__proto__ 来指向实现继承,相对于组合继承少了一次访问构造函数的有点

第二个参数是设置对象自己的属性,而且必须是一个对象descripter描述器,所以直接可以用Object.definepropert实现

// 第二个参数是设置对象自己的属性,而且必须是一个对象descripter描述器
Object._create = function (proto, propertyObject = undefined) {
  if (typeof proto !== 'object' && typeof proto !== 'function') {
    throw new TypeError(
      'Object prototype may only be Object or null to Object',
    );
  }
  if (propertyObject == null) {
    new TypeError('cannot convert undefined or null to Object');
  }

  // 关键就这三行
  function F() {}
  F.prototype = proto;
  const obj = new F();

  if (propertyObject !== undefined) {
    Object.defineProperty(obj, propertyObject);
  }

  if (proto === null) {
    // 创建一个没有原型的对象 Object.create(null)
    obj._proto_ = null;
  }

  return obj;
};

手写promise

這個相比不用多说有多重要了,天天用到的,所以在学习实现手写之前必须掌握好的它的一些特点和细节。
视频讲解地址
由于细节有点多,先写个只完成基本功能的,还有一些promise方法,没写我准备会在b站上发布一个视频来解析我的代码并贴在这里,如果一直没发布希望有人评论提醒我

下面代码有详细注释

class Af {
  // 先定义三种状态
  static PENGDING = 'pengding';
  static FULLFILLED = 'fullfilled';
  static REJECTED = 'rejected';

  constructor(executor) {
    this.status = Af.PENGDING;
    this.value = '';

    // 把状态改变后的函数放到下面数组中执行
    // 等状态改变之后再拿出来执行

    this.callbacks = [];
    // 因为resolve和reject方法是在executor中调用,作用域也是executor的作用域
    // 这样会造成this指向window,现在我么使用的是class定义,this会是undefined
    // 所以下面绑定了this
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // 当执行者出现异常时触发拒绝状态
      this.reject(error);
    }
  }

  resolve(value) {
    // 状态只能改变一次所以在resolve和reject都需要添加状态条件判断
    // 而且之能是 pending->fullfilled  pending->rejected
    if (this.status == Af.PENGDING) {
      this.status = Af.FULLFILLED;
      this.value = value;

      // 添加处理callback方法的代码 异步处理s
      setTimeout(() => {
        this.callbacks.forEach((callback) => callback.onFullfilled(value));
      });
    }
  }

  reject(value) {
    // 状态只能改变一次所以在resolve和reject都需要添加状态条件判断
    // 而且之能是 pending->fullfilled  pending->rejected
    if (this.status == Af.PENGDING) {
      this.status = Af.REJECTED;
      this.value = value;

      // 添加处理callback方法的代码 异步处理
      setTimeout(() => {
        this.callbacks.forEach((callback) => callback.onRejected(value));
      });
    }
  }

  // onFullfilled,onRjected即成功和错误时的回调函数
  then(onFullfilled, onRejected) {
    // then可以有两个参数,即成功和错误时的回调函数
    // then的函数参数都不是必须的,所以需要设置默认为函数
    // 用于处理当没有传递时的情况
    if (typeof onFullfilled !== 'function') {
      onFullfilled = (value) => value;
    }

    if (typeof onRejected !== 'function') {
      onRejected = (value) => value;
    }

    // 返回一个新的promise对象 ,使.then能够链式调用
    return new Af((resolve, reject) => {
      // .then处理pengding状态(如果rejected被异步回调函数包裹,比如用setTimeOut包裹的
      //   时候,状态是异步被改变的,那么没有下面Pending状态的处理,将不会执行.then方法,因为
      //   定时函数之前一直没有执行resolve方法)
      if (this.status == Af.PENGDING) {
        // 将then方法的回调函数添加到 callbacks 数组中,用于异步执行
        this.callbacks.push({
          onFullfilled: (value) => {
            try {
              let res = onFullfilled(value);
              resolve(res);
            } catch (error) {
              reject(error);
            }
          },
          onRejected: (value) => {
            try {
              let res = onRejected(value);
              reject(res);
            } catch (error) {
              reject(error);
            }
          },
        });
      }

      // .then处理fullfilled状态
      if (this.status == Af.FULLFILLED) {
        // setTimeOut的作用是使.then回调函数其异步执行
        setTimeout(() => {
          try {
            let res = onFullfilled(this.value);
            resolve(res);
          } catch (error) {
            reject(error);
          }
        });
      }

      // .then处理rejected状态
      if (this.status == Af.REJECTED) {
        setTimeout(() => {
          try {
            let res = onRejected(this.value);
            reject(res);
          } catch (error) {
            reject(error);
          }
        });
      }
    });
  }
}

// debugger;
let p = new Af((resolve, reject) => {
  // console.log(a);
  setTimeout(() => {
    resolve('阿锋');
    // console.log(a);
  });
})
  .then((value) => {
    console.log(value);
    return 1;
  })
  .then((value) => {
    console.log(value);
  });

console.log('同步代码');

// console.log(p);

实现vue数据的双向绑定

我这里是用proxy实现的,其实vue2是用Object.defineproperty实现的。但是Proxy语法更简单,下面代码不仅仅实现,而且用到了class更具有面向对象的思想,可复用性高

看不懂可以下面评论一起讨论,有机会我也想出个视频解说一些,教学相长

<!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>
</head>
<body>
  <div id="app">
    <div>
      <input type="text" id="username" v-model="username">
      <input type="text" id="sex" v-model="sex">
      <input type="text" id="age" v-model="age"> 
    </div>
    <div>
      <p>姓名:<span>{{username}}</span></p>
      <p>性别:<span>{{sex}}</span></p>
      <p>年龄:<span>{{age}}</span></p>
    </div>
  </div>
  <script>

    class MVVM{
      constructor(el,data){
        this.el = document.querySelector(el)
        this.data = data
        this.domPool = {}


        this.init()
      }


      init(){
        this.initDom(this.el)
        this.watchData()
        this.bindInput(this.el)
      }

      initDom(el){
        const childNodes = el.childNodes
        // console.log(childNodes);
        childNodes.forEach(item=>{
          // console.log(item);
          if(item.nodeType === 3){
            const text = item.nodeValue
            // console.log(text);
            let reg = /\{\{(.+?)\}\}/
                      // /\{\{(.+?)\}\}/
            if(text.trim().length){
              let isValid = reg.test(text)
              if(isValid){
                let key = text.match(reg)[1].trim()
                // console.log(key);
                this.domPool[key] = item.parentNode
                // 使得dom节点能够显示传入进来的this.data数据
                item.parentNode.innerHTML = this.data[key] || undefined
              }
            }
            
          }
          item.childNodes&&this.initDom(item)
        })
    }

    watchData(){
      let _this = this
      this.data = new Proxy(this.data,{
        get(target,key){
          console.log(`获取数据: ${key}`);
          return Reflect.get(target,key)
        },
        set(target,key,value){
          console.log(`设置数据: ${key}`);
          // 设置数据时  顺便改变当前监听节点的text值
          _this.domPool[key].innerHTML = value
          return Reflect.set(target,key,value)
        }
      })
    }

    bindInput(el){
     let allInputs = document.querySelectorAll('input')
    //  console.log(allInputs);
     allInputs.forEach(input=>{
      let key = input.getAttribute('v-model')
      
      input.addEventListener('keyup',()=>{
        // 设置对应的key为input的值 并监听keyup事件  动态绑定成功
        this.data[key] = input.value 
      },false)

     })
    }
  }

    let vm = new MVVM('#app',{
      username:'阿锋',
      sex:'man',
      age:'22'
    })
  </script>
</body>

</html>

到这里 这次的总结就算完了 如有错误 恳请指针

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值