Object()的方法一览

在JavaScript中,万物皆来自对象,但是对Object()这个对象的构造函数却一知半解,所以借这个机会来缕清楚Object的方法,并提供低版本的浏览器中兼容方法。

//从chrome浏览器中的控制台输入Object.getOwnPropertyNames(Object),就能够得到Object对象所有自身属性和方法。
Array ["length", "name", "arguments", "caller", "prototype", "assign", "getOwnPropertyDescriptor",
"getOwnPropertyDescriptors", "getOwnPropertyNames", "getOwnPropertySymbols", "is", "preventExtensions", "seal",
"create", "defineProperties", "defineProperty", "freeze", "getPrototypeOf", "setPrototypeOf", "isExtensible",
"isFrozen", "isSealed", "keys", "entries", "values"]
//从输出台中我们可以知道Object共有25个属性和方法,当然由于浏览器的兼容性问题,在Firefox浏览器中输入,得到如下
Array [ "assign", "getPrototypeOf", "setPrototypeOf", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors",
"keys", "values", "entries", "is", "defineProperty","defineProperties","creat","getOwnPropertyNames",
"getOwnPropertySymbols","preventExtensions", "seal","isFrozen", "isSealed","prototype","length","name",
"freeze","isExtensible"]
//所以为了最大限度的得到Object中的方法,我将以chrome版本得到的数据为基准,对Object的方法进行解析,一些还处于试验
//状态也会提到。

Object.assign()

Object.assign(target,source) 中将sources对象中所有可枚举的属性的值复制到目标的对象中,其会返回目标对象。该方法的兼容性不是很好,IE全面沦陷,在移动端方面,仅有少数的浏览器才兼容该方法。幸好的是在MDN上提供了兼容的方法。

if(typeof Object.assign!='function'){
  Object.assign = function(target){
    'use strict';
    if(target = null){
      throw new TypeError('Cannot convert undefined or null to object');
    }
    target = Object(target);
    for(var index=0;index<arguments.length;index++){
      var source = arguments[index];
      if(source!=null){
        for(var key in source){
          if(Object.prototype.hasOwnProperty.call(source,key)){
            target[key] = source[key];
          }
        }
      }
    }
    return target;
  }
}

其实如果我们仔细观看源码的时候就会发现一个事实,那就是Object.assign()只是对一级属性进行复制,而不会对对象里面的对象进行深度拷贝,如果出现同名属性的key值,那么后者会覆盖前者,并不能做到完整的融合,如果要进行融合的话,可以前往depp-assign中阅读

Object.create()

Object.create(__proto__,[properties]) 该方法将__proto__作为原型对象,并将[properties]作为新对象的属性。

Object.defineProperty()

Object.defineProperty(obj,prop,descriptor) 方法在obj对象上对prop属性进行定义或修改,其中descriptor为被定义或修改的属性符。其中对于descriptor属性符可以设置的值如下显示:

  • 【value】表示属性的值,默认为undefined
  • 【writable】该属性是否为可写,如果直接在对象上定义属性,则默认为true。如果设置为false,则属性仅为可读。
  • 【configurable】 如果为false的话,则不能修改(writabel,configurable,enumerable),如果直接在对象上定义属性,则默认为true
  • 【enumerable】是否能够被枚举,如果直接在对象上定义属性,则默认为true。
  • 【get】当对象访问prop属性的实话,会调用这个方法,并返回结果。默认为undefined
  • 【set】当对象设置该属性的时候,会调用这个方法,默认为undefined。

在MVVM框架中的双向数据绑定是通过 Object.defineProperty() 来实现的,其核心的代码如下所示:

function defineReactive(obj, key, value) {
    var dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            if (Dep.target) {
                dep.depend()
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            if (value === newVal) {
                return
            } else {
                value = newVal
                dep.notify()
            }
        }
    })
}

Object.defineProperty(obj,prop,descriptor) 目前兼容性做的不错,移动端上兼容绝大部分的浏览器,PC上也能够兼容到IE9及以上。注意,该方法在IE8上也是能够使用的,但是其只能够传入DOM对象,如果传入其他对象会报错。可能有人会对该方法的作用产生疑问,其实该方法极大的优化了对象的获取和修改属性方式,以下是来自腾讯AlloyTeam的一个示例:

//当做动画效果的时候,常常这样做,非常的繁琐
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;
//有了Object.defineProperty()之后就可以这样做了
Object.defineProperty(targetDom, 'translateX', {
    set: function(value) {
         var transformText = 'translateX(' + value + 'px)';
        dom.style.webkitTransform = transformText;
        dom.style.transform = transformText;
    }
}
//这样再后面调用的时候, 十分简单
dom.translateX = 10;
dom.translateX = -10;

可能有人觉得这样做还是有点繁琐,但是我们可以封装一个函数库专门做动画效果。
具体的GitHub地址可以前往这里观看Object.defineProperty动画应用

Object.defineProperties()

Object.defineProperties(obj,props) 方法直接在一个对象上修改或创建属性,并返回修改后的对象,其与上面那个方法的区别在于前者可以修改或定义多个属性,但是后者可以定义或修改多个,同时二者的兼容性一样。对于前者而言,有相应的polyfill函数。如下显示:

function defineProperties(obj, properties)
{
  function convertToDescriptor(desc){
    function hasProperty(obj, prop){
      return Object.prototype.hasOwnProperty.call(obj, prop);
    }

    function isCallable(v){
      // 如果除函数以外,还有其他类型的值也可以被调用,则可以修改下面的语句
      return typeof v === "function";
    }

    if (typeof desc !== "object" || desc === null)
      throw new TypeError("不是正规的对象");

    var d = {};
    if (hasProperty(desc, "enumerable"))
      d.enumerable = !!desc.enumerable;
    if (hasProperty(desc, "configurable"))
      d.configurable = !!desc.configurable;
    if (hasProperty(desc, "value"))
      d.value = desc.value;
    if (hasProperty(desc, "writable"))
      d.writable = !!desc.writable;
    if (hasProperty(desc, "get")){
      var g = desc.get;
      if (!isCallable(g) && g !== "undefined")
        throw new TypeError("bad get");
      d.get = g;
    }
    if (hasProperty(desc, "set")){
      var s = desc.set;
      if (!isCallable(s) && s !== "undefined")
        throw new TypeError("bad set");
      d.set = s;
    }

    if (("get" in d || "set" in d) && ("value" in d || "writable" in d))
      throw new TypeError("identity-confused descriptor");

    return d;
  }

  if (typeof obj !== "object" || obj === null)
    throw new TypeError("不是正规的对象");

  properties = Object(properties);
  var keys = Object.keys(properties);
  var descs = [];
  for (var i = 0; i < keys.length; i++)
    descs.push([keys[i], convertToDescriptor(properties[keys[i]])]);
  for (var i = 0; i < descs.length; i++)
    Object.defineProperty(obj, descs[i][0], descs[i][1]);

  return obj;
}

Object.getOwnPropertyDescriptor(obj,prop)

该方法是用于如果prop属性存在对象obj上,则返回其属性描述符,如果不存在就返回undefined。该属性描述符由下面的属性所组成。

  • 【value】表示属性的值,仅针对数据属性描述符有效
  • 【writable】当且仅当属性的值可以被改变时为true。(仅针对数据属性描述有效)
  • 【configurable】 当且仅当指定对象的属性描述可以被改变或者属性可被删除时,为true。
  • 【enumerable】当且仅当指定对象的属性可以被枚举出时,为 true。
  • 【get】获取该属性的访问器函数(getter)。如果没有访问器, 该值为undefined。
  • 【set】获取该属性的设置器函数(setter)。如果没有设置器,该值为undefined。(仅针对包含访问器或设置器的属性描述有效)
    该方法能够在IE8及以上的浏览器上运行。

Object.getOwnPropertyNames(obj)

该方法返回obj上所有自身可枚举和不可枚举的属性(不包括原型链上的属性),如果传入的obj不是数组,则会报错。该方法的兼容IE9及以上的浏览器。

Object.getPrototypeOf(obj)

该方法返回对象的原型对象,如果没有的话,则返回null。需要指出的是,对于函数对象,其返回的并不是显式原型(prototype),而是隐式原型(__proto__),该方法兼容IE9及以上的浏览器。

Object.is(val1,val2)

该方法是确定两个值是否是相同的值,这个方法与===相比,其会将-0和+0看成不等,并且对于两个NaN的比较,Object.is()也会看成是不等的。以下是Object.is()的示例:

Object.is(0,-0)//false
Object.is(-0,-0);//true
Object.is(NaN,0/0); //true
Object.is(5,5/1); //true

该方法在微软公司出的浏览器上只支持EDGE浏览器。不过,万幸的是MDN提供了相应的解决方案。

if (!Object.is) {
  Object.is = function(x, y) {
    if (x === y) { 
      return x !== 0 || 1 / x === 1 / y;
    } else {
      return x !== x && y !== y;
    }
  };
}

Object.preventExtensions()

该方法可以让一个对象永远不能添加新的属性,在严格模式下,如果强行为对象添加属性,会报错,以下是Object.isExtensible()的注意事项:

"use strict";
var obj = {name:"zhang"};
obj.name = "li"//可以进行修改
Object.preventExtensions(obj);
//obj.age = 14;严格模式下会报错
obj.__proto__.age = 13;
console.log(obj);//能够在原型对象上添加属性
obj.__proto__ = {}//不能直接重定义原型,会报错。

Object.seal(obj)

其对一个对象进行密封,并返回被密封的对象,这些对象都是不能够添加属性,不能删除已有属性,以及不能够修改已有属性的可枚举型、可配置型、可写性。

Object.freeze(obj)

该方法将obj对象冻结,其任何属性都是不可以被修改的。现在我们演示下这个用法。

var obj = {name:"zhangsan",prop:{age:23,sex:"man"}};
Object.freeze(obj);
obj.name = "lisi";
console.log(obj.name);//"zhangsan
//我们使用Object.defineProperty()方法来修改属性
Object.defineProperty(obj,'prop',{"age":32,sex:"female"});
console.log(obj.prop);
//{age: 23, sex: "man"}貌似还是不行,我们换种方式看看
Object.prop.age = 25;
console.log(Object.prop);
//{age: 25, sex: "man"}
//这个对象居然改变了,明明已经冻结了,为什么起属性还是可以发生变化

这就要说到Object.freeze(obj) 的特性了,其只是一个浅冻结。何为浅冻结?浅冻结仅仅是对对象的一级属性进行冻结,像上面代码中所演示的那样,如果直接修改其name和prop属性是不能被修改的。如果属性也是一个对象的话,那将不一样了,直接对属性中的属性就行修改,如Object.prop.age = 25;一样,是可以修改的。既然有浅冻结,就一定有深冻结了,那怎么才能实现深冻结呢?

//我们可以配合递归实现
Object.prototype.deepFreeze = Object.prototype.deepFreeze || function (o){
    var prop, propKey;
    Object.freeze(o); // 首先冻结第一层对象
    for (propKey in o){
        prop = o[propKey];
        if(!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)){
            continue;
        }
        deepFreeze(prop); // 递归
    }
}

可以有人会对preventExtensions,seal,freeze这三个方法产生疑问,这三个方法对从扩展、密封和冻结三个方面对对象进行读写状态的控制,防止对象被改变。其中最弱的一层是preventExtensions,只能让对象无法添加新的属性,其次是seal,该方法无法添加属性,也无法删除属性,最后是freeze。当然,上面这三种方法还是可以通过改变对象的原型对象,来增加原型链上的属性,并且都使浅冻结。有对对象进行控制的方法,就肯定有判断其是否被控制的方法,Object.isExtensible()Object.isSealed()Object.isfreeze(obj)。这三个是判断是否被控制,在此就不再赘述。

Object.keys(obj)

该方法会返回obj上所有可以进行枚举的属性的字符串数组,如下所示:

//数组对象
var arr  =[3,4,5];
console.log(Object.keys(obj))
//[0,1,2]
var obj = {}
console.log(Object.keys(obj))
//[],其不会遍历原型链上的属性。

该方法兼容IE9及以上的浏览器,但是有相应的解决方法。

if (!Object.keys) {
  Object.keys = (function () {
    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 = [];
      for (var prop in obj) {
        if (hasOwnProperty.call(obj, prop)) result.push(prop);
      }
      if (hasDontEnumBug) {
        for (var i=0; i < dontEnumsLength; i++) {
          if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
        }
      }
      return result;
    }
  })()
};

getOwnPropertySymbols(obj)

该方法返回obj对象上自身的(非继承的)所有Symbol属性键。

Object.entries()、Object.getOwnPropertyDescriptors()、Object.values()这些方法还只是处于试验阶段的,所以不在这里进行展开。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容