共计 4985 个字符,预计需要花费 13 分钟才能阅读完成。
系列文章
1、从数据类型讲原型原型链
内容回顾
在 JavaScript 中, 数据类型可以分为原始类型以及引用类型。
其中原始类型包括 string,number, boolean, null, undefined, symbol(ES6 新增,表示独一无二的值),这 6 种数据类型是按照值进行分配的,是存放在栈 (stack) 内存中的简单数据段,可以直接访问,数据大小确定,内存空间大小可以分配。
引用类型包括 function,object,array 等可以可以使用 new 创建的数据,又叫对象类型,他们是存放在堆 (heap) 内存中的数据,如 var a = {},变量 a 实际保存的是一个指针,这个指针指向对内存中的数据 {}。
对象类型包含但不限于 object、function、array、string、boolean、number、date、regexp。
这一篇文章则主要讲 object 类型的功能及用法。
一、开篇
在 js 中,function 类型的数据叫函数,又叫方法,array 类型的数据叫数组,而 object 类型的数据就叫对象,需要根据实际情况来区分一些文章中的对象与对象类型。
什么是对象?
对象就是封装一个事物的属性和功能的程序结构,是内存中保存多个属性和方法的一块存储空间。
什么是面向对象编程?
面向对象编程 (OOP) 是一种编程思想,是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。它的特征是封装、继承、多态,优点是易维护、易扩展、质量高、效率高。通俗的讲就是以对象为基础来进行软件开发的思想,举个例子,比如我们做一个雨滴落地的动画,那么可以用面向对象的方法把雨滴看成一个对象,这个对象中包含颜色、形状、大小、速度等属性,同时还包括了开始下落、下落过程、落地后等方法。
二、创建对象
1、直接量 (字面量) 创建
因为这种创建方法很直接,所以叫直接量,又叫字面量
var obj = {a:1}
2、构造函数创建
这一方法创建对象又叫工厂函数创建对象,顾名思义就是批量生产,使用这个方法我们需要用到关键字 new。运行环境内置了一个对象的构造函数—Object,我们直接使用就可以了
var obj = new Object({a:1});
上面的例子中,我们在 Object 中加了一个参数,这个参数是初始化的值(将参数赋值给新对象,不是克隆),这个参数的格式需要符合对象的格式要求,如果你传入了一个别的类型的参数,那么创建的就不是 object 类型的对象了(数据类型还是对象,但不是 object)
var a = new Object(‘a’); // String {“a”}
var b = new Object(‘{a:1}’); // String {“{a:1}”}
var c = new Object(1); // Number {1}
另外,如果没有参数需要传入,则 Object 后面的 () 可以省略,即
var a = new Object; //{}
除了环境自带的构造函数,我们还可以根据实际需求自定义构造函数
function Student(name,age){
this.name = name
this.age = age
}
var xm = new Student(‘xiaoM’,12); // Student {name: “xiaoM”, age: 12}
var xh = new Student(‘xiaoH’,14); // Student {name: “xiaoH”, age: 14}
3、Object.create 创建对象
我们知道,Object 是一个构造函数,而 js 中函数也是对象类型的数据,所以函数也是有属性的,而 create 就是其中的一个方法,该方法的作用是根据现有的对象新创建一个子对象。
var son=Object.create(父对象);
这句话做了 2 件事:
1. 创建空对象 son
2. 设置 son 的__proto__指向父对象
上面的解释其实是有一点瑕疵的,因为我们不但可以根据现有的对象来创建子对象,还可以根据 null 来创建子对象,我这么说是想强调下 null 不是一个对象,虽然 Object.prototype 继承自 null,但 null 也不是一个对象,你可能会说:
typeof null === “object” // true
Object.prototype.toString(null) === “[object Object]” // true
但它确实不是一个对象,这是 js 的历史遗留问题,详细可以看这里 typeof null 的前世今生
继续回到我们的文章上来,如果我们使用了 null 作为父对象来创建一个子对象,那么
子对象.__proto__ === null //true
和 Object.prototype 一个级别的!!!不过不同的是,Object.prototype 是运行环境封装的,里面天生有一些属性及方法,而用 null 创建的子对象就像一个新生儿一样,里面干干净净的,什么都没有。
使用 null 创建子对象的缺点就是我们不再能够使用 Object.prototype 自带的一些属性及 API,如果需要的话,你就得自己去实现了,好处就是这个子对象是一个干干净净的对象,里面的一些属性及方法可以按照自己的想法来,不用担心与原型链上属性重名,造成变量污染,多用于存储数据。
4、ES6 的 class 创建对象
class 关键字是 ES6 引入的概念,用于定义类(对象),用法如下:
// 定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)’;
}
}
可以看到里面有一个 constructor 方法,这就是构造方法,而 this 关键字则代表实例对象 Point。
Point 类除了构造方法,还定义了一个 toString 方法。注意,定义“类”的方法的时候,前面不需要加上 function 这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法。
class Point {
// …
}
typeof Point // “function”
Point === Point.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候,也是直接对类使用 new 命令,跟构造函数的用法完全一致。
var point = new Point(2,3);
point.toString() // “(2,3)”
这里执行 new Point(2,3)可以看作是执行了类 Point 里的 constructor 方法为 x,y 赋值,同时把 toString 函数挂到 Point.prototype 上去
构造函数的 prototype 属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的 prototype 属性上面。
更多 class 的使用说明及其他 es6 的特性看一看阮一峰老师的 ECMAScript 6 入门,讲的很详细,上面关于 class 的讲解也都摘抄自阮一峰老师的这本书里。
三、对象的遍历
遍历就是依次处理该数据结构的所有成员,JavaScript 中的遍历是针对表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了 Map 和 Set。其中对象的遍历主要是使用 for…in 结构,用法如下:
var a = {a:1,b:2}
for(var k in a){
console.log(k)
}
//a b
在 for…in 的使用中,我们需要小心的是,他不但可以遍历自己的私有属性,还会遍历其原型链上的公有属性。
私有属性:对象本身所具有的属性公有属性:包括私有属性在内的其原型链上的可访问到的属性
var a = {a:1,b:2}
a.__proto__ = {c:4}
for(var k in a){
console.log(k)
}
//a b c
我们可以通过 obj.hasOwnProperty 来判断一个属性是不是对象的自有属性
for(var k in a){
if(a.hasOwnProperty( k) ){
console.log(k)
}
}
//a b
以上,我们把所有可以遍历到的属性叫做可枚举属性,那么什么是可枚举属性和不可枚举属性呢?
可枚举属性是指那些内部“可枚举”标志(enumerable)设置为 true 的属性,对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true,对于通过 Object.defineProperty、Object.create 等定义的属性,该标识值默认为 false。可枚举的属性可以通过 for…in 循环进行遍历(除非该属性名是一个 Symbol)。相对的,不可枚举属性就是用 for…in 遍历不到的属性,js 中内置属性是遍历不到的。
四、Object/Object.prototype 常用 API 介绍
我们知道无论是 object、function、array 还是 string、boolean、number、date,他们都是对象类型的数据,我们在可以使用 console.dir()来查看一个数据的结构。这样我们就可以很全面的看到一个对象上有哪些属性了。
1、Object 构造函数的方法
Object 是 object 的构造函数,我们使用 console.dir(Object),可以看到:
Object.assign()通过复制一个或多个对象来创建一个新的对象。
Object.create()使用指定的原型对象和属性创建一个新对象。
Object.defineProperty()给对象添加一个属性并指定该属性的配置。
Object.defineProperties()给对象添加多个属性并分别指定它们的配置。
Object.entries()返回给定对象自身可枚举属性的 [key, value] 数组。
Object.freeze()冻结对象:其他代码不能删除或更改任何属性。
Object.getOwnPropertyDescriptor()返回对象指定的属性配置。
Object.getOwnPropertyNames()返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.getOwnPropertySymbols()返回一个数组,它包含了指定对象自身所有的符号属性。
Object.getPrototypeOf()返回指定对象的原型对象。
Object.is()比较两个值是否相同。所有 NaN 值都相等(这与 == 和 === 不同)。
Object.isExtensible()判断对象是否可扩展。
Object.isFrozen()判断对象是否已经冻结。
Object.isSealed()判断对象是否已经密封。
Object.keys()返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.preventExtensions()防止对象的任何扩展。
Object.seal()防止其他代码删除对象的属性。
Object.setPrototypeOf()设置对象的原型(即内部 [[Prototype]] 属性)。
Object.values()返回给定对象自身可枚举值的数组。
2、Object 实例和 Object 原型对象的方法
Object.prototype 本身是一个对象,所以我们使用 console.log(Object.prototype),就可以看到他的结构:
Object.prototype.hasOwnProperty()返回一个布尔值,表示某个对象是否含有指定的属性,而且此属性非原型链继承的。
Object.prototype.isPrototypeOf()返回一个布尔值,表示指定的对象是否在本对象的原型链中。
Object.prototype.propertyIsEnumerable()判断指定属性是否可枚举
Object.prototype.toLocaleString()直接调用 toString()方法。
Object.prototype.toString()返回对象的字符串表示。
Object.prototype.valueOf()返回指定对象的原始值。
由于 Object 以及 Object.prototype 的属性太多,这里就不详细阐述了,感兴趣的小伙伴可以到 MDN 观看,强烈建议把 Object、Object.prototype 的属性都过一遍。