js高级用法----手写js原生方法

1、call 方法

/**
 * _call
 *
 * @param { context } context
 * @param { arguments } arguments
 */
Function.prototype._call = function(context) {
  // 如果没有传或传的值为空对象 context指向window
  context = context || window
  let fn = Symbol(context)
  context[fn] = this //给context添加一个方法 指向this
  // 处理参数 去除第一个参数this 其它传入fn函数
  let args = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组
  context[fn](...args) //执行fn
  delete context[fn] //删除方法
}

var obj = {
    name: 'Bob',
    age: '18',
    fn() {
        console.log(`My name is ${this.name}, my age is ${this.age}`)
    }
}

var dog = {
    name: 'Snoopy',
    age: 3
}
obj.fn.call(dog,'daddata','ttt','yuyuyuuy') // My name is Snoby, my age is 3
obj.fn._call(dog,'daddata','ttt','yuyuyuuy') // My name is Snoby, my age is 3

2、 apply 方法

Function.prototype._apply = function(context) {
    // 如果没有传或传的值为空对象 context指向window
    context = context || window
    let fn = Symbol(context)
    context[fn] = this 
    let arg = [...arguments].slice(1) 
    context[fn](arg) //执行fn
    delete context[fn] //删除方法
}

3、bind方法

/**
 * _bind
 *
 * @param {*} context
 */
Function.prototype._bind = function (context) {
  //返回一个绑定this的函数,我们需要在此保存this
  let self = this
  // 可以支持柯里化传参,保存参数
  let arg = [...arguments].slice(1)
  // 返回一个函数
  return function () {
    //同样因为支持柯里化形式传参我们需要再次获取存储参数
    let newArg = [...arguments]
    // 返回函数绑定this,传入两次保存的参数
    //考虑返回函数有返回值做了return
    return self.apply(context, arg.concat(newArg))
  }
}

4、promise方法

function MyPromise(exceptor) {
  let _this = this;
  _this.reason = '';
  _this.value = '';
  _this.resolveFnArr = [];
  _this.rejectFnArr = [];
  _this.status = 'pending';
  function resolve(val) {
    if (_this.status === 'pending') {
      _this.value = val;
      _this.status = 'fulfiled';
      _this.resolveFnArr.forEach(fn => fn())
    }
  }

  function reject(reason) {
    if (_this.status === 'pending') {
      _this.reason = reason;
      _this.status = 'reject';
      _this.rejectFnArr.forEach(fn => fn())
    }
  }

  try {
    exceptor(resolve, reject);
  } catch (error) {
    reject();
  }
}

MyPromise.prototype.then = function(resolve, reject) {
  let _this = this;
  if (_this.status === 'fulfiled') {
    resolve(_this.value);
  }
  if (_this.status === 'reject') {
    reject(_this.reason);
  }

  if (_this.status === 'pending') {
    _this.resolveFnArr.push(() => {resolve(_this.value)})
    _this.rejectFnArr.push(() => {reject(_this.reason)})
  }
}

5、全面的promise写法

(function(window,undefined){
 
  // resolve 和 reject 最终都会调用该函数
  var final = function(status,value){
      var _this = this, fn, status;
   
      if(_this._status !== 'PENDING') return;
   
      // 所以的执行都是异步调用,保证then是先执行的
      setTimeout(function(){
          _this._status = status;
          status = _this._status === 'FULFILLED'
          queue = _this[status ? '_resolves' : '_rejects'];
   
          while(fn = queue.shift()) {
              value = fn.call(_this, value) || value;
          }
   
          _this[status ? '_value' : '_reason'] = value;
          _this['_resolves'] = _this['_rejects'] = undefined;
      });
  }
   
   
  //参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve 和 reject
  var MyPromise = function(resolver){
      if (!(typeof resolver === 'function' ))
          throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
      //如果不是promise实例,就new一个
      if(!(this instanceof MyPromise)) return new MyPromise(resolver);
   
      var _this = this;
      _this._value;
      _this._reason;
      _this._status = 'PENDING';
      //存储状态
      _this._resolves = [];
      _this._rejects = [];
   
      //
      var resolve = function(value) {
          //由於apply參數是數組
          final.apply(_this,['FULFILLED'].concat([value]));
      }
   
      var reject = function(reason){
          final.apply(_this,['REJECTED'].concat([reason]));
      }
   
      resolver(resolve,reject);
  }
   
  MyPromise.prototype.then = function(onFulfilled,onRejected){
      var _this = this;
      // 每次返回一个promise,保证是可thenable的
      return new MyPromise(function(resolve,reject){
   
          function handle(value) {
              // 這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve
              var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value;
   
              //判断是不是promise 对象
              if (ret && typeof ret ['then'] == 'function') {
                  ret.then(function(value) {
                      resolve(value);
                  }, function(reason) {
                      reject(reason);
                  });
              } else {
                  resolve(ret);
              }
          }
   
          function errback(reason){
              reason = typeof onRejected === 'function' && onRejected(reason) || reason;
              reject(reason);
          }
   
          if(_this._status === 'PENDING'){
              _this._resolves.push(handle);
              _this._rejects.push(errback);
          }else if(_this._status === FULFILLED){ // 状态改变后的then操作,立刻执行
              callback(_this._value);
          }else if(_this._status === REJECTED){
              errback(_this._reason);
          }
      });
  }
   
  MyPromise.prototype.catch = function(onRejected){
      return this.then(undefined, onRejected)
  }
   
  MyPromise.prototype.delay = function(ms,value){
      return this.then(function(ori){
          return MyPromise.delay(ms,value || ori);
      })
  }
   
  MyPromise.delay = function(ms,value){
      return new MyPromise(function(resolve,reject){
          setTimeout(function(){
              resolve(value);
              console.log('1');
          },ms);
      })
  }
   
  MyPromise.resolve = function(arg){
      return new MyPromise(function(resolve,reject){
          resolve(arg)
      })
  }
   
  MyPromise.reject = function(arg){
      return MyPromise(function(resolve,reject){
          reject(arg)
      })
  }
   
  MyPromise.all = function(promises){
      if (!Array.isArray(promises)) {
          throw new TypeError('You must pass an array to all.');
      }
      return MyPromise(function(resolve,reject){
          var i = 0,
              result = [],
              len = promises.length,
              count = len
   
          //这里与race中的函数相比,多了一层嵌套,要传入index
          function resolver(index) {
            return function(value) {
              resolveAll(index, value);
            };
          }
   
          function rejecter(reason){
              reject(reason);
          }
   
          function resolveAll(index,value){
              result[index] = value;
              if( --count == 0){
                  resolve(result)
              }
          }
   
          for (; i < len; i++) {
              promises[i].then(resolver(i),rejecter);
          }
      });
  }
   
  MyPromise.race = function(promises){
      if (!Array.isArray(promises)) {
          throw new TypeError('You must pass an array to race.');
      }
      return MyPromise(function(resolve,reject){
          var i = 0,
              len = promises.length;
   
          function resolver(value) {
              resolve(value);
          }
   
          function rejecter(reason){
              reject(reason);
          }
   
          for (; i < len; i++) {
              promises[i].then(resolver,rejecter);
          }
      });
  }
   
  window.MyPromise = MyPromise;
   
  })(window);

 6、filter

Array.prototype.filter = function(callback, thisArg) {
  if (this == undefined) {
    throw new TypeError('this is null or not undefined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callback + 'is not a function');
  }
  const res = [];
  // 让O成为回调函数的对象传递(强制转换对象)
  const O = Object(this);
  // >>>0 保证len为number,且为正整数
  const len = O.length >>> 0;
  for (let i = 0; i < len; i++) {
    // 检查i是否在O的属性(会检查原型链)
    if (i in O) {
      // 回调函数调用传参
      if (callback.call(thisArg, O[i], i, O)) {
        res.push(O[i]);
      }
    }
  }
  return res;
}

7、map方法

Array.prototype.map = function(callback, thisArg) {
  if (this == undefined) {
    throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  const res = [];
  // 同理
  const O = Object(this);
  const len = O.length >>> 0;
  for (let i = 0; i < len; i++) {
    if (i in O) {
      // 调用回调函数并传入新数组
      res[i] = callback.call(thisArg, O[i], i, this);
    }
  }
  return res;
}

8、forEach方法

Array.prototype.forEach = function(callback, thisArg) {
  if (this == null) {
    throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== "function") {
    throw new TypeError(callback + ' is not a function');
  }
  const O = Object(this);
  const len = O.length >>> 0;
  let k = 0;
  while (k < len) {
    if (k in O) {
      callback.call(thisArg, O[k], k, O);
    }
    k++;
  }
}

9、reduce方法

Array.prototype.reduce = function(callback, initialValue) {
  if (this == undefined) {
    throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  const O = Object(this);
  const len = this.length >>> 0;
  let accumulator = initialValue;
  let k = 0;
  // 如果第二个参数为undefined的情况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {
    while (k < len && !(k in O)) {
      k++;
    }
    // 如果超出数组界限还没有找到累加器的初始值,则TypeError
    if (k >= len) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = O[k++];
  }
  while (k < len) {
    if (k in O) {
      accumulator = callback.call(undefined, accumulator, O[k], k, O);
    }
    k++;
  }
  return accumulator;
}

10、debounce(防抖)

const debounce = (fn, time) => {
  let timeout = null;
  return function() {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  }
};

11、throttle(节流)

var throttle = function(func, delay) {            
  var prev = Date.now();            
  return function() {                
    var context = this;                
    var args = arguments;                
    var now = Date.now();                
    if (now - prev >= delay) {                    
      func.apply(context, args);                    
      prev = Date.now();                
    }            
  }        
}        
function handle() {            
  console.log(Math.random());        
}        
window.addEventListener('scroll', throttle(handle, 1000));

12、模拟new操作

function newOperator(ctor, ...args) {
  if (typeof ctor !== 'function') {
    throw new TypeError('Type Error');
  }
  const obj = Object.create(ctor.prototype);
  const res = ctor.apply(obj, args);

  const isObject = typeof res === 'object' && res !== null;
  const isFunction = typeof res === 'function';
  return isObject || isFunction ? res : obj;
}

13、instanceof

const myInstanceof = (left, right) => {
  // 基本数据类型都返回false
  if (typeof left !== 'object' || left === null) return false;
  let proto = Object.getPrototypeOf(left);
  while (true) {
    if (proto === null) return false;
    if (proto === right.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}

14、原型继承

function Parent() {
  this.name = 'parent';
}
function Child() {
  Parent.call(this);
  this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

15、Object.assign

Object.defineProperty(Object, 'assign', {
  value: function(target, ...args) {
    if (target == null) {
      return new TypeError('Cannot convert undefined or null to object');
    }
    
    // 目标对象需要统一是引用数据类型,若不是会自动转换
    const to = Object(target);

    for (let i = 0; i < args.length; i++) {
      // 每一个源对象
      const nextSource = args[i];
      if (nextSource !== null) {
        // 使用for...in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)
        for (const nextKey in nextSource) {
          if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
    }
    return to;
  },
  // 不可枚举
  enumerable: false,
  writable: true,
  configurable: true,
})

16、深拷贝

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数处理
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  // 哈希表中存在直接返回
  if (hash.has(target)) return hash.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  // 针对Symbol属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {
    symKeys.forEach(symKey => {
      if (typeof target[symKey] === 'object' && target[symKey] !== null) {
        cloneTarget[symKey] = cloneDeep1(target[symKey]);
      } else {
        cloneTarget[symKey] = target[symKey];
      }
    })
  }

  for (const i in target) {
    if (Object.prototype.hasOwnProperty.call(target, i)) {
      cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }
  return cloneTarget;
}

17、JSONP

const jsonp = ({ url, params, callbackName }) => {
  const generateUrl = () => {
    let dataSrc = '';
    for (let key in params) {
      if (Object.prototype.hasOwnProperty.call(params, key)) {
        dataSrc += `${key}=${params[key]}&`;
      }
    }
    dataSrc += `callback=${callbackName}`;
    return `${url}?${dataSrc}`;
  }
  return new Promise((resolve, reject) => {
    const scriptEle = document.createElement('script');
    scriptEle.src = generateUrl();
    document.body.appendChild(scriptEle);
    window[callbackName] = data => {
      resolve(data);
      document.removeChild(scriptEle);
    }
  })
}

18、AJAX

const getJSON = function(url) {
  return new Promise((resolve, reject) => {
    const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
    xhr.open('GET', url, false);
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) return;
      if (xhr.status === 200 || xhr.status === 304) {
        resolve(xhr.responseText);
      } else {
        reject(new Error(xhr.responseText));
      }
    }
    xhr.send();
  })
}

19、图片懒加载

// 可以给img标签统一自定义属性data-src='default.png',当检测到图片出/现在窗口之后再补充src属性,此时才会进行图片资源加载。
function lazyload() {
  const imgs = document.getElementsByTagName('img');
  const len = imgs.length;
  // 视口的高度
  const viewHeight = document.documentElement.clientHeight;
  // 滚动条高度
  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
  for (let i = 0; i < len; i++) {
    const offsetHeight = imgs[i].offsetTop;
    if (offsetHeight < viewHeight + scrollHeight) {
      const src = imgs[i].dataset.src;
      imgs[i].src = src;
    }
  }
}

20、滚动加载

// 原理就是监听页面滚动事件,分析clientHeight、scrollTop、scrollHeight三者的属性关系。
window.addEventListener('scroll', function() {
  const clientHeight = document.documentElement.clientHeight;
  const scrollTop = document.documentElement.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight;
  if (clientHeight + scrollTop >= scrollHeight) {
    // 检测到滚动至页面底部,进行后续操作
    // ...
  }
}, false);

21、渲染几万条数据不卡住页面

// 渲染大数据时,合理使用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => {
  // 插入十万条数据
  const total = 100000;
  // 一次插入的数据
  const once = 20;
  // 插入数据需要的次数
  const loopCount = Math.ceil(total / once);
  let countOfRender = 0;
  const ul = document.querySelector('ul');
  // 添加数据的方法
  function add() {
    const fragment = document.createDocumentFragment();
    for(let i = 0; i < once; i++) {
      const li = document.createElement('li');
      li.innerText = Math.floor(Math.random() * total);
      fragment.appendChild(li);
    }
    ul.appendChild(fragment);
    countOfRender += 1;
    loop();
  }
  function loop() {
    if(countOfRender < loopCount) {
      window.requestAnimationFrame(add);
    }
  }
  loop();
}, 0)

22、将VirtualDom转化为真实DOM结构

// vnode结构:
// {
//   tag,
//   attrs,
//   children,
// }

//Virtual DOM => DOM
function render(vnode, container) {
  container.appendChild(_render(vnode));
}
function _render(vnode) {
  // 如果是数字类型转化为字符串
  if (typeof vnode === 'number') {
    vnode = String(vnode);
  }
  // 字符串类型直接就是文本节点
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
  }
  // 普通DOM
  const dom = document.createElement(vnode.tag);
  if (vnode.attrs) {
    // 遍历属性
    Object.keys(vnode.attrs).forEach(key => {
      const value = vnode.attrs[key];
      dom.setAttribute(key, value);
    })
  }
  // 子数组进行递归操作
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

 

posted @ 2020-11-20 13:52  believe66  阅读(741)  评论(0编辑  收藏  举报