初识JavaScript对象

  • JavaScript对象语法、类型、属性
  • 属性描述符(getOwnPropertyDescriptor()、defineProperty())
  • [[Get]]、[[Put]]、Getter、Setter
  • 有必要了解Ojbect原型上的那些方法

一、JavaScript对象语法、类型、属性、方法

1.1对象字面量

1 var obj = {
2     id:10,
3     value:"心上诗",
4     foo:function(){
5         console.log("播放歌曲" + this.value);
6     }
7 }

1.2对象原型构造:

1 var obj = new Object();
2 obj.id = 10;
3 obj.value = "心上诗";
4 obj.foo = function(){
5     console.log("播放歌曲"+this.value);
6 }

1.3内置对象:

JavaScript有一些对象子类型,通常被称为内置对象:String、Number、Boolean、Object、Function、Array、Data、RegExp、Error;

null并不是对象,typeof null返回“object”只是语言的bug。检查对象类型详细了解:https://www.cnblogs.com/ZheOneAndOnly/p/10486016.html

这些内置对象很像其他语言的中类型(type)或者类(class),比如Java中的String类,但实际上在JavaScript中这些内置对象并不具备完整意义类的设计模式和功能,在接下来的两篇博客还会解析JavaScript的对象原型和class相关内容,以及new机制创建相应类型的对象和constructor方法,后面的博客都会有详细的解析。这里只关注对象本身。

1.4对象的内容(属性):

对象的内容是由一系列属性组成的,这些属性被存储在对象栈内存中“堆内存地址”所指向的特定位置,并呈key/val(键值对)的数据形式。而这并不是最终的形式,对象容器内部只是存储着这些属性的名称,它们就像指针一样(从技术角度来说就是应用),指向这些值的真正位置。

对属性最常见的操作就是访问调用属性,访问属性有两种方式:属性访问、键访问。

1 var obj = {
2     a:2
3 }
4 obj.a;//属性访问:2
5 obj["a"];//键访问:2

这两种访问属性的语法主要区别就在于(.)操作符要求属性满足标识符的命名规范,而([“...”])语法可以接收任意UTF-8/Unicode字符串作为属性名。如果由属性名是“Super-Fun”的属性,那就必须使用["Super-Fun"]语法访问。因为Super-Fun不是有效标识符属性名。

再就是["..."]语法可以使用字符串来访问属性,所以在程序中可以构造这个字符串,比如下面这个示例:

1 var obj = {
2     a:2,
3     b:"333"
4 }
5 var idx = wantA ? "a" : "b";
6 obj[idx];

在ES6中还新增了可计算属性名,详细可以了解这篇博客第二节:https://www.cnblogs.com/ZheOneAndOnly/p/11349574.html

关于JavaScript属性和方法在技术称谓上有些争议,一些比较严谨的说法是当对象调用方法时称为“函数引用”,当对象添加方法时称为“属性指向函数”。这是因为在JavaScript中函数不会永远属于一个对象,call,apply,bind这种改变函数执行的this指向就可以说明这一点。而在其他的一些语言中,方法的意思就是某类和对象上私有的,永远只能给自身或者类包含的对象调用的函数。

数组也是对象,所以虽然每个下标都是整数,但仍然可以给数组添加属性。有时候可能会将数组当作作对象使用,并不添加任何索引,但是这可能并不是什么好的主意,数组和普通对象都根据其对应的行为和用途进行了优化,所以最好只用对象存键值对,只用数组存下标/值对。

1.5复制对象

浅层复制(采用对象枚举的方式就可以实现):

 1 var obj = {
 2     a:10,
 3     b:function(){console.log(this.a)}, //10
 4     c:{
 5         ca:"100",
 6         cb:function(){
 7             console.log(this.ca); //100
 8         }
 9     },
10     d:[1,"jsj",function(){console.log(this)}] //[1,"jsj",fun..]
11 };
12 var o = {};
13 for(var key in obj){ //基于枚举的浅复制
14     o[key] = obj[key];
15 }

在编程中,更多的会关注深复制,也通常被称为深度克隆,详细实现方式可以了解我之前写的这篇博客:https://www.cnblogs.com/ZheOneAndOnly/p/9865001.html

上面的深度克隆采用的是递归算法实现的,资源消耗比较大,如果确定在对象中不会出现方法的话,可以考虑使用JSON的API来实现深度克隆:

//使用JSON的stringify()和parse()来实现没有方法的对象复制
var newobj = JSON.parse( JSON.stringify(someObj) );

二、属性描述符

ES5之前,JavaScript语言本身没有提供可以直接检测属性特性的方法,比如判断属性是否只读。在ES5中Object上提供getOwnPropertyDescriptor(obj,attr)来查看属性的特性:

1 var obj = {a:2};
2 Object.getOwnPropertyDescriptor(obj,"a"); //查看对象obj属性“a”的属性描述符
3 //返回结果
4 // {
5 //     value: 2,             //属性值
6 //     writable: true,      //可写(是否可以修改属性的值)
7 //     enumerable: true,     //可枚举(是否可以枚举这个属性)
8 //     configurable: true    //可配置(是否可以使用defineProperty(..)方法来修改属性描述符)
9 // }

ES5中,除了提供查看属性特性的Object.getOwnPropertyDescriptor方法以外,还提供了一个配置属性特性的方法Object.defineProperty(),此方法可以直接在对象上顶一个新的具有详细描述的属性,或者修改一个对象的现有属性,并返回这个对象。

(通过查看对象特性以后,印证了前面对象内容描述的:对象存储区间内并不包含属性值,而是属性名,它们指向这些属性真正存储的物理地址,因为对象属性实际上是一个引用值类型的对象)

2.1writable:决定是否可以修改属性的值

1 var obj = {};
2 Object.defineProperty(obj,"a",{
3     value:2,
4     wiritable:false
5 });
6 obj.a = 3; //在严格模式下修改不可修改的属性值会报错:
7 console.log(obj.a); //2

2.2Configurable:只要属性是可配置的,就可以使用defineProperty()方法来修改属性描述符。但是要注意configurable一旦修改为false就无法在修改成true了。

 1 var obj = {
 2     a : 2
 3 }
 4 Object.defineProperty(obj,"a",{
 5     value:3,
 6     writable:true,
 7     configurable:false,
 8     enumerable:true
 9 });
10 console.log(obj.a);//3
11 obj.a = 4;
12 console.log(obj.a);//4
13 Object.defineProperty(obj,‘a‘,{ //这行会报错,这次配置全部不会生效
14     value:2,
15     writable:true,
16     configurable:true,
17     enumerable:true
18 });

configurable:false除了禁止配置这个属性,还会禁止删除这个属性。

delete obj.a;
console.log(obj.a);//4

2.3Enumerable:决定属性是否可以枚举for(var key in Object)。

 1 var obj = {};
 2 Object.defineProperty(obj,"a",{
 3     value:2,
 4     enumerable:true
 5 });
 6 Object.defineProperty(obj,"b",{
 7     value:3,
 8     enumerable:false
 9 });
10 for(var key in obj){
11     console.log(obj[key]); //2
12 }

2.4属性不变性:

在前面了解到了属性的值并不是直接存在对象的内存区间内,在对象区间内存储的属性名称,指向属性真正的内存地址。然后通过获取属性描述符又了解到每个属性的真正值得表达是一个引用值对象,这个对象内包含了value值,writable是否可写,configurable是否可配置,enumerable是否可枚举,这些描述整体得描述了属性得状态。到了这里又引发了一个继续思考的问题,属性描述符真的可以控制属性的行为状态吗?比如属性值是数组、对象呢?这种引用值类型的值本身自己就是对象,它们的属性值会受到属性的描述符的控制吗?看下面这个示例:

 1 var obj = {};
 2 Object.defineProperty(obj,"a",{
 3     value:[1,2,3],
 4     writable:false, //不可写
 5     configurable:true,
 6     enumerable:true
 7 });
 8 Object.defineProperty(obj,"b",{
 9     value:[1,2,3],
10     writable:false,    //不可写
11     configurable:false, //不可配置
12     enumerable:false
13 });
14 obj.a.push(4);
15 obj.b.push(4);
16 console.log(obj.a);    //[1, 2, 3, 4]
17 console.log(obj.b);    //[1, 2, 3, 4]

通过示例,可以看到属性描述符实际上控制的是属性描述符value的栈内存,如果value是一个引用值类型,属性描述符并不能控制value指向的引用值的属性。也就是说,一个对象的属性的值并不能由属性的描述符完全控制,准确的说属性描述符不能控制对象的引用值类型的属性值。

虽然属性描述符完全控制属性,但是后面会有解决的办法,毕竟如果采用层级深度控制的话消耗性能,还不利于程序设计的灵活性,毕竟引用值共享赋值是有利于减低代码冗余的,并且还可以实现数据共享。

除了上面的defineProperty可以用来设置配置属性描述符,JavaScript还提供了一系列API来配置固定模式的属性描述符。同时还增加了一个约束对象属性扩张的API:

2.4.1禁止扩展:Object.prevent(obj)

1 var obj = {a:2};
2 Object.preventExtensions(obj); //禁止obj扩展属性
3 obj.b = 20;
4 console.log(obj.b); //undefined

2.4.2密封:Object.seal(obj)

密封对象实际上就是在禁止对象扩展的基础上,给属性设置了不可配置描述符:configurable:false

实际上密封实现的是,对象不能扩展属性,且不能删除属性;但还是可以通过(.)的方式修改属性的值,只是不能使用defineProperty来配置属性的值。

1 var obj = {a:2};
2 Object.seal(obj); //密封
3 obj.b = 20;
4 console.log(obj.a,obj.b); //2  undefined --证明不能扩展
5 delete obj.a;
6 console.log(obj.a);//2 --证明不能删除
7 obj.a = 50;
8 console.log(obj.a);//50 --证明依然可以通过(.)修改属性值

2.4.3冻结:Object.freeze(obj)

冻结对象实际上是在密封的基础上,给属性设置了不可写描述符:writable:false

实际上冻结对象就是,对象不能扩展,不能删除属性,不能重写属性。

1 var obj = {a:2};
2 Object.freeze(obj); //冻结
3 obj.b = 20;
4 console.log(obj.a,obj.b); //2  undefined --证明不可扩展
5 delete obj.a;
6 console.log(obj.a);//2  --证明不可删除
7 obj.a = 50;
8 console.log(obj.a);//2  --证明不可重写

注:不论是密封还是冻结,本质上都是配置属性描述符,所以依然不能完全控制引用值类型的属性值的行为。

三、[[Get]]、[[Put]]、Getter、Setter

在解析四个关键词之前,先来看一个示例:

 1 var obj = {a:2};
 2 console.log(Object.getOwnPropertyDescriptor(obj,"a"));//下面注释为打印结果
 3 //{
 4 //    value: 2,
 5 //    writable: true,
 6 //    enumerable: true,
 7 //    configurable: true
 8 //}
 9 Object.defineProperty(obj,"a",{
10     get:function(){
11         return 20;
12     }
13 });
14 console.log(obj.a);//20
15 console.log(Object.getOwnPropertyDescriptor(obj,"a"));//下面注释为打印结果
16 //{
17 //    get: ƒ,
18 //    set: undefined,
19 //    enumerable: true,
20 //    configurable: true
21 //}

在上面的示例中,给obj对象属性a的属性描述符配置get方法以后,属性a的属性描述符中的value(值)属性消失,writable(是否可写)属性消失。取而代之的是两个方法get和set,也就是说实际上对象属性的操作实际上受两种操作方式控制,当不配置属性的get和set方法时,属性默认[[Get]]、[[Put]]基于属性描述符的操作。当配置属性的get方法和set方法时,属性基于get()、set()方法的内部处理进行操作。

3.1[[Get]]、[[Put]]

到这里有可能会对[[Get]]、[[Put]]产生疑问,这两并没有出现在属性描述符中,那请思考下面这段代码:

1 var obj = {
2     a:2
3 }
4 console.log(obj.a);//2

基于前面的了解得知,属性值无论时原始值类型,还是引用值类型,它本身都是以一个属性描述对象的方式被属性名引用,如果按照正常的对象属性值调用逻辑应该是obj.a.value的方式获取属性值,但是实际上的获取方式却是obj.a,这就不难想到在这个环节隐式的调用了一个获取属性值的方法,这时候我们回想到obj.a.valueOf()方法,是的,这个方法显式的描述了属性值的实际获取方式,而这个方法再根据默认[[Get]]或者配置的get()方法来决定如何获取值。

详细的描述了[[Get]],那写入值得[[Put]]自然也就明白了,这里就不再赘述了。

3.2get()、set()

通过get()、set()配置写入和读出,get()依然受configurable的配置控制和enumerable枚举控制,而默认模式下的writable写入控制被set()方法替换。这也是在对象API中只有禁止扩展(preventExtensions)、密封(seal)、冻结(freeze),却没有控制写入的API,因为这些API依然对get()、set()配置的属性起作用。

 1 var obj = {};
 2 Object.defineProperty(obj,"a",{
 3     set:function(){ //通过set配置默认值
 4         return 20;
 5     },
 6     get:function(){
 7         return 20;
 8     },
 9     configurable:false, //不能配置
10     enumerable:false    //不能枚举
11 });
12 for(var key in obj){
13     console.log(key,obj[key]); //不能枚举到属性a
14 }
15 Object.defineProperty(obj,"a",{ //报错:不能配置
16     configurable:true
17 });

3.3基于set和get方法配置属性的简写方式:

可以通过set()和get()直接在对象字面量中配置属性,而并不一定要defienProperty()来实现:

 1 var obj = {
 2     set a(val){
 3         this._a_ = val * 2;
 4     },
 5     get a(){
 6         return this._a_;
 7     }
 8 }
 9 obj.a = 20;
10 console.log(obj.a);     //40
11 console.log(obj._a_);    //40

至于为什么要使用obj._a_这种方式,这只是一种惯例,没有特殊行为,这种写法可以直接在控制台中查看对象属性时就了解到,ojb.a是通过set()、get()方式配置的属性。当然如果你不想这么写的话,直接在这两个方法中写return也可以,这里让我想起了ES6中的const声明常量,其底层就是基于直接的return来实现的,看下面的代码:

 1 //可以基于set和get实现不可修改的属性
 2 var obj = {
 3     set a(val){ //当然可以不写set方法
 4         return 20;
 5     },
 6     get a(){
 7         return 20;
 8     }
 9 }
10 obj.a = 10;
11 console.log(obj.a);//20

关于set()和get()方法有种应用叫做数据劫持,这个应用技术被广泛应用于各大js框架中,后面有博客解析这个应用。

四、有必要了解Ojbect原型上的那些方法

本来想把这块内容放到对象原型的相关博客中,但是这些方法的应用基本上与原型链的底层原理没有多大关系。

4.1.hasOwnProperty():判断对象上是否有指定的属性,不能判断原型链上的属性,返回boolean值。

1 var obj = {a:10};
2 console.log(obj.hasOwnProperty("a"));//true

4.2.propertyIsEnumerable():判断对象是否有指定的属性,并且是可枚举的属性,返回boolean值。

 1 var obj = {};
 2 Object.defineProperty(obj,"a",{
 3     value:10,
 4     wiritable:true,
 5     configurable:true,
 6     enumerable:true
 7 });
 8 Object.defineProperty(obj,"b",{
 9     value:10,
10     wiritable:true,
11     configurable:true,
12     enumerable:false //不可枚举
13 });
14 console.log(obj.propertyIsEnumerable("a"));//true
15 console.log(obj.propertyIsEnumerable("b"));//false

4.3.Object对象上的API判断属性是否存在、原型上的属性判断:

除了原型链上提供的这两个方法用来判断属性是存在,Object对象上还提供了两个API:

  • Object.getOwnPropertyNames(obj):获取对象属性名,返回一个对象属性名的数组。(自身所有属性,原型链上的属性名不能获取)
  • Object.keys(obj):获取对象上可枚举的属性名,返回一个对象属性名的数组。(自身所有属性,原型链上的属性名不能获取)

虽然不能直接判断原型上的属性是否存在,但是还是可以通过一些方法来查看,但不建议这么做,请看示例:

 1 var obj = {
 2     set a(val){
 3         this._a_ = val;
 4     },
 5     get a(){
 6         return this._a_;
 7     }
 8 };
 9 Object.defineProperty(obj,"b",{
10     value:10,
11     wiritable:true,
12     configurable:true,
13     enumerable:true
14 });
15 Object.defineProperty(obj,"c",{
16     value:10,
17     wiritable:true,
18     configurable:true,
19     enumerable:false //不可枚举
20 });
21 console.log(obj.hasOwnProperty("a"));//true
22 console.log(Object.getOwnPropertyNames(obj));//["a", "b", "c"]
23 console.log(Object.keys(obj));//["a","b"]
24 var o = Object.create(obj); //o自身为一个空对象,它的原型是obj
25 console.log(o.hasOwnProperty("a"));//false
26 console.log(o.__proto__.hasOwnProperty("a"));//true

4.4.剩余的其他方法:

  • toLocaleString():用来将数组转换成字符串模式,包含“,”间隔符,Object类型使用返回“[object Object]”;
  • toString():用来将值转换成字符串类型,详细了解:类型和原生函数及类型转换(二:终结js类型判断)
  • valueOf():用来获取属性值,内部依据[[Get]]或者get()来实现,第三节有详细的解析。
  • 剩下的__……__方法是浏览器私有方法,不提供给编程使用,但是还是可以了解以下它们有什么作用:

__defineGetter__和__defineSetter__用来读写属性的内置方法,在读写属性时被调用。

__lookupGetter__和__lookupSetter__用来读写自身的方法,在读写自身时调用。

原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/11371294.html

时间: 2024-10-05 09:28:16

初识JavaScript对象的相关文章

深入JavaScript对象(Object)与类(class),详细了解类、原型

JavaScript基于原型的对象机制 JavaScript原型上的哪些事 一.JavaScript基于原型的对象机制 JavaScript对象是基于原型的面向对象机制.在一定程度上js基于原型的对象机制依然维持了类的基本特征:抽象.封装.继承.多态.面向类的设计模式:实例化.继承.多态,这些无法直接对应到JavaScript的对象机制.与强类型语言的类相对应的是JavaScript的原型,所以,只能是基于原型来模拟实现类的设计模式. 为了便于理解,这里采用了Function构造函数及对象原型链

JavaScript对象及初识面向对象

JavaScript对象及初识面向对象:

笔记一、初识 Javascript

一.初识 Javascript javascript是一种专为与网页交互儿设计的脚本语言.由三部分组成:ECMAScript  (ECMA-262定义) : 提供核心语言功能文档对象模型(DOM): 提供访问和操作网页内容的方法和接口浏览器对象模型(BOM): 提供与浏览器交互的方法和接口Javascript的这三个组成部分在当前五大主流浏览器中都得到了不同程度的支持(IE.FireFox.Chrome.Safari.Opera).基本所有的浏览器都大体上支持ECMAScript第三版.但是对于

javascript基础——初识javascript

每一门语言的学习都是从HelloWorld开始的,我今天也遵循这个原则吧!先上一段代码,认识一下javascript <html><head><title>初识javascript</title><script language="javascript" type="text/javascript"><!--alert("Hello World Wide Web!")//-->

第一讲:初识JavaScript

1.0. 学习目标 –了解javascript组成部分 –认识javascript解析机制 –如何使用javascript 1.1.初识JavaScript •javascript是一种专为与网页交互儿设计的脚本语言.由三部分组成: –ECMAScript  (ECMA-262定义)  提供核心语言功能 –文档对象模型(DOM)提供访问和操作网页内容的方法和接口 –浏览器对象模型(BOM)提供与浏览器交互的方法和接口 •Javascript的这三个组成部分在当前五大主流浏览器中都得到了不同程度的

初识 javascript

第一次接触javascript是在网上看教程时,看到有关DOM操作的内容而认识的.刚开始一直误以为javascript是java的一个子集.因为也没有对javascript有很深的理解,所有就没有去深究javascript和java之间的关系.直到最近才发现,我的这个观点是完全错误的.javascript和java是完全不同的两门语言. 以下内容均源自网络: 1 首先,这两个家伙没有任何的血缘关系,java是是由Sun 公司于1995年5月推出的, 2 而javascript是于1995年由Ne

初识JavaScript Promises

JavaScript有很多槽点,嵌套回调怕是千夫所指. 很久之前,我一直使用async来处理JavaScript异步编程中的嵌套回调问题.当然我也大概的了解过一些其它旨在解决这些问题的类库,诸如EventProxy.Jscex.StepJS.thenjs. 当我第一次看到Promises规范的时候,我根本无法理解它所带来的好处.譬如每个初次学习Promises的人都见过如下的示例代码: //callbacks function callback(err, value){ if(err){ //

《前端之路》之 初识 JavaScript

01 初识 JavaScript 作为在码农圈混迹了 四五年的老码畜来说,学习一门新的语言,就仿佛是老司机开新车一样 轻车熟路. 为什么会这么快呢? 因为各种套路啊- 任何一种计算机语言的最开始都是和 数据类型 这个东西分不开,那么今天,我们就从 JavaScript 的数据类型开始 一.JavaScript 的数据类型 因为对于很多的 jser 的初学者而言,或者说说对于很多代码初学者而言,数据类型是让人疑惑的地方.那么一定要解除这个疑虑. JavaScript 一共有 八种数据类型.其中包含

JavaScript 对象

JavaScript 中的所有事物都是对象:字符串.数值.数组.函数... 此外,JavaScript 允许自定义对象. JavaScript 对象 JavaScript 提供多个内建对象,比如 String.Date.Array 等等. 对象只是带有属性和方法的特殊数据类型. 建 JavaScript 对象 通过 JavaScript,您能够定义并创建自己的对象. 创建新对象有两种不同的方法: 定义并创建对象的实例 使用函数来定义对象,然后创建新的对象实例