JavaScript快速入门笔记(7):对象
本系列随笔是本人的学习笔记,初学阶段难免会有理解不当之处,错误之处恳请指正。转载请注明出处:https://www.cnblogs.com/itwhite/p/12230148.html。
简介
JavaScript 中没有类似于C++、Java等语言中所谓“类”的概念,JavaScript 是一门基于“对象”的语言(相对于C++、Java等基于“类”的语言来说):
- 基于“类”的语言:对象是类的实例,一个类可以从另一个(或多个)类中继承,比如:“人类”继承“动物类”的特性;
- 基于“对象”的语言:对象可以从其它对象直接继承,比如:Jack继承他父亲Smith的特性(脾气、性格、手艺等)。
正是基于原型而非类的缘故,JavaScript 弱化了对象的类型,而强化了对象的功能。
原理概述:
- 每一个函数对象都包含一个 prototype 属性(该属性值自身也是一个对象,其中默认包含一个 constructor 属性),通过构造函数创建的对象默认会继承该对象(该对象也称为“原型对象”)。基于此,我们可以通过为原型对象添加方法属性,从而让多个对象共享一组方法。
- 所有对象默认都继承自 Object.prototype(其中有一个 constructor 属性哦,例外:Object.create(null)创建一个不继承自任何对象的对象),所以所有对象都默认包含一个 constructor 属性(继承而来的嘛)。
对象
JavaScript 中,对象是一组属性(包括属性名、属性值以及属性的读写特性等)的集合。因为没有“类”的概念,因此创建对象时也无需指定类名。
JavaScript 中可以用以下三种方式创建对象:
- 直接使用字面值对象(看起来同JSON格式);
- 使用 new 运算符创建(new 后面跟一个构造函数调用,这种方式看起来很像C++、Java等语言中创建对象的方式);
- 使用 Object.create() 方法,其第一个参数指定对象的原型对象,第二个参数为可选参数,用以指定属性相关特性(同Object.defineProperties()的第二个参数)
示例:
var a = {}; // #1,字面值创建方式,创建一个空对象 var b = { x: 1, y: 2 }; // #2,字面值创建方式,创建一个非空对象 var c = new Object(); // #3,通过 new 运算符创建,创建一个空对象,同 #1 var d = new Date(); // #4,通过 new 运算符创建,创建一个日期对象 var e = Object.create(Object.prototype); // #5,创建一个空对象,同 #1 和 #3 var f = Object.create(b); // #6,创建一个继承自 b 的对象 f.x = 3; // 并不会影响父对象 b 中的属性值 console.log(b); // { x: 1, y: 2 } console.log(f); // { x: 3 },这里虽然没有输出 y 属性,但是通过 f.y 能访问 y 属性值 console.log(f.y); // 2
其中:
- #1 和 #2 是通过字面值方式创建的,创建时无法指定父对象;
- #3 和 #4 是通过构造函数创建的,对象的继承关系由构造函数的 prototype 属性决定(后面“继承”一节会详细描述);
- #5 和 #6 是通过 Object.create() 方法创建的,创建时同时可以指定父对象。
对象属性
JavaScript 中,对象可以看做是“属性”的集合(这里的“属性”不仅包括属性名,还包括属性值和一组特性)。
属性的特性可以表明该属性是否为只读属性、是否可枚举、是否可配置等。
查看一个属性的特性,可以使用 Object.getOwnPropertyDescriptor() 方法,例如(下面的示例查看对象 o 中的 x 属于性的特性):
var o = { x: 1, y: 2 }; console.log(Object.getOwnPropertyDescriptor(o, "x")); // {value: 1, writable: true, enumerable: true, configurable: true}
修改某个属性的特性,可以使用 Object.defineProperty() 方法,例如:
var o = { x: 1, y: 2 }; console.log(Object.getOwnPropertyDescriptor(o, "x")); // {value: 1, writable: true, enumerable: true, configurable: true} Object.defineProperty(o, 'x', { writable: false }); // 这里可以同时修改多个特性,也可以单独修改某个特性 console.log(Object.getOwnPropertyDescriptor(o, "x")); // {value: 1, writable: false, enumerable: true, configurable: true}
如果想要同时修改多个属性,请参考:Object.defineProperties()。
如果想要在创建对象的时候指定属性特性,可通过 Object.create() 方法的第二个参数进行设置。
设置属性的 getter 和 setter
属性值可以是一个简单的数据,这样的属性称为“数据属性”。
与之相对的,JavaScript 还支持为属性设置读写方法(getter和setter),这样的属性称为“存取器属性”。如果设置了 getter 表明该属性可读,如果设置了 setter 表明该属性可写,例如:
"use strict" var c = { x: 3, y: 4, get r() { // r 设置了 getter 和 setter,所以它是个可读、可写属性 return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); }, set r(v) { var ratio = v / this.r; this.x = this.x * ratio; this.y = this.y * ratio; }, get s() { return Math.PI * Math.pow(this.r, 2); } // s 只设置了 getter,所以它是个只读属性 }; console.log(c.x, c.y, c.r, c.s); // 3 4 5 78.53981633974483 c.r = 10; console.log(c.x, c.y, c.r, c.s); // 6 8 10 314.1592653589793 c.s = 20; // Uncaught TypeError: Cannot set property s of #<Object> which has only a getter,只有在严格模式下,这里才会报错
注意:
- “数据属性”包含 value、writable、enumerable、configurable 四个特性;
- 而“存取器属性”因为 其值 和 writable 分别根据是否设置了 getter 和 setter 决定可否访问,因此它没有 value、writable 这两个特性,示例:
console.log(Object.getOwnPropertyDescriptor(c, "x")); // {value: 3, writable: true, enumerable: true, configurable: true} console.log(Object.getOwnPropertyDescriptor(c, "r")); // {enumerable: true, configurable: true, get: ƒ, set: ƒ} console.log(Object.getOwnPropertyDescriptor(c, "s")); // {set: undefined, enumerable: true, configurable: true, get: ƒ}
使用 in 和 hasOwnProperty() 检测对象属性(是否存在)
in 可以检测对象的自有属性和继承属性,而 hasOwnProperty() 仅可用于检测对象的自有属性,例如:
var p = { x: 1, y: 2 }; var c = Object.create(p); c.z = 3; console.log("x" in c); // true console.log("z" in c); // true console.log(c.hasOwnProperty("x")); // false console.log(c.hasOwnProperty("z")); // true
构造函数
示例:
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.show = function() { // 所有通过 Person() 构造函数创建的对象都会新建一个 Function 属性 console.log(this.name, this.age, this.gender); } } var jack = new Person("Jack", 23, "male"); // Person 明明是个函数,不过在某些文章里也被当做了一个“类” var linda = new Person("Linda", 19, "female"); // 由此说: jack 和 linda 都是 Person 的对象(或者说实例) jack.show(); linda.show(); alert(jack.show != linda.show); // true,但实际上,我们可能希望多个对象来共享同一个 show() 方法
上面的示例中,jack 和 linda 同属一类对象,但是它们却没能共享同一个方法,通过函数的 prototype 属性我们可以实现对象属性的共享,例如:
Person.prototype.show = function() { console.log(this.name, this.age, this.gender); } function Person(name, age, gender) { // 通过 Person 创建的对象会继承 Person.prototype 对象的属性哦 this.name = name; this.age = age; this.gender = gender; } var jack = new Person("Jack", 23, "male"); var linda = new Person("Linda", 19, "female"); jack.show(); linda.show(); alert(jack.show === linda.show); // true,共享同一个 show() 方法
伪类:模拟其它语言中的类和继承
模拟类的数据成员和函数成员(方法):
- 普通态数据成员:通过普通对象属性模拟
- 静态数据成员:通过构造函数的属性模拟
- 普通函数成员:通过对象方法模拟
- 静态函数成员:通过构造函数的方法属性模拟
例如:
Person.count = 0; // 模拟静态数据成员 function Person(name) { // 模拟“类”,Person 可以当做一个“类” this.name = name; // 模拟普通数据成员 Person.count++; } Person.prototype.sayHi = function() { // 模拟普通函数成员 console.log("Hi " + this.name + "!"); } Person.showCount = function() { // 模拟静态函数成员 console.log(Person.count); } var jack = new Person("Jack"); var bob = new Person("Bob"); var linda = new Person("Linda"); jack.sayHi(); // 输出:Hi Jack! Person.showCount(); // 输出:3
模拟类的继承关系(例如:Dog继承Animal),请参考:阮一峰文章 Javascript面向对象编程(二):构造函数的继承
参考:
《JavaScript权威指南(第六版)》第6章、第9章
《JavaScript高级程序设计(第3版)》第6章
《JavaScript语言精粹》第3章、第5章
阮一峰:Javascript面向对象编程(二):构造函数的继承
完。