第五篇 引用类型 - 对象 - Object

概念

什么是对象: 对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。

let obj = {
name: "caixin",
age: 35
}

创建对象的方式

new Object()
let obj = new Object();
obj.name = "caixin"
字面量方式
let obj = {
name: "caixin",
age: 35
}
使用 Object.create(原型对象,自定义属性对象)
Object.create(原型对象,自定义属性对象)方法创建一个新对象,使用现有的对象来提供新创建的对象的 _ proto _
let tempObj = {
x: 1,
y: 2
}
let newAttrObj = {
a:{
writable:true,
configurable:true,
value:100
}
}
let obj = Object.create(tempObj,newAttrObj);
console.log(obj)
=> {
a:100,
_proto_:{
x:1,
y:2
}
}
创建一个普通对象
let obj_1 = Object.create(Object.prototype)
obj_1.toString(); => [object,object]
创建一个空对象,但该对象不继承任何属性和方法
let obj_2 = Object.create(null)
obj_2.toString() => TypeError: obj2.toString is not a function
工厂方式
function createPerson(name,age){
let person = new Object();
person.name = name;
person.age = age;
return person;
}
let person_1 = createPerson("caixin",35)
构造函数
function Person(name,age){
this.name = name;
this.age = age;
this.sayHi = function (){
console.log("hello")
}
}
let person_1 = new Person("caixin",35)
扩展: 实例对象是通过构造函数来创建的,创建的过程叫实例化。
_ proto _
实例对象 person_1 有个属性 _ proto _ ,称之为原型,这既是一个属性也是一个对象,这个属性是给浏览器使用的,不是标准的属性,它的下面还有一个属性 constructor ,称之为构造器,constructor 的值指向生成该对象实例的构造函数 Person
prototype
构造函数 Person 下面有一个属性 prototype,也称之为原型,这个属性是给程序员使用的,是标准属性,它的下面也有一个属性 constructor ,也叫构造器,constructor 的值指向自己 。
_ proto _ 与 prototype 的关系
js对象都有 _ proto _ 属性,也就是构造函数 Person 与实例对象 person_1 都有
只有函数/方法才有 prototype 属性
实例对象的 _ proto _ 指向了构造函数的原型对象 prototype
实例对象的构造器指向创建自己的构造函数
console.log(person_1._proto_.constructor == Person) => true
console.log(person_1._proto_.constructor == Person.prototype.constructor) => true
如何判断实例对象是不是这种数据类型
console.log(d instanceof Person) => false
说明对象实例d不是由构造函数Person来创建的
对象原型实现数据共享
构造函数的原型对象( prototype )中的方法是可以被实例对象直接访问的
通过原型来添加方法,解决数据共享,节省内存空间。
原型中的方法是可以互相访问的
new 操作符具体干了什么 ?
1、在内存中创建一个空的对象
2、让 this 指向这个空的对象,并继承改对象原型
3、调用构造函数,目的是给对象属性和方法
4、返回一个对象
键名 ( 属性 )
对象的所有键名都是字符串,ES6 引入了 Symbol 值也可以作为键名,所以加不加引号都可以
如果键名是数值,会被自动转为字符串
如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错
let obj = {1p:'caixin'} => 报错
对象的每一个键名又称之为‘属性’,它的‘键值’可以是任何类型。包括 function。如果属性的值还是一个对象,就行成了链式引用。
属性可以动态创建,不必在对象声明时就指定。
对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
let obj1 = {}
let obj2 = {}
obj1.a = 1
obj2.a => 1
obj2.b = 2
obj1.b => 2
注意: 如果取消某一个变量对于原对象的引用,不会影响另一个变量
let obj1 = {}
let obj2 = o1
obj1 = 1
obj2 => {}
注意: 如果两个变量指向同一个原始类型的值,那么,变量这时都是值的拷贝
let x = 1
let y = x
x = 2
y => 1
表达式还是语句

对象采用大括号表示,这导致一个问题: 如果行首是一个大括号,它到底是表达式还是语句 ?

为了避免这个歧义,javascript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
{ console.log(1) } => 1
( {foo:123} ) => 123
( console.log(123) ) // 报错
如果要解释为对象,最好在大括号前加上圆括号,因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。

对象属性的操作

属性的读取
读取对象有两种方法: 点运算符 和 方括号运算符
let obj = {
name:'caixin'
}
obj.name => caixin
obj['name'] => caixin
注意:
1、如果是方括号运算符,键名必须是在引号里面,否则会当作变量处理
let foo = "bar"
let obj = {
foo:1,
bar:2,
}
obj.foo => 1
obj[foo] => 2 没有引号,当作变量处理
2、方括号内可以使用表达式
obj['f' + 'oo'] => 1
3、数值键可以不加引号,但会自动转成字符串;数值键不能使用点运算符,会被当成小数点,只能使用方括号。
属性的查看
Object.keys() 和 for in 语句
let obj = {
name:"caixin"
age:"35"
}
console.log(Object.keys(obj)) => ["name","age"]
for (let i in obj){
console.log(i) => name age
}
注意 不能使用 for of 语句
for(let i of obj){
console.log('for of',i) => obj is not iterable
}
属性的删除
delete 命令用于删除对象的属性,删除成功后返回 true
let obj = { p: 1 }
Object.keys(obj) => ["p"]
delete obj.p => true
obj.p => undefined
Object.keys(obj) => []
注意:
1、删除一个不存在的属性,delete 不报错,而且返回 true , 因此不能根据 delete 命令的结果认定某个属性是否存在。
2、只有一种情况 delete 命令会返回 false , 那就是该属性存在,且不得删除。delete 只能删除对象本身的属性,无法删除继承的属性。
3、删除数组元素,数组的长度不受影响
let arr = [1,2,3]
delete arr[1];
console.log(arr) => [1,empty,3]
属性的检测
1in 运算符用于检查某个对象是否包含某个属性,如果包含返回 true 否则返回 false ( 可检测自有属性和继承属性,如果包含则返回 true
let obj = { name:'caixn' }
console.log( 'name' in obj ) => true
console.log( 'toString' in obj ) => true
console.log('age' in obj ) => false
注意:
1in 运算符有一个问题,不能识别哪些属性是自身的,哪些属性是继承的。
2、可以使用对象的 hasOwnProperty 方法判断一下,是否为对象自身的属性。
let obj = {}
if('toString' in obj){
console.log(obj.hasOwnProperty('toString')) => false
}
2hasOwnProperty()
可检测自有属性,包含则返回true。如果是继承属性将返回false
var o = {x: 1, k: undefined};
o.hasOwnProperty("x"); // true - 自有属性
o.hasOwnProperty("k"); // true - 自有属性
o.hasOwnProperty("y"); // false - 非 o 对象属性
o.hasOwnProperty("toString"); // false - 继承属性
3propertyIsEnumerable()
hasOwnpreperty()的增强版, 只有检测到是自有属性且该属性的可枚举性为true时,才返回true
4、使用 !==
通过 !== 判断一个属性是否是 undefined,如果属性值为 undefined 则无法检测

对象属性的类型

对象属性是由名字、值和一组特性(attribute)组成。在 es5 中 ,属性值可以用一个或者两个方法替代,就是 getter 和 setter 。由 getter 和 setter 定义的属性称为 “存储器属性” 也叫做 访问器属性。

对象的每个属性都有一个描述对象 (Descriptor),用来控制属性的行为。

ECMAScript 中有两种属性: 数据属性 和 访问器属性

数据属性

数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有 4 个 描述其行为的特性,又叫属性特性。

属性名 说明 默认值
configurable 能否通过 delete 删除属性从而重新定义属性;能否修改属性的特性;能否把属性修改为访问器属性 true
enumerable 能否通过 for - in 循环返回属性 true
writable 能否修改属性的值 true
value 属性的数据值,属性值存储和读取都是这个位置 undefined
可通过 Object.getOwnPropertyDescriptor() 查看属性的描述对象
使用: Object.getOwnPropertyDescriptor(属性所在对象,属性名)
let obj = { name: "caixin" };
let attributeObj = Object.getOwnPropertyDescriptor(obj,name)
console.log(attributeObj)
=> {
configurable:true,
enumerable:true,
writable:true,
value:"caixin"
}
可通过 Object.defineProperty() 修改属性默认特性
使用: Object.defineProperty(属性所在对象,属性名,描述符对象)
let obj = {};
Object.defineProperty(obj,"name",{
configurable: false,
enumerable: false,
writable: false, => 不可修改
value: "caixin"
})
console.log(obj.name) => "caixin"
obj.name = "caiqiran"
console.log(obj.name) => "caixin" 修改无效
注意:
1、使用 Object.definedProperty() 定义新的属性,描述对象的属性默认值为false
let obj = { age: 35}
Object.definedProperty(obj,"name",{
value: "caixin"
})
console.log(Object.getOwnPropertyDescriptor(obj,"name"))
新的属性,描述对象属性默认是 false
=> {
configurable: false
enumerable: false
writable: false
value:"caixin"
}
Object.definedProperty(obj,"age",{
value: 32
configurable: false
})
console.log(Object.getOwnPropertyDescriptor(obj,"age"))
如果是已有属性则只影响设置的属性
=> {
configurable: false
enumerable: true
writable: true
value:32
}
2、一旦把属性定义为不可配置的,就不能再把它变回可配置了。此时,再调用 Object.defineProperty()方法修改除 writable 之外的特性,都会导致错误。
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
=> 抛出错误
Object.defineProperty(person, "name", {
configurable: true,
value: "Nicholas"
});
访问器属性

访问器属性不包含数据值;它们包含一对 getter 和 setter 函数(不过,这两个函数都不是必需的)。

读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值。

写入访问器属性时,会调用setter 函数并传入新值,这个函数负责决定如何处理数据。

属性名 说明 默认值
get 读取属性时调用的函数,只有 get 则是一个只读属性 undefined
set 写入属性时调用的函数,只有 set 则是一个只写属性 undefined

注意:访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。

使用 Object.defineProperty() 定义单个属性
let book = {
_year: 2022,
edition: 3
}
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue > 2005){
this._year = newValue;
this.edition += newValue - 2018;
}
}
})
book.year = 2020;
console.log(book.year) => 2020
console.log(book.edition) => 3
使用Object.defineProperties()定义多个属性
let book = {};
Object.defineProperties(book,{
_year:{
value:2022 => 数据属性
},
edition:{
value:1 => 数据属性
},
year:{ => 访问器属性
get:function(){
return this._year;
},
set:function(newValue){
if (newValue > 2018) {
this._year = newValue;
this.edition += newValue - 2018;
}
}
}
})
对象属性类型总结

1、数据属性是属性自带的,数据属性可以被继承。

2、访问器属性是需要创建的,访问器属性可以被继承。

3、相关方法

方法 作用 说明
Object.getOwnPropertyDescriptor() 属性的描述对象 -
Object.defineProperty() 修改属性默认特性/定义单个属性 数据属性和访问器属性都可用,使用后数据属性默认值为 false
Object.defineProperties() 定义多个属性 数据属性和访问器属性可用,但注意数据属性设置 false 后不能修改为 true,所以慎用

对象的固有属性

每个对象都有: 原型属性(prototype)、类 (class)、可拓展性(extensible)这 三个 属性

原型属性 prototype
对象的 原型属性 是用来 继承属性 的,原型属性 也简称为 原型。
原型链
谈到继承时,javaScript 只有一种结构: 对象。
每个实例对象 Object 都有一个私有属性 (称之为 _proto_ )指向它的构造函数的原型对象 protoptype 。该原型对象也有一个自己的原型对象 ( proto ),层层向上直到一个对象的原型对象为 null 。这种链式结构叫做: 原型链。
null 没有原型,并作为这个原型链中的最后一个环节。也可表示空对象指针。
__ proto __ 和 prototype 的关系和区别
在所有实现中,对象实例都没办法通过 [[prototype]] 来访问原型,所以定义了 __proto__ 指向对象构造函数的 prototype 。[[prototype]] 可以使用 __proto__ 访问。
1、prototype 是对象的原型属性,可被继承
2、__proto__ 指向对象构造函数的 prototype
3、constructor 属性将原型对象指向关联的构造函数
isPrototypeOf()
isPrototypeOf(): 测试一个对象是否存在于另一个对象的原型链上
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); => true
console.log(Bar.prototype.isPrototypeOf(baz)); => true
console.log(Foo.prototype.isPrototypeOf(baz)); => true
console.log(Object.prototype.isPrototypeOf(baz)); => true
类属性 class

对象的类属性(class attribute)是一个字符串,用来表示对象的类型信息。可以通过 toString() 方法来查询它。 默认的 toString() 方法会返回下面格式的字符串:

[object class]

toString()方法可以被重写,所以为了能得到正确的数据,需要间接调用 Function.call() 方法。

function classof(o) {
return Object.prototype.toString.call(o);
// return Object.prototype.toString.call(o).slice(8, -1);
}
classof(null); => [object Null]
classof(undefined); => [object Undefined]
classof(1); => [object Number]
classof(""); => [object String]
classof(false); => [object Boolean]
classof({}); => [object Object]
classof([]); => [object Array]
classof(/./); => [object RegExp]
classof(new Date()); => [object Date]
classof(window); => [object Window]
可拓展性

对象的可拓展性表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显示可拓展的。

检测可拓展性
Object.isExtensible()
Object.isSealed()
Object.isFrozen()
影响可拓展性的方法
Object.preventExtensions()
Object.seal()
Object.freeze()
注意
1、一旦转为不可拓展后,就无法再转回可拓展
2、preventExtensions 只影响到对象本身的可拓展性
var a = {};
Object.isExtensible(a); => true
Object.preventExtensions(a); => Object.seal()、Object.freeze() 同理
a.name = 'a';
a; => {}
Object.isExtensible(a); => false

对象的方法

对象原型上 继承 的方法
方法名 功能
hasOwnProperty() 检测自身属性
propertyIsEnumerable() 检测自身属性且该属性的可枚举性为 true 时
isPrototypeOf() 测试一个对象是否存在于另一个对象的原型链上
toString() 返回表示调用这个方法的对象值的字符串
toLocaleString() 返回本地化的字符串
toJSON() 返回序列化的结果
valueOf() 将对象转换为原始值
const a = {name: 123};
a.toString(); => [object Object]
a.toLocaleString(); => [object Object]
a.valueOf(); => {name: 123}
const b = [1, 2, 3]
b.toString(); => 1,2,3
b.toLocaleString();
b.valueOf(); => [1, 2, 3]
const d = new Date();
d.toString(); => Fri Aug 28 2020 11:29:42 GMT+0800 (中国标准时间)
d.toLocaleString(); => 2020/8/28 上午11:29:42
d.toJSON(); => 2020-08-28T03:29:42.460Z
d.valueof(); => 1598585382460
Object 构造函数 上 的方法
Object 属性

1、Object.length: 值为 1
2、Object.prototype: 可以为所有 Object 类型的对象添加属性

ES5 方法
方法名 功能
Object.create() 使用指定的原型对象和属性创建一个新对象
Object.defineProperty() 给对象添加一个属性并指定该属性的配置
Object.defineProperties() 给对象添加多个属性并分别指定它们的配置
Object.getOwnPropertyNames() 返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性
Object.getOwnPropertySymbols() 返回一个数组,它包含了指定对象自身所有的符号属性
Object.getOwnPropertyDescriptor() 返回对象指定的属性配置
Object.isExtensible() 判断对象是否可扩展
Object.preventExtensions() 防止对象的任何扩展
Object.seal() 创建一个“密封”对象,实际上是调用Object.preventExtensions()把所有属性标记为configurable:false,仅可修改属性的值
Object.isSealed() 判断对象是否已经密封
Object.freeze() 创建一个“冻结”对象,实际上调用Object.seal(),并把所有属性标记为writable:false
Object.isFrozen() 判断对象是否已经冻结
ES6 方法
方法名 功能
Object.is() 比较两个值是否相同。所有 NaN 值都相等(这与=不同)
Object.assign() 通过复制一个或多个对象来创建一个新的对象
Object.getOwnPropertyDescriptors() 返回对象所有属性的描述对象
Object.setPrototypeOf() 设置对象的原型(即内部 [[Prototype]] 属性)
Object.getPrototypeOf() 返回指定对象的原型对象
Object.keys() 返回一个包含所有给定对象自身可枚举属性名称的数组
Object.values() 返回给定对象自身可枚举值的数组
Object.entries() 返回给定对象自身可枚举属性的 [key, value] 数
Object.fromEntries() Object.entries()的逆操作,用于将一个键值对数组转为对象

javascript对象类别划分

原生对象 ( native object )

原生对象也可以叫做本地对象或者内部对象

ECMA-262 把原生对象(native object)定义为: "独立于宿主环境的 ECMAScript 实现提供的对象"

所以每一种宿主环境都可以使用原生对象

JavaScript中的原生对象有
ObjectFunctionArrayStringBooleanMathNumberDateRegExpErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIErrorGlobal
内置对象 ( built-in object )

ECMA-262 把内置对象(built-in object)定义为: "由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现"

前半部分和原生对象很像,实际上内置对象也都是原生对象,区别在于后半句 "在 ECMAScript程序开始执行时出现"

这意味着开发者无需使用 new + 构造函数 创建,而是JavaScript引擎初始化的时候就被创建,可以直接使用

目前定义的内置对象只有两个
GlobalMath
内置对象是原生对象的一种,主要区别在于是否需要实例化
ECMAScript 中,不存在独立的函数,所有函数都必须是某个对象的方法
在使用原生对象时,我们一般都需要var obj = new Object() 或字面量 var obj = {} 明确实例化生成一个实例再去调用 Object 的某个方法 obj.toSting()
但如果是内置对象 GlobalMath,我们只需Math.floor(2.4) 直接调用,不需要再进行实例化
宿主对象 ( host object )

什么是宿主,ECMA 仅是一套规范,也就是指定的一套编程规则

规则毕竟是规则,如果要发挥作用,必须要有平台或者说环境,这就是ECMA的宿主

ECMAScript 中的 "宿主" 当然就是我们网页的运行环境,即 "操作系统" 和 "浏览器"

所有非原生对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象

所有的 BOMDOM 对象都是宿主对象,它们都属于window对象的子对象
不同的宿主环境所展示的内容不同
它不是 ECMAScript 官方提供的,而是浏览器这个宿主为了方便开发者而加上去的
实际上所有非原生对象都是宿主对象

深浅拷贝

变量的存储方式 栈(stack)和 堆(heap)
自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值
在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。
基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值
引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量
深拷贝及实现方法
基本类型不存在浅拷贝还是深拷贝的问题,主要是针对于引用类型
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据
也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的
深拷贝的实现方式
1JSON.parse(JSON.stringify())
利用JSON.stringify将对象转成JSON字符串
再用JSON.parse把字符串解析成对象
一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝
缺点
不能处理 函数 和 正则 undefined symbol
JSON.stringifyJSON.parse 处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了
2、递归实现
posted @   caix-1987  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示