美文网首页
polyfill源码阅读(一)ES5

polyfill源码阅读(一)ES5

作者: Atlas_lili | 来源:发表于2019-04-08 21:28 被阅读0次

    这是蚂蚁面试官推荐我读的一个项目的源码,可能因为代码结构比较简单,主要解决兼容性,对于前端小白来说比较有用吧。

    Object.getPrototypeOf ( O )

    该静态方法用来返回参数的原型对象。


    兼容性.png
    if (!Object.getPrototypeOf) {
      Object.getPrototypeOf = function (o) {
        if (o !== Object(o)) { throw TypeError("Object.getPrototypeOf called on non-object"); }
        return o.__proto__ || o.constructor.prototype || Object.prototype;
      };
    }
    

    以上是polyfill的实现,_proto_这个属性是一些浏览器自己的实现,不建议使用。我发现了一点美中不足:对于Object.create(null)的纯净对象(没有原型的)出现了意外的报错!就想瞎改一下。

    return o.__proto__ || (o.constructor) ?  (o.constructor.prototype || Object.prototype) : null;
    

    Object.getOwnPropertyNames ( O )

    该静态方法用来返回参数自身(非原型链)的属性名数组。


    兼容性.png
    if (typeof Object.getOwnPropertyNames !== "function") {
      Object.getOwnPropertyNames = function (o) {
        if (o !== Object(o)) { throw TypeError("Object.getOwnPropertyNames called on non-object"); }
        var props = [], p;
        for (p in o) {
          if (Object.prototype.hasOwnProperty.call(o, p)) {
            props.push(p);
          }
        }
        return props;
      };
    }
    

    一次for-in遍历对象,hasOwnProperty验证对应属性是不是非原型链上,思考过keys方法多好(也不完全一样),兼容性和这个方法完全一样,也就没有意义了。美中不足:只能取出可枚举属性。

    Object.create ( O [, Properties] )

    创建对象,自定义它的原型和自带属性(支持用描述符)。


    兼容性.png
    if (typeof Object.create !== "function") {
      Object.create = function (prototype, properties) {
        if (typeof prototype !== "object") { throw TypeError(); }
        function Ctor() {}
        Ctor.prototype = prototype;
        var o = new Ctor();
        if (prototype) { o.constructor = Ctor; }
        if (properties !== undefined) {
          if (properties !== Object(properties)) { throw TypeError(); }
          Object.defineProperties(o, properties);
        }
        return o;
      };
    }
    

    内部创建了一个构造函数,写了一个原型模式。o.constructor = Ctor;这个我就不太懂了。constructor这个属性本来就慎用(除非我们了解它的机制,就是初始的松散的对应关系),我觉得指向Object比这样好。

    Object.defineProperty( O, P, Attributes )

    该方法用来配置对象的属性描述符。


    兼容性.png
    (function() {
      if (!Object.defineProperty ||
          !(function () { try { Object.defineProperty({}, 'x', {}); return true; } catch (e) { return false; } } ())) {
        var orig = Object.defineProperty;
        Object.defineProperty = function (o, prop, desc) {
          // In IE8 try built-in implementation for defining properties on DOM prototypes.
          if (orig) { try { return orig(o, prop, desc); } catch (e) {} }
    
          if (o !== Object(o)) { throw TypeError("Object.defineProperty called on non-object"); }
          if (Object.prototype.__defineGetter__ && ('get' in desc)) {
            Object.prototype.__defineGetter__.call(o, prop, desc.get);
          }
          if (Object.prototype.__defineSetter__ && ('set' in desc)) {
            Object.prototype.__defineSetter__.call(o, prop, desc.set);
          }
          if ('value' in desc) {
            o[prop] = desc.value;
          }
          return o;
        };
      }
    }());
    

    为什么是一个自运行函数呢?orig! 通过_defineGetter_和_defineSetter_设置get和set方法,value自然赋值就可以,可惜无法设置其他描述符。

    Object.defineProperties ( O, Properties )

    批量进行Object.defineProperty操作


    兼容性.png
    if (typeof Object.defineProperties !== "function") {
      Object.defineProperties = function (o, properties) {
        if (o !== Object(o)) { throw TypeError("Object.defineProperties called on non-object"); }
        var name;
        for (name in properties) {
          if (Object.prototype.hasOwnProperty.call(properties, name)) {
            Object.defineProperty(o, name, properties[name]);
          }
        }
        return o;
      };
    }
    

    if (Object.prototype.hasOwnProperty.call(properties, name))可以规避原型链上的属性。

    Object.keys ( O )

    返回参数对象自身可枚举属性名数组。


    兼容性.png
    if (!Object.keys) {
      Object.keys = function (o) {
        if (o !== Object(o)) { throw TypeError('Object.keys called on non-object'); }
        var ret = [], p;
        for (p in o) {
          if (Object.prototype.hasOwnProperty.call(o, p)) {
            ret.push(p);
          }
        }
        return ret;
      };
    }
    

    对比Object.getOwnPropertyNames发现实现的方法一样,也是无奈。

    Function.prototype.bind ( thisArg [, arg1 [, arg2, ... ]] )

    这个方法可以用来拷贝函数,函数柯里化,绑定this。。。。。。


    兼容性.png
    if (!Function.prototype.bind) {
      Function.prototype.bind = function (o) {
        if (typeof this !== 'function') { throw TypeError("Bind must be called on a function"); }
    
        var args = Array.prototype.slice.call(arguments, 1),
            self = this,
            nop = function() {},
            bound = function () {
              return self.apply(this instanceof nop ? this : o,
                                args.concat(Array.prototype.slice.call(arguments)));
            };
    
        if (this.prototype)
          nop.prototype = this.prototype;
        bound.prototype = new nop();
        return bound;
      };
    }
    

    个人觉得args.concat(Array.prototype.slice.call(arguments)))还挺精妙的,参数拼接再apply调用,而且还做了函数原型链的继承,自己写就考虑不到。

    Array.isArray ( arg )

    顾名思义,判断参数是不是数组。


    兼容性.png
    Array.isArray = Array.isArray || function (o) { return Boolean(o && Object.prototype.toString.call(Object(o)) === '[object Array]'); };
    

    意料之外,还有这种类型检查的方法,大概是利用了,Array、Function等从Object托生出来之后重写了toString,调用老的toString。function也可以如此判断。

    Array.prototype.indexOf ( searchElement [ , fromIndex ] )

    image.png
    if (!Array.prototype.indexOf) {
      Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
        if (this === void 0 || this === null) { throw TypeError(); }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) { return -1; }
        var n = 0;
        if (arguments.length > 0) {
          n = Number(arguments[1]);
          if (isNaN(n)) {
            n = 0;
          } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
            n = (n > 0 || -1) * Math.floor(Math.abs(n));
          }
        }
        if (n >= len) { return -1; }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
          if (k in t && t[k] === searchElement) {
            return k;
          }
        }
        return -1;
      };
    }
    
    1. void 我还以为我看见了C语言,其实js是有void函数的,可以把void 0当成undefined(好处有二:void 0小三个字节;避免非保留字undefined被赋值的情况。)
    2. t.length >>> 0,开发小白平时鲜有用到位操作,但是位操作真的是利器。无符号右移主要做了什么呢?首先要强转Number类型,转不了置0,然后非整数变整数,最后如果是负数,返回负数 + 2的32次方。
    3. var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 负数则从后标志索引位。

    还有就是一直在做参数类型的转化,感觉polyfill对这样一个依靠遍历的常用方法在性能上花了心思。

    Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )

    兼容性.png
    if (!Array.prototype.lastIndexOf) {
      Array.prototype.lastIndexOf = function (searchElement /*, fromIndex*/) {
        if (this === void 0 || this === null) { throw TypeError(); }
    
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) { return -1; }
    
        var n = len;
        if (arguments.length > 1) {
          n = Number(arguments[1]);
          if (n !== n) {
            n = 0;
          } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
            n = (n > 0 || -1) * Math.floor(Math.abs(n));
          }
        }
    
        var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n);
    
        for (; k >= 0; k--) {
          if (k in t && t[k] === searchElement) {
            return k;
          }
        }
        return -1;
      };
    }
    

    感觉如出一辙。。。。。。

    Array.prototype.every ( callbackfn [ , thisArg ] )

    兼容性.png
    if (!Array.prototype.every) {
      Array.prototype.every = function (fun /*, thisp */) {
        if (this === void 0 || this === null) { throw TypeError(); }
        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function") { throw TypeError(); }
        var thisp = arguments[1], i;
        for (i = 0; i < len; i++) {
          if (i in t && !fun.call(thisp, t[i], i, t)) {
            return false;
          }
        }
        return true;
      };
    }
    

    延续前面的风格,看来polyfill对数组遍历的处理方法是一致的。

    some、forEach、map、filter、reduce、reduceRight(这两个区别比前几个大一点)不想赘述了

    String.prototype.trim()

    删除字符串两边的空格。


    兼容性.png
    if (!String.prototype.trim) {
      String.prototype.trim = function () {
        return String(this).replace(/^\s+/, '').replace(/\s+$/, '');
      };
    }
    

    正则匹配,去前空格,再去后空格就可以了。

    Date.now ( )

    方法返回自1970年1月1日 00:00:00 UTC到当前时间的毫秒数。


    兼容性.png
    if (!Date.now) {
      Date.now = function now() {
        return Number(new Date());
      };
    // 这个有多种写法
    /* 
    Date.now1 = function now() {
        return new Date().getTime();
      };
      Date.now2 = function now() {
        return Number(new Date());
      };
      Date.now3 = function now() {
        return new Date().valueOf();
      };
     */
    }
    

    Date.prototype.toISOString ( )

    方法返回一个 ISO格式的字符串: YYYY-MM-DDTHH:mm:ss.sssZ。时区总是UTC(协调世界时),加一个后缀“Z”标识。

    兼容性.png
    if (!Date.prototype.toISOString) {
      Date.prototype.toISOString = function () {
        function pad2(n) { return ('00' + n).slice(-2); }
        function pad3(n) { return ('000' + n).slice(-3); }
    
        return this.getUTCFullYear() + '-' +
          pad2(this.getUTCMonth() + 1) + '-' +
          pad2(this.getUTCDate()) + 'T' +
          pad2(this.getUTCHours()) + ':' +
          pad2(this.getUTCMinutes()) + ':' +
          pad2(this.getUTCSeconds()) + '.' +
          pad3(this.getUTCMilliseconds()) + 'Z';
      };
    }
    

    相关文章

      网友评论

          本文标题:polyfill源码阅读(一)ES5

          本文链接:https://www.haomeiwen.com/subject/bnuuiqtx.html