Javascript Object.defineProperty()

Javascript作为一种语言,有个美誉,开发者可以重新定义任何事情。虽然这在过去的一些javascript可以,但是ECMAScript5中已经开始得到改变,例如,我们可以使用Object.defineProperty创建一个不能被修改的对象的属性。本文中我们将讲述Object.defineProperty的基本用法。 如果你想在文章开始之前,深入了解Object.defineProperty方法,请戳。

一、基本用法

假如我想构建一个math.js库,看下面的实例:
var mathObj = {
    constants: {
        "pi": 3.14
    },
    areaOfCircle: function(radius) {
        return this.constants.pi*radius*radius;
    }
} 

在上例中,如果有人改变pi的值,那么我们将不会得到正确的计算结果,虽然有很多方法可以解决此问题,但是最简单的方法是使用pi属性不可写。看下面实例:

var mathObj = {
    constants: {},
    areaOfCircle: function(radius) {
        return this.constants.pi*radius*radius;
    }
} 
 
Object.defineProperty(mathObj.constants, "pi", {
    value: 3.14,
    writable: false
});
 
mathObj.constants.pi = "Benjamin";
 
//Outputs: 3.14
console.log(mathObj.constants.pi);

Object.defineProperty(obj, prop, descriptor)方法接收三个参数:需要添加或修改属性的对象,属性名称,属性描述options。从上例可以看出,当给pi赋值为“Benjamin”时,最后输出的值还是3.14。 但是如果给math.js使用“use strict",将会报错,和给undefined赋值一样:

"use strict";
var mathObj = {
    constants: {},
    areaOfCircle: function(radius) {
        return this.constants.pi*radius*radius;
    }
} 
 
Object.defineProperty(mathObj.constants, "pi", {
    value: 3.14,
    writable: false
});
 
mathObj.constants.pi = "Benjamin";
 
//<span style="color: #ff0000;">Outputs: Uncaught TypeError: Cannot assign to read only property 'pi' of #<Object></span> 
console.log(mathObj.constants.pi);

第三个参数的options中,writable默认值为false,所以在上例中可以省略,configurable默认值为false,如果你想使用你的库的用户故意重写pi的值,你可以设置configurable值为true。

Object.defineProperty(principia.constants, "pi", {
    value: 3.14,
    configurable: true
});

但是当你使用Object.defineProperty时,也有一种相当大的Hack,即使设置了writable的值,它也不会保持属性值不变的:

var container = {};
 
Object.defineProperty(container, "arr", {
    writable: false,
    value: ["a", "b"]
});
 
container.arr = ["new array"];
 
// Outputs: ["a", "b"]
console.log(container.arr);
 
container.arr.push("new value");
 
// Outputs: ["a", "b", "new value"]
console.log(container.arr);

arr数组是不可写的,所以始终指向同一个数组,但是数组的成员是可以变化的,所以将来可能会增加锁定数组或者对象来解决此问题。

 
二、兼容性
因为Object.defineProperty方法是ES5的一部分,所以在IE9及现代浏览器,IE8中只得到了部分实现,尽可以使用在DOM对象上,不幸的是,并没有IE8相关的shim来兼容。但是,如果你不需要处理旧的浏览器,defineProperty可能会有你使用的地方。 以上就是对Object.defineProperty方法的描述,文中不妥之处,还望批评指正。
 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 

Vue不兼容IE8原因以及Object.defineProperty详解

原因概述:

  • Vue.js使用了IE8不能模拟的ECMAScript5特性. Vue.js支持所有兼容ES5的浏览器.
  • Vue将遍历此对象所有的属性, 并使用Object.defineProperty把这些属性全部转为getter/setter.
  • Object.defindProperty是仅ES5支持, 且无法shim的特性.

接下来逐步介绍概念.

shim特性

指把一个库引入一个旧的浏览器, 然后用旧的API, 实现一些新的API的功能.

Object.definePropety()

  • 语法: Object.definePropety(obj, prop, descriptor)
  • 参数:
    • obj: 操作对象
    • prop: 需要操作的属性名称
    • descriptor: 属性具有的特性
  • 返回值: 传入的对象, 即第一个参数obj
  • 针对特性描述存在两种形式: 数据描述和存取器描述

数据描述

当修改或定义对象的时候, 给属性添加一些特性

var obj = {
  test: 'hello'
}

// 对象已有的属相添加特性描述
Object.defineProperty(obj, 'test', {
  configurable: true | false,
  enumerable: true | false,
  value: `任意类型的值`,
  writable: true | false
})

// 对象新添加的属性描述
Object.defineProperty(obj, 'newKey', {
  configurable: true | false,
  enumerable: true | false,
  value: `任意类型的值`,
  writable: true | false
})

value

  • 属性对应的值, 可以为任意类型的值.
  • 默认: undefined
// 不设置value的值
Object.defineProperty(obj, 'newKey', {

})
console.log(obj.newKey) // undefined

/*
注: 两段代码不能同时出现 ;
报错: Cannot redefine property: newKey
原因: configurable属性默认为false, 不能修改; writable默认fasle, 不能被重写
*/
// 设置value值
Object.defineProperty(obj, 'newKey', {
  value: 'this is test'
})
console.log(obj.newKey) // undefined

writable

  • 属性的是否可以被重写.
  • 默认false, 不能被重写.
// writable为false, 不可被重写
Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  writable: false
})

Object.defineProperty(obj, 'newKey', {
  value: 'change'
})
// 这种情况下会报错: Cannot redefine property: newKey
console.log(obj.newKey)
// 可以被重写
Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  writable: false
})

obj.newKey = 'change'

console.log(obj.newKey) // hello

enumerable

  • 此属性是否可以枚举(使用for...in或者Object.keys)
  • 默认为false: 不可枚举
// 不可枚举
var obj = {}

Object.defineProperty(obj, 'newKey', {
  value: 'hello'
})

console.dir(obj) // {}
// 可以枚举
var obj = {}

Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  enumerable: true
})

console.dir(obj) // { newKey: 'hello' }

configurable

  • 目标属性是否可以被删除
  • 目标属性的特性是否可以被再次修改
  • 默认false, 不可删除与修改
// 属性不可被删除
var obj = {}
Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  configurable: false
})
delete obj.newKey
console.log(obj.newKey) // hello
// 属性可以被删除
var obj = {}
Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  configurable: true
})
delete obj.newKey
console.log(obj.newKey) // undefined
// 不能修改特性
var obj = {}
Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  writable: false,
  configurable: false
})

Object.defineProperty(obj, 'newKey', {
  writable: true,
})
// 报错: Cannot redefine property: newKey
// 再次修改特性
var obj = {}
Object.defineProperty(obj, 'newKey', {
  value: 'hello',
  writable: false,
  configurable: true
})

Object.defineProperty(obj, 'newKey', {
  writable: true,
})
obj.newKey = 'change'
console.log(obj.newKey) // change

注意

  • 一旦使用Objec.defineProperty给对象添加属性, 如果不设置属性的话, 那么configuable, enumerable, writable这些都是默认的false
  • 不能被枚举, 不能被重写, 不能被再次更改属性

存取器描述

当使用存取器描述特性的时候, 允许使用以下特性属性:

var obj = {}

Object.defineProperty(obj, 'newKey', {
  get: function() {} | undefined,
  set: function() {} | undefined,
  configurable: true | false,
  enumerable: true | false
})
  • 当使用了getter或者setter方法, 不允许使用writablevalue这两个属性

getter/setter

  • 当设置或获取某个对象的属性值的时候, 可以提供getter/setter方法
    • getter: 是一种获取值的方法
    • setter: 是一种设置值的方法
// 在特性中使用get/set属性来定义对应的方法
var obj = {}
var initVlue = 'hello'
Object.defineProperty(obj, 'newKey', {
  get: function () {
    // 当获取值的时候, 触发这个函数
    return initVlue
  },
  set: function (value) {
    // 设置值的时候, 触发这个函数
    initVlue = value
  }
})
// 获取值
console.log(obj.newKey) // hello

obj.newKey = 'change'

console.log(initVlue)// change
  • get/set不必成对出现, 任写其一就可以. 如果设置不方便, 则get和set的默认值为undeifend

兼容性

在IE8下只能对DOM对象使用, 如果对原生对象使用Object.defineProtry()会报错


参考: https://segmentfault.com/a/1190000007434923

posted @ 2016-10-25 16:53  jiangxiaobo  阅读(395)  评论(0编辑  收藏  举报