学习笔记12—Object的属性详解

1 属性的类型

ECMA-262使用一些内部特性来描述属性的特征,这些特性是由为JavaScript实现引擎的规范定义的,因此开发者不能在JavaScript中直接访问这些特性。为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]。属性分两种:数据属性和访问器属性。

(1)数据属性

数据属性包含一个保存数据值的位置,值会从这个位置读取,也会写入到这个位置,数据属性有4个特性描述它们的行为:

  • [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • [[Enumerable]]:表示属性是否可以通过for-in循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • [[Writable]]:表示属性的值是否可以被修改,默认情况下所有直接定义在对象上的属性的这个特性都是true。
  • [[Value]]:包含属性实际的值,这就是前面提到的那个读取和写入属性值的位置。

将属性显式添加到对象之后,[[Configurable]]、[[Enumerable]]和[[Writable]]都会被设置为true,而[[Value]]特性会被设置为指定的值:

要修改属性的默认特性,就必须使用Object.defineProperty()方法,这个方法接收3个参数:要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数,即描述符对象上的属性可以包含:configurable、enumerable、writable和value,跟相关特性的名称一一对应,根据要修改的特性,可以设置一个或多个值:

let person={};
Object.defineProperty(person,"name",{
	writable:false,
	value:"Nicholas"
})
console.log(person.name);//"Nicholas"
person.name = "Greg";
console.log(person.name);//"Nicholas"

这个例子创建了一个名为name的属性,并给他赋予了一个只读的"Nicholas",这个属性的值不能再修改了,在非严格模式下尝试给这个属性重新赋值会被忽略,在严格模式下,尝试修改只读属性的值会抛出错误。
类似的规则也适用于创建不可配置的属性:

let person={};
Object.defineProperty(person,"name",{
	configurable:false,
	value:"Nicholas"
});
console.log(person.name);//Nicholas
delete person.name;
console.log(person.name);//Nicholas

这个例子把configurable设置为false,意味着这个属性不能从对象上被删除。非严格模式下对这个属性调用delete没有效果,严格模式下会抛出错误。此外一个属性被定义为不可配置之后,就不能再变回可配置的了,再次调用Object.defineProperty()并修改任何任何非writable属性会导致错误:

let person = {};
Object.defineProperty(person,"name",{
	configurable:false,
	value:"Nicholas"
});

//Throw an error
Object.defineProperty(person,"name",{
	configurable:true,
	value:"Nicholas"
});

在调用Object.defineProperty()时,configurable、enumerable和writable的值如果不指定,则都会默认为false。

(2)访问器属性
访问器属性不包含数值,相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必须的,在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值, 这个函数必须决定对数据做出什么修改。访问器属性有4个特性描述它们的行为:

  • [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • [[Enumerable]]:表示属性是否可以通过for-in循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • [[Get]]:获取函数,在读取属性时调用,默认值为undefined。
  • [[Set]]:设置函数,在写入属性时调用。默认值为undefined。

访问器属性是不能直接定义的,必须使用Object.defineProperty():

let book = {
	year_:2017,
	edition:1
};

Object.defineProperty(book,"year",{
	get(){
		return this.year_;
	}
	set(newValue){
		if(newValue>2017){
			this.year_ = newValue;
			this.edition += newValue - 2017;
		}
	}
});
book.year = 2018;
console.log(book.edition);//2

在这个例子中,对象book有两个默认属性:year_和edition。year_中的下划线常用来表示该属性并不希望在对象方法的外部被访问。 另一个属性year被定义为一个访问器属性,其中获取函数简单的返回year_的值,而设置函数会做一些计算以决定正确的版本(edition)。因此,把year属性修改为2018会导致year_变成2018,edition变成2。获取函数和设置函数不一定都要定义,只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。

2 定义多个属性

在一个对象上同时定义多个属性的可能性是非常大的,为此ECMAScript提供了Object.defineProperties()方法,这个方法可以通过多个描述符一次性定义多个属性,它接收两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应:

let book = {};
Object.defineProperties(book,{
	year_:{
		value:2017
	},
	edition:{
		value:1
	},
	year:{
		get(){
			return this.year_;
		}
	
		set(newValue){
			if(newValue>2017){
				this.year_ = newValue;
				this.edition += newValue - 2017;
			}
		}
	}
});

这段代码和上面示例一样,唯一的区别是所有属性都是同时定义的,并且数据属性的configurable、enumerable和writable特性都是false;

3 读取属性的特性

使用Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符,这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名,返回值是一个对象,对于访问器属性包含configurable、enumerable、get和set属性,对于数据属性包含configurable、enumerable、writable和value属性:

let book = {};
Object.defineProperties(book,{
	year_:{
		value:2017
	},
	eidtion:{
		value:1
	},
	year:{
		get:function(){
			return this.year_;
		},
		set:function(){
			if(newValue>2017){
				this.year_ = newValue;
				this.edition += newValue - 2017;
			}
		}
	}
});

let descriptor = Object.getOwnPropertyDescriptor(book,"year_");
console.log(descriptor.value);//2017
console.log(descriptor.configure);//false
console.log(typeof descriptor.get);//undefined
let descriptor = Object.getOwnPropertyDescriptor(book,"year");
console.log(descriptor.value);//undefined
console.log(descriptor.enumerable);//false
console.log(typeof descriptor.get);//function

4 合并对象

JavaScript开发者经常觉得"合并"(merge)两个对象很有用,更具体的说,就是把源对象所有的本地属性一起复制到目标对象上。有时候这种操作也被称为"混入"(mixin),因为目标对象通过混入源对象得到了增强。
ECMAScript6专门为合并对象提供了Object.assign()方法。这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象,以字符串和符号为键的属性会被复制,对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值。

let dest,src,result;

dest = {};
src = {id:'src'};

result = Object.assign(dest,src);

//Object.assign修改目标对象,也会返回修改后的目标对象
console.log(dest === result);//true
console.log(dest !== src);//true
console.log(result);//{id:'src'}
console.log(dest);//{id:'src'}

dest = {};
result = Object.assign(dest,{a:'foo'},{b:'bar'});
console.log(result);//{a:foo,b:bar}


Object.assign()实际上对每个源对象执行的是浅复制,如果多个源对象都有相同的属性,则使用最后一个复制的值。此外从源对象访问器属性获取的值,比如获取函数会作为一个静态值赋给目标对象,换句话说不能在两个对象间转移获取函数和设置函数:

let dest,src,result;

//覆盖属性
dest = {id:'dest'};
result = Object.assign(dest,{id:'src1',a:'foo'},{id:'src2',b:'bar')};

//Object.assign会覆盖重复的属性
console.log(result);//{id:src2,a:foo,b:bar}

//可以通过目标对象上的设置函数观察到覆盖的过程
dest = {
	set id(x){
		console.log(x);
	}
};

Object.assign(dest,{id:'first'},{id:'second'},{id:'third'});
//first
//second
//third

//对象引用
dest = {};
src = {a:{}}:
Object.assign(dest,src);

//浅复制意味着只会复制对象的引用
console.log(dest); //{a:{}}
console.log(dest.a === src.a); //true

如果赋值期间出错,则操作会中止并退出,同时抛出错误,Object.assign()没有回滚之前赋值的概念,因此它是一个尽力而为、可能只会完成部分复制的方法。

let dest,src,result;

//错误处理
dest={};
src={
	a:'foo',
	get b(){
		//Object.assign()在调用这个获取函数时会抛出错误
		throw new Error();
	},
	c:'bar'
};

try{
	Object.assign(dest,src);
}catch(e){}

//Object.assign()没办法回滚已完成的修改
//因此在抛出错误之前,目标对象上已完成的修改会继续存在
console.log(dest);//{a:foo}

5 增强的对象语法

ECMAScript6为定义和操作对象新增了很多及其有用的语法糖特性,这些特性都没有改变现有引擎的行为,但极大地提升了处理对象的方便程度。

(1)属性值的简写
在给对象添加变量的时候,开发者经常会发现属性名和变量名是一样的:

let name = 'Matt';

let person = {
	name:name
};

console.log(person);//{name:'Matt'}

为此简写属性名语法出现了,简写属性名只要使用变量名就会自动被解释为同名的属性键,如果没有找到同名变量,则会抛出ReferenceError。

let name = 'Matt';
let person = {
	name
};

console.log(person);//{name:'Matt'}

(2)可计算属性
在引入可计算属性之前,如果想使用变量的值作为属性,那么必须先声明对象,然后使用中括号语法来添加属性,换句话说不能在对象字面量中直接动态命名属性:

const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';

let person = {};
person[nameKey] = 'Matt';
person[ageKey] = 27;
person[jobKey] = 'Software engineer';
console.log(person);//{name:'Matt',age:27,job:'Software engineer'}

有了可计算属性,就可以在对象字面量中完成动态属性赋值,中括号包围的对象属性键告诉运行时将其作为JavaScript表达式而不是字符串来求值:

const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';

let person = {
	[nameKey]:'Matt',
	[ageKey]:27,
	[jobKey]:'Software engineer'
};
console.log(person);//{name:'Matt',age:27,job:'Software engineer'}

因为被当作JavaScript表达式求值,所以可计算属性本身可以是复杂的表达式,在实例化时再求值:

const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let uniqueToken = 0;

function getUniqueKey(key){
	return `${key}_${uniqueToken++}`;
}

let person = {
	[getUniqueKey(nameKey)]:'Matt',
	[getUniqueKey(ageKey)]:27,
	[getUniqueKey(jobKey)]:'Software engineer'
};
console.log(person);//{name_0:'Matt',age_1:27,job_2:'Software engineer'}

(3)简写方法名
在给对象定义方法时,通常都要写一个方法名、冒号,然后再引用一个匿名函数表达式:

let person = {
	sayName:function(name){
		console.log(`My name is ${name}`);
	}
};

person.sayName('Matt');//My name is Matt

新的简写方法的语法遵循同样的模式,但开发者要放弃给函数表达式命名。相应地,这样也可以明显缩短方法声明。

let person = {
	sayName(name){
		console.log(`My name is ${name}`);
	}
};

person.sayName('Matt');//My name is Matt

6 对象解构

ECMAScript新增了对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值:

//不使用对象解构
let person = {
	name:'Matt',
	age:27
};

let personName = person.name,
	personAge = person.age;
console.log(personName);//'Matt'
console.log(personAge);//27

//使用对象解构
let person = {
	name:'Matt',
	age:27
};
let {name:personName,age:personAge} = person;
console.log(personName);//Matt
console.log(personAge);//27

使用解构可以在一个类似对象字面量的结构中,声明多个变量,同时执行多个赋值操作,如果想让变量直接使用属性的名称,那么可以使用简写语法:

let person = {
	name:'Matt',
	age:27
};
let {name,age} = person;
console.log(name);//Matt
console.log(age);//27

解构赋值不一定与对象的属性匹配,赋值的时候可以忽略某些属性,而如果引用的属性不存在,则该变量的值就是undefined:

let person = {
	name:'Matt',
	age:27
};
let{name,job} = person;
console.log(name);//'Matt'
console.log(job);//undefined

也可以在解构赋值的同时定义默认值,这适用于前面刚提到的引用属性不存在于源对象中情况:

let person = {
	name:'Matt',
	age:27
};
let {name,job='Software engineer'} = person;
console.log(name);//Matt
console.log(job);//Software engineer

解构并不要求变量必须在解构表达式中声明,不过如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中:

let personName,personAge;

let person = {
	name:'Matt',
	age:27
};

({name:personName,age:personAge} = person);
console.log(personName,personAge);//Matt,27

嵌套解构

解构对于引用嵌套的属性或赋值目标没有限制,为此可以通过解构来复制对象属性:

let person = {
	name:'Matt',
	age:27,
	job:{
		title:'Software engineer'
	}
};
let personCopy = {};

({
	name:personCopy.name,
	age:personCopy.age,
	job:personCopy.job
} = person);

//因为一个对象的引用被赋值给personCopy,所以修改
//person.job对象的属性也会影响personCopy
person.job.title = 'Hacker';

console.log(person);//{name:'Matt',age:27,job:{title:'Hacker'}}
console.log(personCopy);//{name:'Matt',age:27,job:{title:'Hacker'}}

解构赋值可以使用嵌套结构,以匹配嵌套的属性:

let person = {
	name:'Matt',
	age:27,
	job:{
		title:'Software engineer
	}
};

//声明title变量并将person.job.title的值赋给它
let {job:{title}} = person;
console.log(title);//'Software engineer'

在外层属性没有定义的情况下不能使用嵌套解构,无论源对象还是目标对象都一样:

let person = {
	job:{
		title:'Software engineer'
	}
};
let personCopy = {};

//foo在源对象上是undefined
({
	foo:{
		bar:personCopy.bar
	}
} = person);
//TypeError:Cannot destructure property 'bar' of 'undefined' or null

//job在目标对象上是undefined
({
	job:{
		title:personCopy.job.title
	}
} = person);
//TypeError:Cannot set property 'title' of undefined

在函数参数列表中也可以进行解构赋值,对参数的解构赋值不会影响arguments对象,但可以在函数签名中声明在函数体内使用局部变量:

let person = {
	name:'Matt',
	age:27
};

function printPerson(foo,{name,age},bar){
	console.log(arguments);
	console.log(name,age);
}

function printPerson2(foo,{name:personName,age:personAge},bar){
	console.log(arguments);
	console.log(personName,personAge);
}

printPerson('1st',person,'2nd');
//['1st',{name:'Matt',age:27},'2nd']
//'Matt',27

printPerson2('1st',person,'2nd');
//['1st',{name:'Matt',age:27},'2nd']
//'Matt',27
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
个人前几年学习Vxworks时,整理的笔记,总给大家了。 1 基于硬盘启动的Vxworks环境搭建 3 2 Vxworks引导盘制作 6 2.1 通过DOS加载VxWorks方法 6 2.2 Bootrom三种类型 7 2.3 VxWorks映象 7 2.4 Bootrom.sys最快制作方法 8 2.5 从网络引导 8 2.6 从本地硬盘引导 9 2.7 制作bootrom文件 9 2.8 Bootrom编译步骤 11 2.9 用BSP生成Bootable工程(即我们的程序文件) 12 2.10 FTP Server下载VxWorks 14 3 Tornado调试环境的建立 17 3.1.1 配置文件config.h 17 3.1.2 网络连接 18 6 从主机搭接(attach)到目标机 21 7.1.3 串口连接 21 4 4.Config.h文件注释说明 22 4.1 启动行说明 #define DEFAULT_BOOT_LINE \: 22 4.2 Config.h 文件说明 23 4.3 启动参数结构体 BOOT_PARAMS 35 4.4 booting过程介绍,比较详细 38 5 sysClkRateGet();返回系统时钟每秒的tick数量, tick详解 43 6 中断应用设计要点 44 7 驱动程序设计 49 8 缩短vxworks的启动时间 51 9 调试篇 54 10 驱动编程步骤 54 10.1 将驱动程序增加到“系统驱动程序列表”中 54 10.2 将设备增加到“系统设备列表”中 55 10.3 打开设备,得到文件描述符 55 10.4 SELECT机制的使用 55 10.4.1 select( )函数翻译 57 11 VxWorks系统的网络驱动(END) 60 12 VXworks操作系统中信号量用于多任务同步与互斥的讨论 62 12.1 二进制信号量实现互斥和同步 64 12.1.1 互斥的实现: 64 12.1.2 同步的实现: 65 12.2 互斥信号量 67 13 Tornado的文件目说明录 68 14 Shell 内置命令说明 72 14.1 任务管理 72 14.2 任务状态信息 72 14.3 系统修改和调试 73 14.4 对象命令( WindSh Commands for Object Display ) 73 14.5 WindShell and Browser, Shell 命令 74 15 驱动篇 81 16 中断篇 83 16.1 中断服务程序ISR编写注意事项 83 16.2 中断号与中断向量的转换 83 16.3 安装中断服务程序 intConnect() 83 16.4 调试中断服务程序方法 84 17 mkboot批处理命令详细解释 84 18 MakeFile 说明 85 19 VxWorks5.4中的输入输出重定向 89 19.1 vxworks屏幕输出, 一般来说用printf都是串口/shell输出,串口输出就可以到屏幕上了? 89 19.2 20.2 VxWorks中针对X86开发时标准输入输出的重定向? 90 20 怎样加入外部.o文件? 92 21 如何在Vxworks中使用 cd ,pwd , ls 命令:启用File System and Disk Utilities组件(INCLUDE_DISK_UTIL),可在shell下用pwd/cd/ls等命令 93 22 Error: image is larger than 524288 bytes 94 23 proxyArpDefaultOn()未定义解决方法? 94 24 如何将VxWorks的系统定时间隔或系统Ticks设置为1ms????? 95 25 read/write、fread/fwrite、fopen/open有什么区别 96 26 快速启动??????????????? 100 27 启动时报ATA0a和硬盘启动相关问题 100 28 如何安装USB2.2新版本及编译USB驱动? 101 29 WindML、图形界面相关问题 102 29.1 WindML,ugldemo出错? 102 29.2 5101 VxWorks黑屏问题? 102 29.3 添加ugldemo.c后,编译报undefined reference to “ugltextdraw” 错误一大堆? 102 29.4 WindML 中文字库显示? 103 30 VxWork6.8相关问题 105 30.1 ELF和bin文件的区别? 105 30.2 diab和GNU的区别? 106 31 No such file or directory错误? 107

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值