创建对象
1、字面量
let obj = {
property_i : value_i,
}
2、Object构造函数
let obj = new Object();
大部分情况下,我们都是通过构造函数创建对象。常用的对象有:new String()|new Number()|new Boolean()|new Date()|new RegExp()| new Function()
以上两种方法的缺点:创建具有同样接口的多个对象需要重复多个代码。
3、工厂模式
两步走:
1、创建工厂
2、运行工厂
// 1、创建工厂
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name)
}
return o;
}
// 2、运行工厂
let person = createPerson("张三", 18, "程序员");
console.dir(person)
运行结果:
优点:解决创建多个类似对象的问题
缺点:没有解决对象标识问题(即新创建的对象是Object类型,没有具体化)
4、构造函数模式
两步走:
1、通过创建一个构造函数来定义对象的类型,首字母大写是非常普通而且很恰当的惯用法。
2、通过new
创建对象实例。
注意事项:
1.使用this关键字
2.首字母大写
// 第一步
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.driver = function () {
console.log("driver")
} // 可以将此函数定义在构造函数的外部,但是不规范【全局作用域被搞乱了】
// return this; // 会自动return this
}
// 第二步
var car1 = new Car("Nissan", "300ZX", 1992);
var car2 = new Car("Nissan1", "300ZX1", 2000);
console.dir(car1)
console.log(car1.driver == car2.driver)
运行结果:
使用new操作符执行的操作
1、在内存中创建一个新对象。
2、在这个新对象内部的[[Prototype]]
特性被赋值为构造函数的prototype
属性。
3、构造函数的内部的this
被赋值为这个新对象(this指向新对象)
4、执行构造函数内部的代码(给新对象添加属性)
5、如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
构造函数 VS 函数
构造函数与普通函数唯一的区别就是调用方式不同。
构造函数的优点:解决对象标识问题
构造函数的缺点:构造函数中定义的方法会在每个实例上都创建一遍,各个实例会定义不同的Function实例。
5、原型模式
每个函数都会创建一个prototype
属性,这个属性是一个对象,包含应该由特定类型的实例共享的属性和方法。
function Person() {}
Person.prototype.name = "张三";
Person.prototype.age = 30;
Person.prototype.job = "程序员";
Person.prototype.sayName = function() {
console.log(this.name)
}
let person1 = new Person();
let person2 = new Person();
person1.sayName() // 张三
console.log(person1.sayName == person2.sayName) // true
原型性质
- 原型的写法
1、单个写
这样写只是在原型上新增属性和方法,已有的属性和方法不会受到影响。
function Person() {}
Person.prototype.name = "张三";
Person.prototype.sayName = function() {
console.log(this.name)
}
2、合并写
给构造函数的原型重新赋值时,原来的原型将被替换,其中最重要的属性是constructor,constructor指向构造函数,而且还是不可迭代的。[[Enumerable]]的值为false。
function Person() {}
Person.prototype = {
name: "张三",
sayName: function() {
console.log(this.name);
}
} // 对象重置,原有的属性均消失
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
原型的动态性
任何时候对原型所做的修改会在实例上反映出来。注意不是给原型重新赋值。原生对象原型
不要修改原生对象的原型,因为可能造成误会或命名冲突。推荐的做法是创建一个自定义的类型,继承原生对象。
原型模式的缺点:弱化了构造函数传递初始化参数的能力;原型模式的共享特性的弊端,所有实例都会影响共享特性。
构造函数 VS 原型 VS 实例
构造函数:类似与类,与函数类似,只是通过new使用
原型:所有的JavaScript对象至少继承于一个对象,被继承的对象被称为原型。JS中的每个函数都有一个prototype原型属性,这个属性也是一个对象,用途是包括能够由特定类型的全部实例共享的属性和方法。
实例:new 构造函数之后得到的对象。
- 只要创建一个函数,就会按照特定的规则为这个函数创建一个
prototype
属性,指向原型对象,原型是一个实例对象。 - 默认情况下,所有原型对象自动获取一个名为
constructor
属性,指回与之关联的构造函数,即循环引用。Person.prototype.constructor = Person
- 实例化对象中的
__proto__
属性指向原型,构造函数中的prototype
属性指向原型,即person.__proto__ = Person.prototype
- 原型中的
__proto__
指向父原型的prototype
,即Person.prototype.__proto__ == Object.prototype
- 正常的原型链都会终止与
Object
的原型,即Person.prototype.__proto__.constructor == Object
- Object原型的原型是null
Object.prototype.__proto__ == null; // true
- instanceof 操作符检查实例的原型链中是否包含指定构造函数的原型。
- 使用原型的
isPrototypeOf()
方法确定是否属于这个原型。 - 获取一个实例的原型
Object.getPrototypeOf(实例)
- 设置一个实例的原型
Object.setPrototypeOf(实例, 原型)
;此方法会严重影响代码性能,可以使用Object.create()
方法创建一个指定原型的对象。
通过Object.create()
方法创建时,我们可以为创建的对象选定一个原型对象,而不用定义构造函数。
- 参数:
proto:新创建对象的原型对象;
propertiesObject:为新创建的对象添加指定的属性值和对应的属性描述符。 - 返回值:一个新对象,带着指定的原型对象和属性。
- 注意事项:
Object.create(object, propertiesObject)中propertiesObject是null或非原始包装对象,则会抛出TyperError异常。
// 使用对象初始化器创建一个对象
var Animal = {
type: "Invertebrates", // 属性默认值
displayType : function() { // 用于显示type属性的方法
console.log(this.type);
}
}
// 通过Object.create()创建对象animal
var animal1 = Object.create(Animal);
animal1.displayType(); // Output:Invertebrates
// 创建一种新的动物——Fishes
var fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Output:Fishes
原型层级
-
访问属性和方法的流程
通过对象访问属性的时候,根据属性名称从对象自身开始搜索,如果自身有此属性,则返回对应的值;如果自身没有此属性,则到最近原型上搜索,直到原型为null或搜索到此属性。
- 属性的覆盖
实例是有与原型同名的属性,会将原型上同名的属性给覆盖。
属性的位置
hasOwnProperty() |
in | |
---|---|---|
作用 | 判断自身是否有此属性 | 访问自身或原型上的属性 |
使用方法 | in操作符:判断对象是否有此属性[原型和自身] for..in:遍历对象上可迭代的属性[原型和自身] |
属性枚举
以下方法受到enumerable
和原型链
的影响。
方法 | for...in循环 | Object.keys(o) | Object.getOwnPropertyNames(o) | Object.getOwnPropertySymbols(o) |
---|---|---|---|---|
含义 | 返回对象及原型链上可枚举的属性 | 返回自身的所有可枚举属性 | 返回自身所有属性 | 返回自身所有的Symbols属性 |
是否可枚举 | 可 | 可 | 不关心 | Symbol |
是否访问原型链 | 访问 | 不访问 | 不访问 | 不访问 |
是否访问Symbol | 不访问 | 不访问 | 不访问 | 访问 |
- Object.getOwnPropertyNames()
作用:返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。 - Object.getOwnPropertySymbols()
作用:返回一个给定对象自身的所有 Symbol 属性的数组。
6、使用Object.assign方法
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
Object.assig(target, ...sources)
参数:
target:目标对象
sources:源对象返回值:
目标对象过程:
Object.assign方法只会拷贝源对象自身并可枚举的属性到目标对象。该方法会使用源对象的getter和setter方法。如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面源对象的属性。注意事项
1、Object.assign()`是一个浅拷贝。
2、继承属性和不可枚举属性是不能拷贝的
3、Object.assgin()方法没有创建原型。-
使用场景
- 合并对象:多个对象进行合并。
- 复制对象:对象的浅拷贝。
对象的迭代
枚举一个对象的属性
以下方法受到enumerable
和原型链
的影响。
方法 | for...in循环 | Object.keys(o) | Object.getOwnPropertyNames(o) | Object.getOwnPropertySymbols(o) |
---|---|---|---|---|
含义 | 返回对象及原型链上可枚举的属性 | 返回自身的所有可枚举属性 | 返回自身所有属性 | 返回自身所有的Symbols属性 |
是否可枚举 | 可 | 可 | 不关心 | Symbol |
是否访问原型链 | 访问 | 不访问 | 不访问 | 不访问 |
是否访问Symbol | 不访问 | 不访问 | 不访问 | 访问 |
for...in
作用:以任意顺序遍历一个对象的除Symbol以外的可枚举属性【包含原型链上可枚举的属性】
Object.keys()
作用:返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
对象迭代
ES2017新增两个静态方法Object.values()、Object.entries(),用于将对象内容转换为序列化的、可迭代的的格式。
Object.values(Object)
作用:返回一个给定对象自身的所有可枚举属性值的数组。
Object.entries(Object)
作用:返回一个给定对象自身可枚举属性的键值对数组
对象的继承
所有的JavScript对象至少继承于一个对象,被继承的对象被称为原型。
每个对象可以通过构造函数的prototype
属性找到原型 或 每个实例对象有一个私有属性__proto__
指向原型
获取对象原型
方法一:Object.getPrototypeOf()
方法返回指定对象的原型。
方法二:实例化对象.__proto__
Object.getPrototypeOf(object)
- 参数
obj:要返回其原型的对象 - 返回值
给定对象的原型,如果没有继承属性,则返回null
设置或修改对象原型
方法一:Object.create()
创建对象的时候指定原型.
方法二:Object.prototype.__proto__
方法三:Objcet.setPrototypeOf()
方法四:Reflect.setPrototypeOf()
Object.create(proto, propertiesObject)
:
- 参数:
proto:新创建对象的原型对象
propertiesObject:可选 - 返回值
一个新对象,带着指定的原型对象的属性。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
"use strict"
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}
// 子类续承父类,以下两句话一般同时存在
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.dir(rect) // rect --> Rectangle---->Shape---->Object
</script>
</body>
</html>
Objcet.setPrototypeOf(obj, prototype)
- 参数:
obj:设置其原型对象
prototype:对象的新原型(对象或null) - 过程:
如果对象的原型被修改成不可扩展(通过Object.isExtensible()
)的,则抛出TypeError异常。
如果prototype新原型参数不是对象或努力了,则什么也不做。
否则,该方法修改对象的原型。
Reflect.setPrototypeOf(target, prototype)
- 参数:
target:设置其原型对象
prototype:对象的新原型(对象或null) - 返回值:
返回一个boolean类型表明是否原型已经设置成功。 - 过程:
如果参数target不是Object,或者prototype既不是对象,也不是null,则抛出TypeError异常。
Object原型与属性相关的方法
方法 | 作用 | 参数 | 返回值 |
---|---|---|---|
obj.hasOwnProperty() | 判断属性是否在某个对象自身上 {不含原型链} | prop:要检测的属性 | true:有 false:无 |
prop in obj | 判断属性是否在某个对象上 {含原型链} | prop:要检测的属性 | true:有 false:无 |
prototypeObj.isPrototypeOf(obj) | 测试一个对象是否在另一个对象的原型链上 | object:在该对象的原型链上搜寻 | true:在 false:不 |
instanceof操作符 | 检测构造函数的prototype属性是否会出现在某个实例对象的原型链上 | 操作符,不是函数 | true:在 false:不在 |
obj.propertyIsEnumerable(prop) | 判断属性名是否可枚举 | prop需要测试的属性 | true:枚举 false:非枚举 |