polyfill源码阅读(一)ES5

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

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)