很多同学甚至在相当长的时间里,都忽略了JavaScript也可以进行面向对象编程这个事实。一方面是因为,在入门阶段我们所实现的各种页面交互功能,都非常顺理成章地使用过程式程序设计解决了,我们只需要写一些方法,然后将事件绑定在页面中的DOM节点上便可以完成。尤其像我这类一开始C++这类语言没好好学,第一门主力语言就是JavaScript的同学来说,过程化程序设计的思维似乎更加根深蒂固。另一方面,就算是对于Java、C++等语言的程序员来说,JavaScript的面向对象也是一个异类:JavaScript中没有class的概念(在ES5及之前版本中没有,ES6会单独介绍),其基于prototype的继承模式也与传统面向对象语言不同,而JavaScript的弱类型特性更会令这里面的很多人抓狂。当然,在熟悉了之后,这种灵活性也会带来很多好处。总之,封装、继承、多态、聚合这些面向对象的基本特性JavaScript都有其自己的实现方式,这些知识的学习是从入门级JS程序员进阶的必经之路。
JavaScript面向对象(2)——谈谈函数(函数、对象、闭包)
JavaScript面向对象(3)——原型与基于构造函数的继承模式(原型链)
JavaScript面向对象(4)——最佳继承模式(深拷贝、多重继承、构造器借用、组合寄生式继承)
很久没有更新博客了,这段时间里方向发生了很大的变化,现在在准备出国读研的事情,技术学习少了很多很多。这段时间一直想把方向的转变过程中所做的思考、面对的问题发出来~ 工作室送老刚过去不久,每一级同学都有着各自面临着的问题,或许我的思考过程可以带来启示? 当然,假如日后证明我选错了,这些思虑就可以当作经验教训来复盘了哈哈哈哈。
毫无疑问的,JavaScript是一种面向对象的编程语言,它的面向对象的实现机制又是那么的特殊。最近想把这一整块的知识整理一下,先从JavaScript的一些语言特性说起,谈谈变量,谈谈对象,谈谈函数,谈谈JavaScript面向对象的实现。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
要来谈谈JavaScript中的对象,我们要从JavaScript的数据类型来说起。在JavaScript中,除了字符串、数字、true、false、null、undefined之外,所有的值都是对象,对象在JavaScript中可是一等公民。对象自身,则是一系列属性的无序集合,也就是键值对的集合,这样的数据结构在其他地方被叫做“散列”、“字典”、“关联数组”等~ 其中键是字符串,而值则可以是JS中的任意数据类型。
JS对象是引用类型的,是可变的,也是动态的。我们将对象赋值给别的变量,并不会创建一个新的对象副本,只是赋了一个对象的引用;而对象可变,在语句修改了对象的内容之后,所有引用了这个对象的变量都会变化。而JS对象的动态性可以让我们对属性进行操作,新增、删除等等都可以随时进行。这个特性在之后的面向对象实现中很重要,在传统面向对象语言中我们必须预先设计好类,将方法和行为提前设计好;在JS中,我们可以在使用对象的时候直接对它进行扩充,就是利用了它的动态性。在从几个方面讨论对象之前,关于对象与属性,还有一些需要知道的概念:
对象特性:除了属性之外,对象拥有的一些特性,其中包括:
对象的原型:指向另一个对象,相当于父对象。
对象的类:一个标识对象类型的字符串。
对象的扩展标记:指明了是否可以向该对象添加新属性。
对象分类:按照对象的来源,共有三类JavaScript对象:
内置对象:由ECMAScript规范所定义的对象和类,包括数组、函数、日期、正则表达式、错误等,输入JavaScript语言核心的范畴。
宿主对象:由JavaScript解释器所嵌入的宿主环境所定义的。比如文档对象就是由浏览器所定义的。(还记得刚接触NodeJs第一次使用repl的时候,直接敲了个alert~ alert是浏览器所定义的Window对象提供的,在NodeJs环境中并不存在这个对象。)
自定义对象:由运行中的JavaScript语句所创建的对象
属性特性:除了名字和值之外,每个属性还有几个与之相关的特性值:可写、可枚举、可配置。它们决定了该属性是否可写、是否可以通过for/in循环返回、是否何以删除或修改。
属性分类:自有属性和继承属性。 自由属性是直接在对象中定义的属性,而继承属性是在对象的原型对象中定义的属性。
一、创建对象的三种方法
1、通过对象直接量创建:大括号内是一个对象,键-值用冒号分隔,键-值间用逗号分隔。嗯,没错,就是JavaScript对象表示法(JSON)了。
var object = {
"name" : "zhuwq",
"age" : 21,
"callHim" : function(){
//call me
},
"girlFriend" : {
"name" : undefined,
"age" : undefined
}
}
2、通过new关键字创建对象:与其他面向对象语言一样,可以使用new关键字调用构造函数初始化一个对象。JavaScript内置对象都内置构造函数:
var object = new Object(),
array = new Array(),
date = new Date(),
regExp = new RegExp();
使用自定义构造函数来初始化新对象更加重要,会在之后详述。
3、使用Object.create()方法:对于这种对象创建方法,原型的概念非常重要:
Object.create()方法是ES5开始才提供的,它可以通过将原型对象(原型会在之后单独讨论)作为参数传入的方式创建一个新对象:
var obj = { "name" : "obj"};
var o1 = Object.create(obj);
obj.a; // "obj"
var o2 = Object.create(null); // 创建一个没有原型的新对象
var o3 = Object.create(Object.prorotype); // 创建一个普通的空对象 与var o3 = {}; 以及var o3 = new Object();相同
二、属性的操作
var obj = { "name" : "obj"};
obj.name; //"obj"
obj["name"]; //"obj"
对于点来说,右边必须是一个以属性命名的简单标识符;而对方括号来说,方括号中必须是一个返回字符串或者可以转换为字符串的表达式。这就使得在某些情况下,通过方括号这种类似于关联数组的访问方式可以完成更加灵活的工作:
var addr = {};
for(var i = 0;i < 5;i++){
addr["name"+i] = i;
}
addr; //{ name0: 0, name1: 1, name2: 2, name3: 3, name4: 4 }
在很多无法预知属性名的场景下,只有使用数组写法才能完成程序。
var object = {
"name" : "zhuwq",
"age" : 21,
"girlFriend" : {
"name" : undefined,
"age" : undefined
}
}
var message = '';
for(pro in object){
message += pro + ":" + object[pro] + ",";
}
message; //name:zhuwq,age:21,girlFriend:[object Object],
2、属性的添加:在访问属性的时候,直接向一个不存在的属性赋值就会直接添加该属性:
var obj ;= {};
obj.a = "a";
obj["b"] = "b";
obj; // { "a": "a", "b": "b" }
3、属性的删除:直接使用delete运算符即可
var obj = {"a":"a","b":"b"};
delete obj.a; //true
obj; // {"b" : "b"}
4、存取器属性:我们通常所使用的对象属性叫做数据属性,而从ES5开始,存取器属性被添加了进来。它使得对象的属性值可以被一两个方法所替代:当程序设置一个存取器属性的值时调用setter方法,当程序查询一个存取器属性的值时调用getter方法。同样的,可以只添加getter方法或只添加setter方法使这个属性成为一个只读或只写属性。定义存取器方法时,不使用functon关键字而是使用get或set关键字定义与属性名同名的方法,函数体和属性名之间无需冒号分隔,但一个函数体与下一项之间依然需要逗号分隔:
var rectangle = {
width: 1.0,
height: 2.0,
name: undefined,
get area(){
return this.width * this.height;
},
set nameValue(newName){
this.name = newName;
},
get nameValue(){
return "This object's name is " + this.name;
}
}
rectangle.area; // 2
rectangle.nameValue = "zhuwq";
rectangle.nameValue; // This object's name is zhuwq
三、关于属性的特性
在文章的最开始已经介绍过,JavaScript对象中的属性除了其名字和值以外,还有三个特性,分别标识了这些属性是否可写、可枚举、可配置。在ES3的时代,这些特性是无法修改的,并且所有通过程序创建的属性都是可写可枚举可配置的。从ES5开始,JavaScript包含了配置属性特性的API。数据属性的特性包括值、可写性、可枚举性、可配置性;而存取器属性的特性包括读取性、写入性、可枚举性、可配置性。//构建对象
var object1 = {
x : 1
}
var object2 = Object.create(object1);
object2.y = 2;
//检测方法1: 使用in运算符 不区分自由属性和继承属性
"x" in object2; // true
"y" in object2; // true
"z" in object2; // false
//检测方法2: 使用hasOwnProperty()方法 检测是否为自有属性
object2.hasOwnProperty("x"); // false 继承自object1
object2.hasOwnProperty("y"); // true
object2.hasOwnProperty("z"); // false 无该属性
//检测方法3: 使用propertyIsEnumerable()方法 检测是否为可枚举自有属性
object2.propertyIsEnumerable("x"); //false 继承自object1
object2.propertyIsEnumerable("y"); //true
object2.propertyIsEnumerable("z"); //false 无该属性
object2.propertyIsEnumerable("toString"); //false 不可枚举
还有一种情况需要注意区分:在对象没有该属性或者该属性值为undefined时,访问该属性都会返回undefined。这时候需要灵活使用in运算符和!==运算符按需区分这两种情况。
var rectangle = {
width: 1.0,
set nameValue(newName){
this.name = newName;
},
get nameValue(){
return "This object's name is " + this.name;
}
}
Object.getOwnPropertyDescriptor(rectangle,"width"); // { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(rectangle,"nameValue");// { get: [Function: get nameValue], set: [Function: set nameValue], enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(rectangle,"aaa"); // undefined
Object.getOwnPropertyDescriptor(rectangle,"toString"); // undefined
3、属性特性的创建和修改:有Object.defineProperty()和Object.defineProperties()两个方法,分别用来创建或修改单个属性特性和多个属性特性:
//通过将对象、属性、属性描述符对象传入Object.defineProperty()对象来配置单个属性的属性特性
var obj = {};
Object.defineProperty(obj,"b",{
value: 1,
writable: true,
enumerable: true,
configurable: true
});
obj.b; //1
Object.defineProperty(obj,"b",{
value: 1,
writable: false,
enumerable: true,
configurable: true
});
obj.b = 2;
obj.b; //1 这里仅展示了可写性的配置
//通过将对象、属性及其描述符的集合对象传入Object.defineProperties()对象来配置多个属性的属性特性
var obj = {};
Object.defineProperties(obj,{
b: {value: 1,writable: false, enumerable: true, configurable: true},
c: {get: function(){ return "get c"}, set: undefined,enumerable: true, configurable: true}
});
obj.b = 2;
obj.b; //1
obj.c; // get c
当然了,这两个操作的成功与否,与对象的可扩展性、该属性的可配置性、可写性都有关联。操作失败是则会抛出类型错误异常。
四、对象的三个特性
function classOf(obj){
if (obj === null) return "Null";
if (obj === undefined) return "undefined";
return Object.prototype.toString.call(obj).slice(8,-1);
}
3、可扩展性:对象的可扩展性决定了是否可以给对象添加新属性。在JavaScript中所有的内置对象和自定义对象都是可扩展的。有一些方法可以将对象转为不可扩展的,需要注意的是,一旦转为不可扩展对象,就无法再转变回来了。进行将对象封闭,可以避免外界的干扰。有三种方法可以从不同程度将对象进行锁定,这里按程度递增介绍:
//通过Object.esExtensible()方法查询对象是否可扩展
var obj = {};
Object.esExtensible(obj); // true
//通过Object.preventExtensions()方法将对象转为不可扩展
Object.preventExtensions(obj);
Object.esExtensible(obj); // false
var obj = {a:1};
//通过Object.seal()方法将对象转为不可扩展并将所有自有属性设为不可配置
//可以使用Object.isSealed()方法判断是否已经封闭
Object.isSealed(obj); // false
Object.seal(obj);
Object.isSealed(obj); // true
Object.esExtensible(obj); //false
Object.defineProperty(obj,"b",{
value: 1,
writable: false,
enumerable: true,
configurable: true
}); // TypeError: Cannot define property:b, object is not extensible.
var obj = {a:1};
//通过Object.freeze()方法将对象转为不可扩展、将所有自有属性设为不可配置、将所有数据属性设为只读
//可以使用Object.isFrozen()方法判断是否已经封闭
Object.isFrozen(obj); // false
Object.freeze(obj);
Object.isFrozen(obj); // true
Object.esExtensible(obj); //false
Object.defineProperty(obj,"b",{
value: 1,
writable: false,
enumerable: true,
configurable: true
}); // TypeError: Cannot define property:b, object is not extensible.
b.a = 2;
b.a; // 1