深入JavaScript对象

对象在js中是一个重要组成部分,在实际的项目开发中关于对象我们可能会遇到以下但不局限于以下的使用场景

  • 创建一个或多个不同对象
  • 使用for-in 循环来遍历对象
  • 给已有对象添加新的属性或者删除已有属性
    进一步当我们在使用for-in 循环来遍历对象时,为了避免遍历原型链上的属性,会使用hasOwnProperty方法来过滤继承的属性。因为for-in 会遍历自身及原型链上所有可枚举的属性。
    进而言之当创建一个var o = {foo: 'foo'} 对象并对o进行遍历并没有使用hasOwnProperty方法进行过滤时指挥遍历到toString属性。但使用'toString' in o === true 告诉我们toString也是o的属性,继承而来的属性,在使用for-in进行遍历时没有打印出toString,由此我们可以推测,toString 属性是不可枚举的。

从上面我们可以看到对象是JS中一个基础而又复杂的数据类型。当我们在项目开发中根据经验教训或者最佳实践来操作对象时,我们应该明白其背后的原理。下面我们就从以下方面(参考但不局限于JS权威指南)对对象进行阐述。

  • 对象创建,继承
  • 属性的查询,设置及更新
  • 属性删除
  • 属性检测
  • 属性枚举
  • 属性gettersetter
  • 属性的特性

对象创建

一切都皆为对象

js中几乎所有类型的值包括原始类型(数字,字符串,布尔值)都可以认为是对象。
在讨论原始类型时这里只讨论字符串类型,数字和布尔值类型的原始值参考字符串类型。

对象的创建
new 构造函数名()
var foo1 = new String('foo');
var foo2 = String('foo');
var foo3 = 'foo'
console.log(foo2 === foo3) // true
console.log(foo1 === foo2) // false
console.log(typeof foo1, typeof foo2, typeof foo3)  // object string string

从上面的代码中可以看到使用 new 和构造函数以及直接使用构造函数来创建函数得到的结果是不同的。 直接使用构造函数创建的foo2 和使用字面量创建字符串的foo3是严格相等的。

下面我们来关注下foo1.

console.log(foo1['0'], foo1['1'], foo1['2'])  //f o o
foo1['0'] = 'b';
console.log(foo1,foo1['0'])  //String {"foo"} "f"
Object.getOwnPropertyDescriptor(foo1, '0') //{value: "f", writable: false, enumerable: true, configurable: false}

foo1是一个对象类型,它具有对象的一切特性,比如访问属性,但在更改foo1'0' 属性时,我们发现无法生效。由此可以推测,该属性是只读不可写的。通过getOwnPropertyDescriptor 获取属性描述符可知该属性不可写,可遍历,不可配置(以为着不可删除)。

字面量创建对象

在实际开发中推荐使用字面量形式创建对象,简洁清晰。
当属性名是非法标识符时需要用引号包括,否则推荐属性名不使用引号。

// 创建一个空对象,自身没有属性,但有继承属性
var foo = {}   
// 创建一个没有任何属性的空对象
var bar = Object.create(null)
//  创建一个具有自身属性及行为(方法)的对象来描述Tom个体
var Tom = {
	age: 12
	sex: 'male',
	'school-name': 'xxx',
	sayHello: function() {
		console.log('hello')
	}
}

属性查询设置

查询及设置

JS中,我们可以随时为对象添加新的属性,也可以更改属性的值。但在设置属性前我们先聊一聊属性的访问,因为这关系到属性的设置。

// 创建一个继承某个对象的函数
function inherit(p) {
	if(p == null) throw new Error('param p cannot be null');
	if(Object.create) {
		return Object.create(p);
	}
	function F() {}
	F.prototype = p;
	return new F();
}
var p = {
	foo: 'foo'
}
Object.defineProperty(p,'bar', {
	value: 'bar',
	writable: false,
	configurable: true,
	enumerable: false
})

var s = inherit(p)
console.log(s) // {}
for(var key in s) {
	console.log(s)    // foo
}
s.foo; //foo 访问一个存在的属性
s.bar; //bar
s.toString // ƒ toString() { [native code] }
s.foobar // undefined
s.foo = 'newFoo' //更改一个存在的属性
s.bar = 'newBar' //更改一个存在的属性,未生效
s  // {foo: 'newFoo'}
s.foobar = 'foobar'// 设置一个未知的属性
s //{foo: "newFoo", foobar: "foobar"}

上面我们创建了一个继承自p对象的s对象,s对象自身没有任何属性,只有继承而来的属性foo bar以及原型链上顶层属性toString,其他属性这里就不一一列举了。在设置属性时从自身查找到Object.prototype属性,如果原型链上的某个原型对象上已有改属性并且改属性不可写即writable:false时,设置新属性无效,即不会在对象自身添加属性比如newBar,如果原型上的某个属性时可写的,那么会给对象自身创建一个新的属性进而覆盖原型对象比如newFoo,如果自身及原型链上所有对象都不包含新设置的属性,直接给对象自身添加新的属性。

对象的可拓展性

isExtensible | preventExtensions

var o1 = {foo: 'foo'};
Object.isExtensible(o1);  // false  p判断是否可拓展
o1.bar = 'bar'; 
o1  // {foo: "foo", bar: "bar"}
Object.preventExtensions(o1);  // o1 不可拓展
o1.type = 'object'    // 不可添加新的属性
o1 //{foo: "foo", bar: "bar"}     
o1.foo = 'newFoo';   //可以更改已有的属性
o1 //{foo: "new Foo", bar: "bar"}
delete o1.foo   // 可删除属性
o1 // {bar: "bar"}  

isSealed | seal
Object.seal将对象所有自身属性设置为不可配置,但自身属性的可写性不发升改变

var o2 = {foo: 'foo'};
Object.isSealed (o2 );  // false  p判断是否密封
o2 .bar = 'bar'; 
o2 // {foo: "foo", bar: "bar"}
Object.seal(o2 );  // o1 不可拓展
o2 .type = 'object'    // 不可添加新的属性
o2 //{foo: "foo", bar: "bar"}     
o2 .foo = 'newFoo';   //可以更改已有的属性
o2 //{foo: "new Foo", bar: "bar"}
delete o2 .foo   // 无法删除,不可配置
o2 //{foo: "newFoo", bar: "bar"}

isFrozen | freeze
Object.freeze将对象所有自身属性设置为不可配置,不可写(只读),但不影响存取器属性。

var o3 = {foo: 'foo'};
Object.isFrozen(o3 );  // false  p判断是否密封
o3 .bar = 'bar'; 
o3 // {foo: "foo", bar: "bar"}
Object.freeze(o3 );  // o1 不可拓展
o3 .type = 'object'    // 不可添加新的属性
o3 //{foo: "foo", bar: "bar"}     
o3 .foo = 'newFoo';   //不可以更改已有的属性
o3 //{foo: "new Foo", bar: "bar"}
delete o3 .foo   // 无法删除,不可配置
o3 //{foo: "foo", bar: "bar"}

获取一个对象的属性时,先查看自身,在从原型链上一级一级向上查找,直到Object.prototype
给对象设置自身没有的属性时,先查看原型上是否存在该属性,如果存在且改属性是不可写的,设置新属性无效,否则给对象自身添加新属性从而覆盖从原型上继承的属性。

属性删除

delete

  • 只能删除自身属性,不能删除原型上的属性
  • 不可配置的属性不可删除

属性检测

检测对象的属性类型有多种方式,比如typeof ,instanceof ,constructor Object.prototype.tostring.call(value)

typeof 操作符返回值
string number boolean undefined object function symbol

typeof 'foo'   // 'string'
typeof 1   //"number"
typeof true //"boolean"
typeof undefined //"undefined"
typeof null //"object"
typeof {} //"object"
typeof function() {}  // "function"
typeof [] //"object"
typeof Symbol()  //"symbol"

不能准确区分数组的类型,对数组进行操作返回object,可以使用Array.isArray(paramer)instanceof Array来判断一个值是否是数组

js中,通过构造函数得到的对象成为构造函数的实例,每个实例都具有constructor属性,指向构造函数。


(1).constructor === Number //true
'1'.constructor  === String  // true
true.constructor   === Boolean // true
({}).constructor === Object // true
([]).constructor === Array // true
(function(){}).constructor  === Function
var s = Symbol()
s.constructor === Symbol // true

Object.prototype.toString.call

// 字符串
var val1 = 's'
val1.toString() //"s"
Object.prototype.toString.call(val1) // "[object String]"

// 数字
var val2 = 2
val2.toString()  //"2"  
Object.prototype.toString.call(val2) // "[object Number]"

// 布尔值
var val3 = true
val3 .toString()  //"true"
Object.prototype.toString.call(val3 ) // "[object Boolean]"

// 数组
var val4 = [1,2,3]
val4.toString()  //"1,2,3"  等价于Object.join()
Object.prototype.toString.call(val4) // "[object Array]"

// 对象

var val5 = {foo: 'foo'}
val5.toString()  //"[object Object]"
Object.prototype.toString.call(val5) //"[object Object]"
// 函数
var val6 = function f() {}
val6 .toString()  //"function f() {}"
Object.prototype.toString.call(val6 ) //"[object Function]"

var val7 = Symbol()
val7.toString()  //"Symbol()"
Object.prototype.toString.call(val7 ) //"[object Symbol]"

构建一个方法简化判断

function getType(arg1) {
	return Object.prototype.toString.call(arg1).slice(8,-1)
}
getType(val1) //"String"
getType(val2) //"Number"
getType(val3) // "Boolean"
getType(val4) // "Array"
getType(val5) // "Object"
getType(val6) //"Function"
getType(null) //"Null"
getType(undefined) // "Undefined"

属性枚举

Object.prototype.propertyIsEnumerable() 检测属性是否可枚举
for-in

遍历自身及原型链上所有可枚举属性,如果自身某个属性不可枚举,原型链上某个属性是可枚举的,那么原型链上可枚举的同名属性不会被遍历出。

var obj= {foo: 'foo'}
Object.defineProperty(obj,'bar', {
	value: 'bar',
	enumerable: false,
	writble: true,
	configable: true
})
Object.prototype.bar = 'prototype_bar';
Object.prototype.newbar = 'prototype_newbar';
obj // {foo: "foo", bar: "bar"}
//输出 foo bar,原型链上的原型对象内置属性是不可遍历的,所以不会被输出,比如toString
for(var key in obj) {
	console.log(key) 
}
Object.keys的实现
Object.keys(obj)  // ['foo']
function getkeys(o) {
	if(typeof p !== 'object') throw New Error('the argument type  must be object');
	let props = [];
	for(var key in o) {
		if o.hasOwnProperty(key) {
			props.push(key)
		}
	}
	return props;
}

从Object.keys的类似实现中可以看到Object.keys输出自身可枚举属性,首先是可枚举属性,然后是自身属性,使用了hasOwnProperty过滤了继承的属性。

getOwnPropertyNames

输出自身所有属性

Object.getOwnPropertyNames(obj) // ["foo", "bar"]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JavaScript对象是用来存储数据和处理数据的一种数据类型。对象是由一些键值对组成的,其中键是唯一的字符串,值可以是任意数据类型。 例如,我们可以创建一个表示人的对象,其中包含姓名、年龄和职业信息: ``` const person = { name: 'John', age: 30, job: 'software developer' }; ``` 我们可以访问对象中的值使用点语法或方括号语法: ``` console.log(person.name); // 'John' console.log(person['age']); // 30 ``` 我们还可以给对象增加新的属性或更新现有属性的值: ``` person.location = 'New York'; person.age = 31; ``` JavaScript对象还可以包含方法,即可以在对象内部定义的函数。例如,我们可以为人对象添加一个方法来打印出自我介绍: ``` person.sayHi = function() { console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`); }; person.sayHi(); // 'Hi, my name is John and I am 31 years old.' ``` JavaScript对象可以通过继承从其他对象中继承属性和方法。这被称为对象的原型链,并且可以使用JavaScript内置的`Object.create()`函数来创建。 例如,我们可以创建一个基本对象,然后从中创建更多特定的对象,继承基 ### 回答2: JavaScript对象是一种复合数据类型,用于存储多个相关属性和方法的集合。对象由一对大括号{}表示,其中包含了若干个键值对(properties)。键(key)是一个字符串或者Symbol类型,值(value)可以是任意类型,包括基本数据类型和其他对象对象的属性可以通过两种方式访问:点运算符(.)和方括号运算符([])。通过点运算符,可以根据属性名称直接访问对象的属性。例如,obj.name将返回对象obj的名为name的属性值。若属性名称是一个变量、表达式或含有特殊字符,可使用方括号运算符。例如,obj[varName] 将返回obj中varName变量对应属性的值。 对象的方法是对象属性的一种特殊类型,它是可以被调用的函数。方法可以通过对象的属性来定义。例如,obj.method = function(){...} 定义了一个名为method的方法。方法可以访问对象的属性,并且使用this关键字引用方法所属的对象。 当通过new关键字创建一个对象时,会使用构造函数来初始化对象的属性和方法。构造函数是一种特殊的函数,用于创建对象实例。由构造函数创建的对象称为实例。每个对象实例都具有构造函数定义的属性和方法。 JavaScript对象还支持对象的扩展。通过Object.create()方法可以创建一个对象,该对象的原型(prototype)是传递的参数或者null。在ES6中引入了class关键字,可以创建类来定义对象,并使用extends关键字继承其他类的属性和方法。 总结起来,JavaScript对象是一种存储多个相关属性和方法的集合。对象通过属性和方法来定义和访问数据。构造函数可以初始化对象的属性和方法,而对象扩展和类机制则提供了更灵活和可扩展的对象创建方式。 ### 回答3: JavaScript对象是一种数据类型,用于存储和组织相关数据和功能。它是由一组属性和方法组成的。对象可以通过字面量形式创建,也可以通过构造函数创建。 对象属性是用键值对表示的,其中键是字符串类型,值可以是任何数据类型,甚至可以是另一个对象。属性值可以通过点操作符或方括号操作符来进行访问和修改。 对象方法是对象属性的一种特殊类型,它是一个函数。方法可以通过对象引用和点操作符来调用。方法可以访问和修改对象的属性,也可以执行其他操作。 JavaScript对象是动态的,可以随时添加和删除属性和方法。这使得对象非常灵活,可以根据需要进行扩展和修改。 JavaScript还提供了一些内置对象,如数组、日期和正则表达式对象。这些对象提供了特定类型数据的功能和方法。 对象可以通过原型继承来共享方法和属性。原型是每个对象都具有的一个属性,它指向另一个对象。当访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript会沿着原型链一直向上查找,直到找到该属性或方法为止。 通过使用对象,我们可以创建复杂的数据结构,组织和管理相关数据和功能。对象在Web开发中非常常见,常用于处理DOM元素、处理用户输入、发送Ajax请求等。 总结来说,JavaScript对象是一种用于存储和组织相关数据和功能的数据类型。对象由属性和方法组成,可以动态地添加和删除属性和方法。对象可以通过原型继承来共享方法和属性,并且在Web开发中非常常用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值