本文目的:es5实现es6的const常量声明
目录
1.1 Object.defineProperty(obj, prop, descriptor)
1.defineProperty用法
1.1 Object.defineProperty(obj, prop, descriptor)
方法用于对对象的属性进行修饰,三个传入参数。第一个参数是要修饰的对象;第二个参数是修饰对象中要被修饰的属性;第三个参数是对于该属性的修饰内容,以对象形式传入,有固定的写法。
举个栗子来简单的说明一下
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
writable: false // example.test的值无法修改
})
example.test = 'change' // 严格模式下会直接报错
console.log(example.test) // test
1.2 修饰参数
下面来解释下它的修饰内容有哪些,一共有六条属性。
1.2.1 configurable
当且仅当该属性的 configurable
键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为 false
。(该段解释源自MDN)
var example = {}
Object.defineProperty(example, 'test', {
configurable: false
})
Object.defineProperty(example, 'test', {
configurable: true
}) // 报错,因为configurable为false,所以描述符不能再被更改
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
configurable: false
})
delete example.test // 严格模式下会报错
console.log(example.test) // test
1.2.2 enumerable
当且仅当该属性的 enumerable
键值为 true
时,该属性才会出现在对象的枚举属性中。
默认为 false
。(该段解释源自MDN)
var example = {
one: 1,
two: 2,
three: 3,
four: 4
}
Object.defineProperty(example, 'three', {
enumerable: false
})
for(var k in example){
console.log(k, example[k])
}
// one 1
// two 2
// four 4
1.2.3 value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。(该段解释源自MDN)
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
value: 'change'
})
console.log(example.test) // change
1.2.4 writable
当且仅当该属性的 writable
键值为 true
时,属性的值,也就是上面的 value
,才能被赋值运算符改变。
默认为 false
。(该段解释源自MDN)
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
writable: false // example.test的值无法修改
})
example.test = 'change' // 严格模式下会直接报错
console.log(example.test) // test
1.2.5 get
属性的 getter 函数,如果没有 getter,则为 undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为 undefined。(该段解释源自MDN)
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
get: function (){
return 'getTest'
}
})
console.log(example.test) // getTest
1.2.6 set
属性的 setter 函数,如果没有 setter,则为 undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this
对象。
默认为 undefined。(该段解释源自MDN)
var example = {}
example.test = 'test'
Object.defineProperty(example, '_test', {
set: function () {
this.test = 'setTest'
}
})
var arr = ['test1', 'test2', 'test3']
for (var k in arr) {
example._test = arr[k]
console.log(example.test) // setTest
}
// 这里之所以用_test赋值,是为了防止test属性自身赋值时调用set产生无限递归
1.3 修饰参数的用法
看完上面的6条修饰属性后,可能会感到很凌乱。可以发现,value和get的功能很类似,writable和set的功能很类似。那么应该怎么使用才能避开冲突呢?
答案其实很简单:分开用。
修饰符的写法可以分成互不共存的两类,分别名为,数据描述符和存取描述符(这两条名词源于MDN)。
数据描述符包括:configurable,enumerable,value,writable
存取描述符包括:configurable,enumerable,get,set
在写修饰参数时,数据描述符独有的value,writable和存取描述符独有的get,set是不能同时出现的,否则会报错。
1.4 需要警惕的默认值
1.4.1 显示创建对象属性
在显式创建一个对象属性时,该属性的修饰词会默认使用数据描述符,并且给value赋予属性值(如果没有属性值的话会赋予默认值undefined,这也是为什么会报错提示 Cannot read properties of undefined 的原因)enumerable、writable的值赋予true。而configurable要分两种情况,一种是通过var声明的,一种是直接作为对象属性创建的
// var声明的对象属性(这里相当于在window对象下声明)
var test='test'
var descriptor = Object.getOwnPropertyDescriptor(window,'test') // 获取修饰词
console.log(descriptor)
// {configurable: false, enumerable: true, value: 'test', writable: true}
test = 'test'; // 作为window对象属性
var descriptor = Object.getOwnPropertyDescriptor(window,'test')
console.log(descriptor)
// {configurable: true, enumerable: true, value: 'test', writable: true}
var example = {}
example.test='test'
var descriptor1 = Object.getOwnPropertyDescriptor(example,'test') // 获取修饰词
console.log(descriptor1)
// {configurable: true, enumerable: true, value: 'test', writable: true}
可以看到,上述两块代码中,修饰符的差别主要体现在configurable
这里可以理解为,因为configurable具有能否被delete删除的功能。上述第一部分代码 var 声明的变量是作为作用域内一个变量存在的,当然没有delete变量这样的写法。而第二部分代码就只是给对象增加了一个属性,所以理应会支持delete来删除
1.4.2 defineProperty创建对象属性
但如果使用defineProperty创建属性,那么未赋值修饰词就会变成默认值(默认值可参考1.2)
var example = {}
Object.defineProperty(example, 'test', {
value: 'test'
})
var descriptor = Object.getOwnPropertyDescriptor(example, 'test')
console.log(descriptor)
// { configurable: false, enumerable: false, value: "test", writable: false }
2 用defineProperty实现常量声明
2.1 数据描述符实现
function setConst(key, value){
Object.defineProperty(window, key, {
configurable: true,
enumerable: true,
value: value,
writable: false
})
}
setConst('test', 'test')
test = 'change' // 非严格模式不会报错
console.log(test) // test
即在window对象下声明了一个不可修改的变量
但这样做有一个缺点,就是如果不在严格模式下,则给常量赋值是不会有报错的提示的
所以就需要另一种实现方式——用访问器属性实现
2.2 存取描述符实现
function setConst(key, value){
Object.defineProperty(window, key, {
configurable: true,
enumerable: true,
get: function (){
return value
},
set: function (){
console.error(`${key} is a constant. Can't be modified`)
}
})
}
setConst('test', 'test')
test = 'change' // test is a constant. Can't be modified
console.log(test) // test
这样可以同样达到声明常量的效果。并且在对常量进行二次赋值时会进行提示
3.难以避免的缺陷以及尽可能的优化
相比于es6的const,我们现在的方法无疑是有很多缺陷的
3.1 所有常量都是全局的
const声明的变量是存在块级作用域的,但setConst产生的所有常量是挂在window全局作用域上的
function setConst(key, value){
Object.defineProperty(window, key, {
configurable: true,
enumerable: true,
get: function (){
return value
},
set: function (){
console.error(`${key} is a constant. Can't be modified`)
}
})
}
setConst('test', 'test')
console.log(window.test) // test
3.2 变量提升
const是不允许变量提升的。但我们的写法如果在调用setConst前使用或声明了该变量,则会因为var允许变量提升的原因,结果就会在作用域中显式声明了一个变量。
如果这个作用域为全局,则相当于提前创建了一个全局变量,并被赋予修饰符。(此时的修饰符内容可参考1.4.1)这样很容易与setConst中defineProperty产生冲突
如果这个作用域为函数作用域,则该变量与setConst生成的全局变量完全没有一点关系
3.3 尽可能的优化
对于上面的问题,可以在函数作用域中声明一个空对象,然后将常量全部维护在这个对象中
use()
function use() {
var constant = {}
setConst('test', 1, constant)
console.log(constant.test) // 1
// ...
}
// 方法新增一个入参用于规定属性所在的目标对象
function setConst(key, value, obj){
obj= obj? obj: window
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get: function (){
return value
},
set: function (){
console.error(`${key} is a constant. Can't be modified`)
}
})
}
通过这样的方式至少可以将常量限制在局部作用域内。但对于变量提升依然没有处理的办法
所以,综上所述,能用es6就不要用es5(QAQ)