Javascript作为一种语言,有个美誉,开发者可以重新定义任何事情。虽然这在过去的一些javascript可以,但是ECMAScript5中已经开始得到改变,例如,我们可以使用Object.defineProperty创建一个不能被修改的对象的属性。本文中我们将讲述Object.defineProperty的基本用法。 如果你想在文章开始之前,深入了解Object.defineProperty方法,请戳。
一、基本用法
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数组是不可写的,所以始终指向同一个数组,但是数组的成员是可以变化的,所以将来可能会增加锁定数组或者对象来解决此问题。
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方法, 不允许使用
writable
和value
这两个属性
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()会报错