小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

ES5属性描述符学习笔记

属性的描述

es5对一个对象的属性有个更精准的描述,使得对象的属性在开发人员中更可控,属性可以分为两类:

  • 访问器描述:get,set
  • 数据描述:value,writable
  • 可枚举性描述:enumerable
  • 可配置性描述:configurable

其中访问器描述和数据描述只能存在一个,所以针对每个属性,会有四个描述,writable,enumerable,configurable决定了属性的写入,枚举,配置,这些会影响到诸如Object.keysObject.getOwnPropertyNames获取的结果。

最后的可配置性是正对描述的配置,如果设为false,则这个属性的writable,enumerable,configurable将不能被改变,最为明显的是属性无法被删除,这也是诸如delete window.a无效的原因

window.a=2;
delete window.a; //false
console.log(window.a);//返回 2,说明删除无效

首先我们应该知晓一个属性的默认描述,我们可以通过Object.getOwnPropertyDescriptor来获取一个属性的描述

var o = {"a":1};
Object.getOwnPropertyDescriptor(o,"a");
//返回:Object {value: "1", writable: true, enumerable: true, configurable: true}

因此器默认属性是writeable,enumerable,configurable都为true

定义一个属性的描述,使用Object.defineProperty

var o = {};
Object.defineProperty(o,"a",{
    value:1,
    writable:true,
    enumable:true,
    configurable:true
});
console.log(o.a);  //1

Object.defineProperty的第三个参数也不需要每次都传入4个参数,它也有自己的默认值,我们也需要了解的:

var o = {}
Object.defineProperty(o,"b",{});
console.log(o.b); //Object {b: undefined}
Object.getOwnPropertyDescriptor(o,"a");
//返回:Object {value: undefined, writable: false, enumerable: false, configurable: false}

可见Object.defineProperty第三个参数提供的writable,enumerable,configurable的值全为false,和传统写出的对象属性相反。

接下来看看我们es5为我们带来的基于属性描述符的新特性

属性的遍历

可以通过Object.keysObject.getOwnPropertyNames来获取对象自身(不包括原型链)的属性keys,前者至返回可枚举的属性keys,后者返回可枚举与不可枚举的属性keys

var o = {
    "a":"aa",
    "b":"bb"
};
console.log(Object.keys(o));//["a","b"]
console.log(Object.getOwnPropertyNames(o));//["a","b"]

function fn(){}
console.log(Object.keys(fn));//[]
console.log(Object.getOwnPropertyNames(fn));//["length", "name", "arguments", "caller", "prototype"]

原型创建与获取

这一点和属性描述没多大关系,只是顺便一提。以前我们获取原型可以通过__prop__来获取,一个对象私有属性__prop__是指向它的原型,在es5中提供了新的方式来获取原型:Object.getPrototypeOf

var o = {};
console.log(Object.getPrototypeOf(o)===Object.prototype);//true
var fn = function(){};
console.log(Object.getPrototypeOf(fn)===Function.prototype);//true

我们一可以自己来构建父类与子类,es5提供了Object.create方式来创建子类的原型,第一个参数为父类的原型,第二个参数为子类自身的一些配置,可选(用父子类描述不恰当,因为子类是复制得来的)。

function A(){};
protoA = {a:1,b:2};
A.prototype = protoA;

protoB = Object.create(protoA,{
  c:{
      value:3,
      writable:false
  }
});
function B(){};
B.prototype = protoB;

var b = new B()
console.log(Object.getPrototypeOf(b)===protoB); //true
console.log(Object.getPrototypeOf(protoB)===protoA); //true
console.log(b.a); //通过原型链取值:1

可惜的一点是无法很方便的通过原型链来链式获取父级原型了。

旧方法:
console.log(b.__proto__.__proto__ == protoA);//true
新方法,无法链式了:
var protoTemp = Object.getPrototypeOf(b)
console.log(Object.getPrototypeOf(protoTemp)===protoA);//true

属性get/set访问器

属性可以通过get/set来访问,通过get/set可以用来做更多的事情,比方说我可以用它来观察另外一个属性,并且可以在其get/set期间做其它的处理:

var o = {};
var value = 2;
Object.defineProperty(o,"a",{
    get:function(){
        return value*2;
    },
    set:function(newValue){
        value = newValue*3;
    },
    enumable:true,
    configurable:true
});

o.a = 10; console.log(value);//30 console.log(o.a); //60

这也是前端MVVM框架的最基本的知识了,例如我们可以通过这个来实现更新数据Model后,自定更新view显示:

var model = {};
var node = document.getElementById("div");
Object.defineProperty(model,"node",{
    get:function(){
        return node.innerHTML;
    },
    set:function(newValue){
        node.innerHTML = newValue;
    },
    enumable:true,
    configurable:true
});
console.log(model.node);//通过model获取view的数据
model.node = "update view";//更新model时也就更新了界面

对象内部状态控制

Object对象为我们提供了一些便捷的内部状态控制的方法,下面几个方法从上到下越来越严:

  • Object.preventExtensions:使对象不能新增属性,但可以删除属性。

    var o = {};
    Object.preventExtensions(o);
    Object.defineProperty(o,"a",{
        value:2
    });
    console.log(o.a);//undefined,严格模式下会报错
    
  • Object.seal:使对象不能增加属性,也不能删除属性。其内部实现会把configurable设为false

    var o = {p:"aa"};
    Object.seal(o);
    Object.defineProperty(o,"a",{
        value:2
    });
    console.log(o.a);//undefined,严格模式下会报错
    delete o.p;
    console.log(o.p);//"aa",删除失败
    
  • Object.freeze:使对象不能增加属性,也不能删除属性,同时不能改变其值。其内部实现会把writable也变为false

    var o = {p:"aa"};
    Object.freeze(o);
    o.p = "bb";
    console.log(o.p);//"aa",赋值失败
    

有三种去改变状态的操作,当然也就有方法去盘对某一对象的状态是否被改变:

  • Object.isExtensible
  • Object.isSealed
  • Object.isFrozen

对象的扩展(拷贝)

我们经常需要去扩展一个对象,或者是将一个对象的属性赋给另外一个对象,经常被称为extend或者mixin。一个最简单的mixin函数如下:

function mixin(target,source){
    var hasown = Object.prototype.hasOwnProperty;
    for(var i in source){
        if(hasown.call(source,i)){
            target[i] = source[i];
        }
    }
}
var o = {a:1};
mixin(o,{b:2});
console.log(o);//{a:1,b:2}

但是这个函数功能比较弱,在设计到深拷贝、属性覆写、属性描述等的时候功能就显得不足了。Object.assign方法就可以很好的帮助我们实现相应的功能了但目前只有firefox(34以上)实现了此功能

Object.assign 方法会拷贝源对象自身的并且可枚举的属性到目标对象身上。对于访问器属性,该方法会执行那个访问器属性的 getter 函数,然后把得到的值拷贝给目标对象。并且字符串类型和 symbol 类型的属性都会被拷贝。

var o = {a:1};
Object.assign(o,{b:3},{c:4});
console.log(o);//{a:1,b:3,c:4}

Polyfill

身处前端,最为麻烦也是比较有挑战性的工作就是兼容性了,为了使用新的特性,往往需要一些Ployfill来兼容旧版本的浏览器,可喜的是现在应该IE67基本快退出历史舞台,也为我们的兼容减轻了很多负担,下面也来看看一些API的Polyfill的实现:

  • Object.defineProperty

    if(typeof Object.defineProperty !== 'function'){
        Object.defineProperty = function(obj,prop,desc){
            if('value' in desc){
                obj[prop] = desc.value;
            }
            if('get' in desc){
                obj.__defineGetter__(prop,desc.get);
            }
            if('set' in desc){
                obj.__defineSetter__(prop,desc.set);
            }
            return obj;
        }
    }
    

    上述实现主要是defineGetter,defineSetter的运用。

  • Object.defineProperties

    if(typeof Object.defineProperties !== 'function'){
        Object.defineProperties = function(obj,descs){
          var hasOwn = Object.prototype.hasOwnProperty;
          for(var item in descs){
            if(hasOwn.call(desc,item){
              Object.defineProperty(obj,item,descs[item]);
            }
          }
          return obj;
        }
    }
    
  • Object.create

    if(typeof Object.create !== 'function'){
        Object.create = function(proto,descs){
            function F(){};
            F.prototype = proto;
            var obj = new F();
            if(descs != null){
                Object.defineProperties(obj,descs);
            }
            return obj;
       }
    }
    
  • Object.assign

    这个比较麻烦,github有比较好的实现,在这里贴上链接:

    https://github.com/sindresorhus/object-assign

暂时写这么多了,写文章的过程又学到了不少,写作能使自己深入下去,继续加油!

←支付宝← →微信 →
comments powered by Disqus