属性操作

_.has

_.has(obj, key):判断 obj 是否含有 key 属性。

该函数依赖于 JavaScript 原生的 Object.prototype.hasOwnProperty,因而他判断的是自身的属性,而不会去寻找原型链上的属性:

_.has = function(obj, key) {
  return obj != null && hasOwnProperty.call(obj, key);
}

用例

var foo = new Object();
foo.bar = 'bar';
_.has(foo, 'bar'); // => true
_.has(foo, 'toString'); // => false

_.keys

_.keys(obj):获得对象的所有属性名。

源码

_.keys = function (obj) {
    if (!_.isObject(obj)) return [];
    // 如果原生的Object.keys方法存在的话,
    // 直接调用Object.keys来获得自有属性
    if (nativeKeys) return nativeKeys(obj);
    var keys = [];
    // 通过_.has,剔除掉非自有属性
    for (var key in obj) if (_.has(obj, key)) keys.push(key);
    // IE9之前存在枚举bug,需要校正最后的属性集合
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
};

如果支持 ES5 的 Object.keys,则用该方法获得对象的属性,否则通过 for in 来取得对象的 key。获得对象 key 集合之后,需要判断 key 集合是否应当修复,因为在 IE9 之前的版本,存在一个 bug,一些 key 不能被 for key in ... 所迭代,我们判断该 bug 的方式是:

var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

该 bug 下不能被 for..in 迭代的 key 有:

var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
        'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

比如,我们重载(遮蔽了)原型链上的 toString 方法,显然,toString 应当属于 obj 的 key 集合,但在IE9以前,可能 就不是这样:

var obj = {
    toString: function() {
        return 'i am an object';
    },
    bar: 'bar'
};

var keys = []
for(var key in obj) {
    keys.push(key);
}
console.log(keys);
// => 小于IE9: ['bar']
// => other: ['bar', 'toString']

在 MDN 提供的 Object.keys 的 polyfill 中,也对此 bug 进行了修复,修复的思路很简单,就是如果发现了不可枚举属性被重载,则把该属性放入 key 集合中:

if (!Object.keys) {
    Object.keys = (function () {
        'use strict';
        var hasOwnProperty = Object.prototype.hasOwnProperty,
            hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
            dontEnums = [
                'toString',
                'toLocaleString',
                'valueOf',
                'hasOwnProperty',
                'isPrototypeOf',
                'propertyIsEnumerable',
                'constructor'
            ],
            dontEnumsLength = dontEnums.length;

        return function (obj) {
            if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
                throw new TypeError('Object.keys called on non-object');
            }

            var result = [],
                prop, i;

            for (prop in obj) {
                if (hasOwnProperty.call(obj, prop)) {
                    result.push(prop);
                }
            }

            if (hasDontEnumBug) {
                for (i = 0; i < dontEnumsLength; i++) {
                    // 如果不可枚举被重载了,则进行修复
                    if (hasOwnProperty.call(obj, dontEnums[i])) {
                        result.push(dontEnums[i]);
                    }
                }
            }
            return result;
        };
    }());
}

而 underscore 则提供了一个内置函数 collectNonEnumProps 来修复属性:

var collectNonEnumProps = function (obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    // 通过对象构造函数获得对象的原型
    var constructor = obj.constructor;
    // 如果构造函数合法,且具有`prototype`属性,那么`prototype`就是该obj的原型
    // 否则默认obj的原型为Object.prototype
    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

    // 如果对象有constructors属性,且当前的属性集合不存在构造函数这一属性
    var prop = 'constructor';
    // 需要将constructor属性添加到属性集合中
    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    // 将不可枚举的属性也添加到属性集合中
    while (nonEnumIdx--) {
        prop = nonEnumerableProps[nonEnumIdx];
        // 注意,添加的属性只能是自有属性 (obj[prop] !== proto[prop])
        if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
            keys.push(prop);
        }
    }
};

与 MDN 中的 polyfill 不同的是,collectNonEnumProps 没有通过 Object.prototype.hasOwnProperty 来判断属性的自有性,而是通过 obj[prop] !== proto[prop] 来判断。

用例

var obj = {
    toString: function() {
        return 'i am an object';
    },
    bar: 'bar'
};

_.keys(obj);
// => ["toString", "bar"]

_.allKeys

_.allKeys(obj):获得 obj 所有属性,包括原型链上的属性。

源码

_.allKeys = function (obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
};

用例

function Person(name) {
    this.name = name;
}

Person.prototype.say = function() {
    console.log('hello');
}

var wxj = new Person('wxj');
_.keys(wxj);
// => ['name']

_.allKeys(wxj);
// => ['name', 'say']

_.values

_.values(obj):获得 obj 的所有值。

源码

_.values = function (obj) {
    // 很简单,遍历key集合,取到对应的value
    var keys = _.keys(obj);
    var length = keys.length;
    // 定长初始化,提前分配内存空间
    var values = Array(length);
    for (var i = 0; i < length; i++) {
        values[i] = obj[keys[i]];
    }
    return values;
};

用例

_.values({one: 1, two: 2, three: 3});
// => [1, 2, 3]

.invert

.invert(obj):调换 obj 的属性和值。

源码

_.invert = function (obj) {
    var result = {};
    var keys = _.keys(obj);
    for (var i = 0, length = keys.length; i < length; i++) {
        result[obj[keys[i]]] = keys[i];
    }
    return result;
};

用例

var obj = {name: 'wxj', age:13};
var newObj = _.invert(obj);
// => {wxj:name, 13:'age'}

_.functions

_.functions(obj):获得 obj 含有的方法。

源码

_.functions = _.methods = function (obj) {
    var names = [];
    for (var key in obj) {
        if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
};

注意到,最后返回的函数列表会按字典序进行排序。

用例

var obj = {
   say: function() {
       console.log("hello");
   },
   run: function() {
       console.log("running");
   }
};
var funcs = _.functions(obj);
// funcs: ["run","say"];

results matching ""

    No results matching ""