说明

  前面三篇博客介绍了js中基本的知识点,包括变量类型及其转换、表达式、运算符等小知识点,这篇博客主要讲的是对象。如果你学过java等语言,你也许在下文的阅读中发现在js中的对象与java中的对象存在一定的区别。

相关术语说明

一、对象中成员变量的”属性特性“

  ①可写:表明该属性可以设置其值。

  ②可枚举:表明是否可以通过for/in循环返回该属性。

  ③可配置:表明是否可以删除或者修改该属性。

注:关于for/in等语句的说明,第一篇博文就说过,本系列教程是建立在读者对基本的编程有一定了解的基础上,所以一些基本的if、for等语句的语法在本系列教程中并不涉及,这样不仅能节约我的时间,也能节约大家阅读的时间。但是对于一些其他语言中少见的语法,在本系列教程中还是会谈到,这里虽然没有谈到for/in语句,但是已给出站外链接,供大家参考。总之鄙人认为有必要让读者了解的东西,要么会出现的自己的博文中,要么会给出站外链接。同样为了节约大家的时间,这里给出的链接一般是内容较好且行文精简的教程。

二、每个对象拥有的“对象特性”

  ①原型(prototype):指向另一个对象,js里面的继承就靠这个prototype实现。与一般的静态编程语言不同,js中的继承出现在对象上,而不是类上。

  ②类(class):标识对象类型的字符串。

  ③扩展标记(extensible flag):指明了该对象是否可以添加新属性。在js中对象的成员属性是可以动态添加的,而不像静态的面向对象的编程语言在编译前就确定了。

三、对象的分类

  ①内置对象:由JavaScript标准提供的对象或类,例如数组、正则等。

  ②宿主对象:由浏览器提供的对象,比如进行DOM操作时常用的document对象,还有进行BOM操作常用的window对象等。

  ③自定义对象:我们自己写的对象。

注:这里对一些属于的解释可能较为抽象,如果实在不明白也没关系,下面会用例子进行实例的讲解。

对象的创建

  js中对象的创建有三种方式,可以通过对象直接量、关键字new和Object.create()函数来创建。

原型

  在介绍对象的创建之前,有必要谈一下对象的原型对象。原型对象原本是属于一个函数的,但我们经常看到对象的原型对象这种说法。一般地,如果某一个函数是构造函数,由这个构造函数创建出来的对象的原型对象就是这个构造函数的原型对象。

  原型对象的出现实现了类似与继承的功能。一般来说每个对象的原型对象是不可访问的,但是在某些浏览器(比如chrome)中提供了对原型对象的访问,其属性名为__proto__。这么说起来你可能觉得比较抽象,难以理解,结合下面的图再讲你可能会更清楚。

  这张图中,Person是一个构造函数,p1和p2都是由这个构造函数创建出来的对象,它们三都指向同一个原型对象(Person prototype由解释器创建)。这里就相当于p1和p2继承于这个原型对象,它俩都共享原型对象的name和age属性,如果这个原型对象还有原型对象,它俩也可以共享,这样就能构造出一条原型链,处于原型链低端的对象可以访问原型链高处的成员属性。上图中p1有自己的name属性,所以它不会再向上访问原型对象的name。

  原型是js中理解对象编程的一个很重要的知识点,如果你觉得还是比较模糊,或者说想更清楚地了解有关原型的内容,强烈推荐你阅读这篇博客。另外上面这张图也是从这篇博客中复制过来的。

对象直接量

  这种创建对象的方式在前几篇的博客中也出现过,就是直接用一个大括号将属性/值括起来,其中属性名可以是任意的字符串,如果属性名是正常的标识符,则可以不用引号括起来,否则必须用引号括起来。属性名和其值之间用冒号分割开。不同的属性之间用逗号分开,具体看下面的例子。

 var empty = {};    //一个没有任何属性的对象。
var point = {x:0, y:0}; //拥有两个属性的对象。
var point2 = {x:point.x, y:point.y};
var book = {
"main title":"javaScript",
'sub-title':"the definitive guide",
"for":"all audiences",
author:{
firstname:"David",
lastname:"Flanagan"
}
};

  这里需要说明的就是point2和book引用的对象,其中point2引用的对象的属性值是一个表达式,js允许属性值是任意有值的表达式。在book中的main title和sub-title不是标识符,所以必须用引号括起来。for虽然是标识符但同时也是保留字,在ECMAScript3中必须用引号括起来,但是ECMAScript5中可以不括起来。值得注意的是对象直接量是一个表达式,这种表达式如果放在循环里面,每次被执行都会创建一个新的对象,且对象的属性值也有可能不同。

关键字new

  new运算符可以后跟一个构造函数来创建一个新对象。如果后面的构造函数没有参数传入,可以省略其后面的括号。

 var o = new Object();    //没有参数传入,等价于new Object;
var a = new Array(); //和空数组[]一样。
var d = new Date();
var r = new RegExp("js");

  除了内置的构造函数,我们也可以自己定义构造函数来初始化新对象,这些内容将在后面的类和模块的博客中进行详细介绍。

Object.create()

  学过java的同学可能知道所有的类都有父类,除了Object类自己。在js中也差不多,所有的对象都有原型对象,除了null。利用Object.create这个静态函数创建对象可以手动指定该对象所继承的原型对象以及其属性。看下面的例子:

 var o1 = Object.create({x:1, y:2});    //o1继承了x和y属性
var o2 = Object.create(null); //不继承任何属性
var o3 = Object.create(Object.prototype); //等价于{}或new Object()

  这里要注意的是o2这个对象,它的原型对象为null,所以它没有继承到任何属性,这和o3这个对象不同,o3这个对象虽然为{}空对象,但是它还是从它的原型对象那里继承了一些属性的,比如toString()方法等。上面说到的Object.create()还可以用第二个参数给新对象设置属性,这将在本博客的后面讲解。

属性的查询和设置

  js中对对象属性的访问有两种方式,分别利用点(.)和方括号([])进行。其中点运算符只支持对象属性访问,且属性名必须是标识符,而方括号不仅能访问对象的属性,还可以访问数组中的元素,且方括号里面的属性名可以是任意的字符串或者是能转换为字符串的表达式。

 var author = book.author;    //等价于book['author']
var title = book['main title']; //只能用方括号访问。因为'main title'不是标识符,标识符不能含有空格。

  我们平时写的函数名、变量名等就是标识符,关于js中标识符的要求基本上和其它语言差不多,具体请参考本系列教程的第一篇中的”js的词法“部分。

  上面两个例子都是对对象中属性的查询访问,如果需要设置其属性只需将其放在赋值号的左边即可。如果是查询访问,当属性不存(也不存在于原型链中)在会返回undefined,如果是设置其属性,当属性不存在,会在当前对象中创建一个同名属性并设置其值。

  由于在属性访问时不存在的属性会返回undefined,所以这里涉及到一个错误处理的问题,见下面的代码:

 //存在问题的代码
var len = book.subtitle.length; //冗余但易懂的处理方法
var len = undefined;
if(book){
if(book.subtitle)
len = book.subtitle.length;
}
} //精简的常用方法
var len = book && book.subtitle && book.subtitle.length;

删除属性

  js中有一个delete运算符,用于删除对象的属性,但是它只能删除自有属性,不能删除继承而来的属性,要删除原型链上的属性必须找到那个原型。值得注意的是delete对属性的删除只是解除其指向这个属性值的引用,而不会去操作其属性的属性,因为js中所有内存的释放都是垃圾回收器完成的。见下面的代码:

 var a={p:{x:1}};
var b=a.p;
delete a.p; //解除了a.p指向{x:1}这个属性值的引用
b.x //1:虽然a.p对{x:1}的引用没了,但是b对{x:1}的引用还在。

  一般情况下使用delete删除属性都将返回一个true,即使是以下这些情况也将返回true:

 var o={x:1};
delete o.x; //删除x返回true
delete o.x; //什么也没做(x已经不存在了),返回true
delete o.toString; //什么也没做(toString是继承来的),返回true。
delete 1; //无意义,返回true

  上面说的一般情况,不过存在以下情况的delete语句会返回false。(注意以下情况是在非严格模式下,在严格模式下删除不可配置属性会报错。)

 delete Object.prototype;    //不能删除,属性不可配置
var x=1; //声明一个全局变量
delete this.x; //全局变量的是不可配置的
function f() {} //声明一个全局函数
delete this.f; //也不能删除全局函数

检测属性

  属性的检测有三种方法,①通过in运算符、②通过hasOwnProperty()方法、③通过propertyIsEnumerable()方法。

in运算符

  in运算符左侧写属性名(字符串),右侧写对象名,如果这个对象含有或者继承了这个属性,则返回true。

 var o = {x:1}
"x" in o; //true
"y" in o; //false
"toString" in o //true

hasOwnProperty()方法

  只有这个属性是自有属性才返回true,对于继承而来的属性返回false。

 var o = {x:1}
o.hasOwnProperty("x"); //true
o.hasOwnProperty("y"); //false
o.hasOwnProperty("toString"); //false

propertyIsEnumerable()方法

  与hasOwnProperty()方法相比更严格,要求该属性不仅是自有属性,还必须是可枚举的。(下面将讲解什么是可枚举)

其它

  除了以上三种方法用于判断某一对象是否包含某一属性外,还有一种方法如下:

 var o={x:1}
o.x !== undefined; //true
o.y !== undefined; //false
o.toString !== undefined //true

  不过这种方式存在问题,当这个x的值本身就是undefined时,上面o.x !=== undefined这条语句就会返回false。

枚举属性

  所谓可枚举的属性就是使用for/in循环可以遍历到的属性。一般来说我们自己写的属性(包括函数)都是可枚举的,除非用下文提到的一个方法将它转换为不可枚举。注意,可枚举属性不仅可以是自有属性,还可以是继承而来的属性。

  在ECMAScript5的标准中提供了两个用来获取可枚举属性名的函数,第一个是Object.keys()返回对象中所有的自有可枚举属性名组成的组数。第二个是Object.getOwnPropertyNames()返回对象中所有的自有属性名组成的数组。

  先不管definedProperties(下文将会讲),总之上面先创建了一个对象拥有一个x属性值为1可枚举,和一个y属性值为hello不可枚举。然后执行Object.keys()方法和Object.getOwnPropertyNames()方法分别得到了上面这两个数组。

属性setter和getter

  如果你对JavaBean或者说POJO有一定的了解,那么学这两个东西也许还没开始就能猜出十有八九。通常来说对象里面的属性都是键值对(属性名:属性值)的形式存在的,但是js允许某些属性不以这种形式存在,取而代之的是setter和getter。或许这样说很难解释,看下面这个例子你也许会很快了解。

 var p = {
//记录笛卡尔坐标系的位置
x:1.0,
y:1.0, //r是由x和y转为极坐标后的极径
//r的setter和getter
get r() {
return Math.sqrt(this.x*this.x + this.y*this.y);
},
set r(_new) {
var _old = Math.sqrt(this.x*this.x + this.y*this.y);
var ratio = _new/_old;
this.x *= ratio;
this.y *= ratio;
}, //θ为极角
get theta() {
return Math.atan2(this.y, this.x);
}
};

  上面这个对象我们在外部看来它拥有四个属性,事实上你用Object.keys()函数也能得到["x", "y", "r", "theta"]这个值。然而对象内部真实地却只有两个变量组成的属性x和y。其中属性r和属性theta都是用setter或(和)getter函数模拟出来的。由于theta只有getter,所以这是一个只读属性。

  注意这个例子中setter和getter的书写格式,get或set替代原本用来声明函数的关键字function,并且get和set与函数体之间没有通常键值对用于分割的冒号,这个函数名就是我们模拟出来的对象的属性。

属性的特性

  对象的属性有四个特性:①值、②可写、③可枚举、④可配置。对于由getter和setter模拟出来的属性也有四个特性:①读(get)、②写(set)、③可枚举、④可配置。

获取属性描述符

在ECMAScript5中可以利用Object.getOwnPropertyDescriptor()来获取某一个特定属性的属性描述符,看下面的例子:

  这里使用Object.getOwnPropertyDescriptor()方法对上面定义的“笛卡尔坐标系与极坐标系互转的对象p”进行了属性描述符的获取(在chrome中)。可以看出默认情况下我们定义的属性是可写、可枚举和可配置的。而模拟出来的属性默认也都是可枚举和可配置的。如果它不存在getter或setter,那么在相应的属性位置显示为undefined。如果这个对象(p)不包含该属性(nothing),那么直接返回undefined。

  注意Object.getOwnPropertyDescriptor()该方法只返回自有属性,也就是说要想获得继承属性的特性需要遍历原型链。

设置属性特性

  上面通过Object.getOwnPropertyDescriptor()这个方法可以获取某个对象的属性描述符,如果我们想设置某个对象的某个属性的特性,可以使用Object.defineProperty()这个方法。看下面的例子:

 var a = Object.defineProperty({}, "x", {
value:1,
writable:true,
enumerable:true,
configurable:true});
//上面这段代码等价于 var a = {x:1};

对象的三个属性

  每个对象(null除外)都有与之相关联的三个属性,分别是原型、类和可扩展性。

原型属性

  每一个对象(null除外)都有原型,它在js中是如此的重要,以至于只要你想学好js的对象和函数部分这是你一道跨不过的坎,上文曾稍微谈了一下原型对象,还强烈建议读者阅读这篇博客,如果你对原型对象还不是特别了解,还是建议你去看一下这篇博客,或者其它的讲原型对象的博客,这真的很重要。

  在ECMAScript5中,你可以通过Object.getPrototypeOf()这个方法来获取某个对象的原型对象。也可以使用每个对象的isPrototypeOf()方法来检测继承关系。

 var a = {x:1};
var b = Object.create(a);
b.y = 2;
Object.getPrototypeOf(b); //a
a.isPrototypeOf(b) //true

类属性

  类属性是一个字符串,在ECMAScript3和ECMAScript5中并没有提供对这个字符串的修改。对这个字符串的获取就是我们常说的toString()方法。但是某些时候toString()这个函数经常被重写,为了能调用正确版本的toString()方法必须间接调用Function.call()方法。Function()调用方式与Function.call()的调用方式的区别就是函数执行Function这个函数执行时的this指向不同。关于Function.call和Function.apply这两个方法的具体使用将在后面的博客中讲解。如果你有兴趣也可以先参考一下这篇博客

var a=[1,2];    //定义一个数组
a.toString(); //得到字符串"1,2"
Object.prototype.toString.call(a); //得到字符串"[object Array]"

可扩展性

  js中的对象的可扩展性直接决定了这个对象是否能添加新属性,如果这个对象不具有可扩展性,那么他将和java等静态语言中的对象一样,无法动态添加属性了。在对象属性的扩展性这方面,ECMAScript5提供了三组常用的函数:

 Object.isExtensible(a);    //判断对象a是否可扩展。
Object.preventExtensions(a); //设置a为不可扩展
Object.isSealed(b); //判断对象b是否被封闭
Object.seal(b); //封闭对象b
Object.isFrozen(c); //判断对象c是否被冻结
Object.freeze(c); //冻结对象c

  注意上面的设置不可扩展、封闭和冻结等动作都是不可逆的,其中由不可扩展到冻结对属性的限制越来越严。不可扩展只是不允许添加新的属性,封闭不仅不可添加新的属性还将所有的属性都设置为了不可配置,也就说已有的属性也不让删除,而冻结就更严格了,在封闭的基础上还将所有属性设置为了只读(模拟属性中的setter除外)。

序列化对象

  对象序列化是指将对象的状态转化为字符串,也可以将字符串还原为对象。ECMAScript5提供了JSON.stringify()和JSON.parse()来序列化和还原JavaScript对象。JSON的全称是JavaScript Object Notation的缩写,意思是”JavaScript对象表示法“。JSON的语法是JavaScript语法的子集,它并不能表示JavaScript中的所有值。由于序列化这个功能不太常用,所以这里就不展开讲解了,有兴趣的同学可以参考这篇博客

对象方法

  上文已经谈到过js中的所有对象都从Object.prototype中继承了属性,且这些属性主要是方法。我们前面已经涉及过hasOwnProperty()、propertyIsEnumerable()和isPrototypeOf()这三个方法。静态方法方面涉及了Object.create()和Object.getPrototypeOf()等方法。

toString()方法

  一般将对象转换为字符串时js会自动调用这个方法。比如:"now is " + new Date();这句话中js会自动调用日期这个对象的toString()方法将其转换为字符串再与前面的字符串相连接。该方法经常被重写,在后面的博客中将介绍怎么重写这个方法。

valueOf()方法

  这个方法和上面的toString()方法差不多,一般也是被js自动调用在期待数字的时候,比如:23==new String("23");这个比较中js会自动调用new String("23")这个对象的valueOf方法,将其转换为字符串再进行判等比较。

JavaScript基础入门教程(四)的更多相关文章

  1. JavaScript基础入门教程(二)

    说明 前一篇博客介绍了js以及一些关于js基本类型的简单知识,本篇博客将详细介绍js的基础类型,捎带介绍对象类型,更详细的对象类型的说明将后续再讲. js中类型的说明 js中的类型分为基本类型和对象类 ...

  2. JavaScript基础入门教程(六)

    说明 在看这篇博文之前还是希望读者阅读本系列前几篇文章,还有就是该系列需要读者拥有其它语言的编程基础,一些基本的知识点,比如什么是形参和实参将不再赘述.这篇博文主要讲函数. 函数的定义 在js种支持函 ...

  3. JavaScript基础入门教程(五)

    说明 本系列博客的第一篇已经说明了,要求阅读者需要具有其它语言的编程基础,所以关于组数的基础部分本篇博客将不再赘述,主要讲js中数组的特性. 创建数组 数组的创建主要有两种方法,一种是数组直接量,还有 ...

  4. JavaScript基础入门教程(一)

    本系列教程的说明 本教程说白了可以说是我自己学习JavaScript的笔记,主要内容参考自<JavaScript权威指南>,部分内容可能来自互联网,本系列教程假设学者之前学过c或者其它的编 ...

  5. JavaScript基础入门教程(三)

    说明 前面的两篇博客介绍了js中的基本知识中的变量类型.标识符等.这篇博客主要谈表达式以及运算符. 原始表达式 原始表达式就是表达式中最小的,不能在分割的表达式,一般指变量.常数直接量.关键字(tru ...

  6. 无废话ExtJs 入门教程四[表单:FormPanel]

    无废话ExtJs 入门教程四[表单:FormPanel] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在窗体里加了个表单.如下所示代码区的第28行位置,items:form. ...

  7. React Native基础&入门教程:初步使用Flexbox布局

    在上篇中,笔者分享了部分安装并调试React Native应用过程里的一点经验,如果还没有看过的同学请点击<React Native基础&入门教程:调试React Native应用的一小 ...

  8. ECMAScript 6.0基础入门教程

    ECMAScript 6.0基础入门教程 转:https://blog.csdn.net/hexinyu_1022/article/details/80778727 https://blog.csdn ...

  9. JavaScript基础入门06

    目录 JavaScript 基础入门06 Math 对象 Math对象的静态属性 Math对象的静态方法 指定范围的随机数 返回随机字符 三角函数 Date对象 基础知识 日期对象具体API 构造函数 ...

随机推荐

  1. 2015多校第7场 HDU 5378 Leader in Tree Land 概率DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5378 题意:一棵n个节点的树.对其节点进行标号(1~n).求恰好存在k个节点的标号是其节点所在子树的最 ...

  2. Load balancer does not have available server for client:xxx

    今天在搭建一个springcloud项目在搭建以zuul为网关的时候,项目抛了一个异常, com.netflix.zuul.exception.ZuulException: Forwarding er ...

  3. ubuntu 10.04打开错误

    打开ubuntu时,出现的错误如下: Invalid configuration file. File "E:\Ubuntu12.04.vmwarevm\Ubuntu12.04.vmx&qu ...

  4. Linux系统编程——进程间通信(System V IPC 对象)

    基本查看命令 ipcs  -m查看共享内存        ipcs -s查看信号量        ipcs -q查看消息队列 ipcrm  -m  id 删除共享内存   -M+key值 ipcrm ...

  5. python库-urllib

    urllib库提供了一系列操作url的功能,是python处理爬虫的入门级工具,网上的学习资料也很多.我做爬虫是一开始就用了Scrapy框架,并不是一步步从urllib开始的,反而是在后来解决一些小问 ...

  6. 15:django 缓存架构

    动态网站的一个基本权衡就是他们是动态的,每次一个用户请求一个页面,web服务器进行各种各样的计算-从数据库查询到模板渲染到业务逻辑-从而生成站点访问者看到的页面.从处理开销的角度来看,相比标准的从文件 ...

  7. 属性名、变量名与 内部关键字 重名 加&

    procedure TForm4.btn3Click(Sender: TObject); var MyQj: TQJson; MyPrinter: TPrinter; begin MyQj := TQ ...

  8. bzoj 1485 卡特兰数 + 分解因子

    思路:打表可以看出是卡特兰数,但是模数不一定是素数,所以需要分解一下因数. #include<bits/stdc++.h> #define LL long long #define fi ...

  9. 【剑指offer】面试题 9. 用两个栈实现队列

    面试题 9. 用两个栈实现队列 题目描述 题目:用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. 解答过程 import java.util.Stack; publ ...

  10. VS Code开发技巧集锦

    2016 年 9 月 23-24 日,由 CSDN 和创新工场联合主办的“MDCC 2016 移动开发者大会? 中国”(Mobile Developer Conference China)将在北京? ...