文章目录
对象
每一个语言最重要与最基础的数据结构非对象莫属,因为可以通过它实现其他任何数据结构。
什么是对象
在JS语言中,对象的定义未:无序属性的集合,其属性可以包含基本值、对象或者函数,换句话说,就是对象里包含许多属性,这些属性通过映射可以指向任何类型的值。
创建一个对象
- 通过实例
Object来创建一个对象
var o = new Object()
o.name = 'Rex'
o.age = 23
o.getName = function(){
return this.name
}
- 通过对象字面量来创建一个对象
var rexDate = {
name:'rex',
age:23,
getName:function(){
return this.name
}
}
使用对象
JS有2种方法来进行访问对象
- 第一种是通过
.来进行访问
这个是具有声明变量的功能,如果访问的是对象不存在的属性的时候,返回的是
undefined,但是它只是存在于你调用的时候,在你调用完之后,由于JS的垃圾回收机制就会将这个内存进行删除,所以不用担心内存泄漏。这里说一句只是为了区别与我们直接输出声明过的变量是会直接报错的。
var rexDate = {
name:'rex',
age:23,
getName:function(){
return this.name
}
}
console.log(rexDate.age)// 23
console.log(rexDate.getName())// rex
- 第二种是通过
[]来进行访问
var rexDate = {
name:'rex',
age:23,
getName:function(){
return this.name
}
}
console.log(rexDate['age'])// 23
console.log(rexDate['getName']())// rex
当然不推荐这样访问对象,这样不仅写起来麻烦阅读性差,并且每一个属性名字都需要加''字符串进行访问,实在太不人性了。
熟悉数组的同学很快就会发现这就是array的访问的方法,所以也可以侧面看出来数组其实本质就是对象,只不过编译器进行封装,我们可以非常方便实用数组。
对象进阶
我们简单熟悉对象之后,也要认识下一些对象底层的一些方法。无论你通过什么方式进行声明对象,最终的实例都会继承Object的原型链上的属性和方法。这里我们就来认识下这些必然会存在的方法和属性(特性)吧!
被忽视的属性(特性)
1. 数据属性:包含一个数据值的位置。在这个位置可以进行读取和写入
Configurable属性能否被delete删除,能否被修改特性或者能否把属性修改为访问器的属性。
稍微解释下,delete是人自行删除释放内存的方法之一,如果设置为false则无法被这个方法删除。而后者则是无法进行修改其他特性和访问器都不能进行设置。这里简单可以理解为属性的权限。Enumerable属性,顾名思义可枚举的,就是设置是否能够被for-in进行枚举Writable属性,就是能否被修改。Value属性,保存的就是属性的值本身——数据值。如果我们只是声明没有进行赋值,则为undefined
我们直接来测试下吧,文字始终是难记住的,通过例子来强化下记忆!
我们在声明属性的时候如何同时设置这些属性值,我们不进行任何操作时候他们的默认值是多少?
通过Object.getOwnPropertyDescriptor(obj, prop)来获取属性配置
var rexDate = {
}
rexDate.name = 'rex'
console.log(Object.getOwnPropertyDescriptor(rexDate, 'name'))
输出情况
{ value: 'rex',
writable: true,
enumerable: true,
configurable: true }
默认情况下,数据属性都是TRUE,Value就是数值。
我们继续看下函数的Value是如何保存的?
var rexDate = {
}
rexDate.getName = function() {
console.log('rex')
};
console.log(Object.getOwnPropertyDescriptor(rexDate, 'getName'))
Object.getOwnPropertyDescriptor(rexDate, 'getName').value()
{ value: [Function],
writable: true,
enumerable: true,
configurable: true }
rex
我们通过访问value直接执行是可以正确返回的,所以value同样是保存数据值的。
尝试下修改这些值会发现什么变化?
通过Object.defineProperty(obj, prop, descriptor)来获取属性配置
Configurable为false时。
var rexDate = {
}
Object.defineProperty(rexDate, 'name',{
enumerable: true,
configurable: false,
writable: true,
value: "rex"
})
delete rexDate.name
console.log(rexDate.name)
rexDate.name ='Rex'
delete rexDate.name
console.log(rexDate.name)
输出
rex
Rex
无法成功删除,可以修改value,除了configurable之外都是可以重新设置的。
如果我们设置了configurable为false,如果重新设置为true会报错。
var rexDate = {
}
Object.defineProperty(rexDate, 'name',{
enumerable: true,
configurable: false,
writable: true,
value: "rex"
})
Object.defineProperty(rexDate, 'name',{
enumerable: true,
configurable: true,
writable: true,
value: "rex"
})

enumerable为false
var rexDate = {
}
Object.defineProperty(rexDate, 'name',{
enumerable: true,
configurable: true,
writable: true,
value: "rex"
})
for(let i in rexDate) {
console.log(i,rexDate[i])
}
Object.defineProperty(rexDate, 'name',{
enumerable: false,
configurable: true,
writable: true,
value: "rex"
})
for(let i in rexDate) {
console.log(i,rexDate[i])
}
console.log('测试完成')
输出
name rex
测试完成
只输出了一行,所以设置了enumerable就无法被for-in遍历了。
writable为false时
var rexDate = {
}
Object.defineProperty(rexDate, 'name',{
enumerable: true,
configurable: true,
writable: true,
value: "rex"
})
rexDate.name = "Rex"
console.log(rexDate.name)
Object.defineProperty(rexDate, 'name',{
enumerable: true,
configurable: true,
writable: false,
value: "rex"
})
rexDate.name = "Rex"
console.log(rexDate.name)
console.log('测试完成')
输出
Rex
rex
测试完成
设置了false之后,即使修改了也不会保存,但是修改无效。
2. 访问器属性
访问器属性熟悉的同学都知道这是VUE2实现双向绑定的方法,我们就用实现双向绑定来认识访问器属性吧
Configurable同上Enumerable同上Get这里设置当访问值时调用的函数。访问方法通过.或者[]Set这里设置是当属性的value数据值发生变化的时候执行的函数。
直接看例子
<!DOCTYPE HTML>
<html>
<head></head>
<body>
<input ></input>
<p id='data'></p>
</body>
<script>
data ={}
Object.defineProperty(data,'innerHtml',{
get:function(v){
return v
},
set:function (params) {
var pNode = document.querySelector('#data')
pNode.innerHTML=params
inputNoed.value=params
}
})
window.onload=function(){
var inputNoed = document.querySelector('input')
var pNode = document.querySelector('#data')
data.innerHtml= '初始化数据'
}
// 添加input事件
inputNoed.addEventListener('change',function(e){
data.innerHtml = e.target.value
})
</script>
</html>
输出情况

这是一个非常简单的demo,vue会复杂很多,不过理解起来也不难。有了这个属性我们就可以通过给属性设置一些函数,让他们具有特殊的限制,比如我们抽奖的时候,累积抽奖可以提高中奖率,但是避免中奖率又不能超过一个数,在开发的时候,我们设置set让它赋值超过20的时候限制只返回20,这样可以防止后续开发者误操作让中奖率设置太高导致损失。
Object原型上的方法
我们这里列举一些比较通用和常见的。
Object.assign()通过复制一个或多个对象来创建一个新的对象。
可以将多个对象进行合并为一个对象,不过这里是浅复制,如果出现相同名字的属性,则以最后一个对象中的重复属性的值为主。
只会拷贝源对象自身的并且可枚举的属性到目标对象,如果设置了
enumerable为false的话是无法复制的。
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }
我们通过绕下路,将属性中的对象变成字符串,在进行合并,合并之后将字符串变为对象,这样就可以实现深复制。
面试的时候用这个回答会很加分。
// Deep Clone
obj1 = { a: 0 , b: { c: 0}};
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
Object.create()使用指定的原型对象和属性创建一个新对象。
var obj1 = {
name :'rex'
}
var obj2 = Object.create(obj1)
console.log(obj2, obj2.name)
var obj3 = new Object(obj1)
console.log(obj3, obj3.name)
输出
{} 'rex'
{ name: 'rex' } 'rex'
create进行创建的方法并不是直接保存到返回的对象上的,而是保存到对象的prototype原型上,所以可以访问到name
等价于
var obj = function () {
}
var test = {name:'rex'}
obj.prototype = test
var o = new obj()
console.log(o, o.name)
console.log(o.__proto__ === obj.prototype)
console.log(o instanceof obj)
输出情况
{} 'rex'
true
true
new Object()等价于
var obj = function () {this.name = 'rex'}
obj2 = new obj()
/*
var o = new Object()
var bindObj = obj.bind(o)
bindObj()
return o
*/
// 可以看看下面的代码输出什么
var test = function () {
this.name = 'rex'
}
var o = new Object()
var bindO = test.bind(o)
bindO()
console.log(o) // {name : 'rex'}
我们用一张图来解释下这里的区别

我们在寻找属性的时候,如果实例没有对应的属性,会通过访问prototype来继续访问,如果没有则继续访问prototype的prototype直到Object的原型上,如果没有则返回undefined
我们看下Object.create是如何进行创建的?

我们继续看new的过程

我们这里来证明下这个图
let obj = function (params) {
this.name = 'rex'
}
let o = new obj()
console.log(o.prototype == obj)
console.log(o.__proto__ == obj.prototype)
console.log(o.__proto__.constructor == obj)
console.log(o.constructor == obj)
输出
false
true
true
实例的__proto__的确指向这构造函数的原型,构造函数constructor这个属性保存的是构造函数本身,new过程中绑定的函数就是它,通过输出o.__proto__.constructor == obj可以清楚地看到,obj并不是原型本身,而是原型上构造函数constructor指向的函数,而构造函数本身存在一个prototype属性指向这原型。有点难理清楚,我们画张图表示下就明白了。

-
Object.defineProperty()设置属性和属性的配置,Object.defineProperties()是同时设置多个属性的配置 -
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,这里可以阅读阮一峰的ES6教程里的遍历器 -
Object.fromEntries(iterable);参数有键值对的列表参数。map转对象const map = new Map([ ['foo', 'bar'], ['baz', 42] ]); console.log(map)// Map { 'foo' => 'bar', 'baz' => 42 } const obj = Object.fromEntries(map); console.log(obj); // { foo: "bar", baz: 42 }数组转对象
const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]; console.log(arr) // [ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ] const obj = Object.fromEntries(arr); console.log(obj); // { 0: "a", 1: "b", 2: "c" } -
Object.getPrototypeOf()在实例中,我们需要通过__proto__来访问实例的prototype,也可以使用这个方法获取实例的prototype
const prototype1 = {};
const object1 = Object.create(prototype1);
console.log(object1.__proto__ === prototype1);// true
console.log(Object.getPrototypeOf(object1) === prototype1);
// expected output: true
Object.freeze()方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。
下面是ES6拓展的方法Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。Object.getOwnPropertySymbols(obj)返回一个数组,包含对象自身的所有 Symbol 属性的键名。
let obj = {
name:'a',
age:'b',
[Symbol('123')]:123
}
console.log(Object.getOwnPropertyNames(obj))
console.log(Object.getOwnPropertySymbols(obj))
输出
[ 'name', 'age' ]
[ Symbol(123) ]
Object.setPrototypeOf设置prototype
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
设计一个对象
我们从上面可以了解到,通过构造函数可以给每一个实例添加属性和方法,实例也可以继承构造函数的原型对象,这2个唯一的区别就是前者每一个实例都是独立的,而后者每一个实例都是共享的。
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.sayname= function(){
console.log(this.name)
}
Person.prototype.sex = 'mela'
var p1 = new Person('rex',12)
var p2 = new Person('jone',23)
console.log(p1.sayname())// rex
console.log(p2.sayname())// rex
console.log(p2.sex,p1.sex )// mela mela
p1.sex = 'femela'
console.log(p2.sex,p1.sex )// femela femela
构造函数保存每个实例需要单独保存的方法。
原型保存所有实例的共享方法和属性。
参考链接
更多Object方法和详细内容请浏览MDN
《Javascript高级程序设计》

本文深入讲解JavaScript中的对象概念,包括对象的创建、使用、进阶属性及Object原型上的方法,帮助读者理解对象作为JS中最基础的数据结构的重要作用。
3812

被折叠的 条评论
为什么被折叠?



