第八章 对象、类与面向对象编程
对象:
1、理解对象
2、如何创建对象,添加对象属性,对象的增强写法
3、对象创建的过程
4、理解原型,理解继承
一、理解对象
1、对象即集合。
2、每 ***(对象实例 )***都有属性和方法:
constructor:创建当前对象的函数即当前对象的类型,如let obj=new Object(),即当前对象的函数是Object
hasOwnProperty(propertyName):判断**(当前对象实例)**上是否存在给定的属性,如:
obj.hasOwnProperty('name')。
isPrototypeOf(): 判断当前对象是否是另一个(对象的原型),对象原型即prototype
propertyIsEnumerble: 用于判断给定的属性是否可以使用(for in )语句枚举
toLocaleString(): 返回对象字符串表示
toString(): 返回对象的字符串表示
valueOf() **返回(对象对应的字符串)**,数值或布尔表示**。**
** 在ECMAScript 中Object是所有对象的基类,所以任何对象都有这些属性和方法**
二、创建对象,对象属性,合并对象,对象增强写法
2.1创建对象
字面量:
let person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() { console.log(this.name); } };
添加属性:
. 点符号
【】 中括号
obj.name='';
obj['age']=12;
构造函数:let person = new Object(); 或let person=new Object
person.name = "Nicholas";
person.age = 29; person.job ="Software Engineer";
person.sayName = function() { console.log(this.name); };
添加属性:
. 点符号
【】 中括号
objnew.year=12;
objnew['school']='school'
2.1.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;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
2.1.2构造函数模式
//函数名 首字母大写 Person
//定义自定义构造函数可以确保实例被标识为特定类型
//函数声明的方式,也可以是函数表达式 let Person=function(){}
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { console.log(this.name); }; }
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName();
// Nicholas person2.sayName();
// Greg
与构造函数相比:
1、没有显示创建对象
2、属性和方法直接赋值给this
3、没有return
4、构造函数函数名大写
new创建实例:构造函数会执行如下操作
1、在内存中创建一个新的对象
2、这个**(新对象内部的[[Prototype]]特性)**被赋值为**(构造函数的prototype属性)**
3、**构造函数内部的this**被赋值为这个新对象(即this指向新对象)
4、执行构造函数内部的代码(给新对象添加属性)
5、如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象
回看理解对象:2、每 ***(对象实例 )***都有属性和方法:
constructor:创建当前对象的函数即当前对象的类型,如let obj=new Object(),即当前对象的函数是Object
则上述:
person1.constructor==person2.constructor //true
判断类型: 可以使用constructor
instanceof 操作符是确定对象类型更可靠的方式
注:
1、每个对象都是object的实例,上述person1,person2既是object实例,又是Person实例,
person1 instanceof Object //true
person1 instanceof Person //true
person2 instanceof Object //true
person2 instanceof Person //true
//为什么是Person实例?
//是因为(所有自定义对象)都继承 自 Object
构造函数与普通函数:
1、唯一的区别就是调用的方式不同
2、任何函数使用new 操作符进行调用就是构造函数
3、不使用new操作符调用的就是普通函数
调用函数没有明确设置this情况(没有作为对象的方法调用,没有使用call()/apply()调用)?
// 作为构造函数
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName();// "Nicholas"
// 作为函数调用
Person("Greg", 27, "Doctor");
// 添加到 window 对象
window.sayName();// "Greg"
// 在另一个对象的作用域中调用
let o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); // "Kristen"
1、this会指向Global对象,即挂在window对象上。
2、通过call()/apply()
构造函数的问题:
1、每个实例上的方法会在每个实例上都创建一遍
2、如上例子,**方法功能是一致的,但是确是不同的Function实例。所有都创建一遍就是多余的操作**
实际上的逻辑如下:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
//每创建一个实例,就会创建一个函数实例
this.sayName = new Function("console.log(this.name)");
// 逻辑等价
}
//console.log(person1.sayName == person2.sayName); // false
- 使用构造函数解决构造函数的问题! ( 既然每个实例都会创建一个函数实例, 那么就把方法提到外面来,相当于一个全局的方法(实例可以共享全局的方法),解决重复定义的问题)
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName();
// Nicholas
person2.sayName();
// Greg
//本身一个实例的方法,被提到全局中,
//1、 扰乱了全局
2、本属于特性实例的方法,如果方法很多,那么全局就会有很多的方法
3、不好维护
4、致自定义类型引用的代码不能很好地聚集一起
2.1.3原型模式
工厂函数:没法识别对象的类型
构造函数:可以识别对象的类型, 但每个实例都会创建一个函数实例,虽然能将方法提到全局,实例进行共享,但是会有一定的问题 迎来原型模式
每个函数,只要创建一个函数都会**(创建一个prototype属性)**,此属性为一个对象,
此对象 包含应该由特定 **引用类型的** **实例共享的方法和属性**
此对象 就是通过调用 ** 构造函数创建的对象 ** 的原型(实例的原型)
-
理解原型(prototype)
只要创建一个函数,就会按照特性的规则为这个(函数创建一个prototype属性), 这个属性指向的是一个**原型对象**。 **原型对象** 自动获取一个名为 **constructor 的属性**,指回**与之关联的构造函数**, 如上所示:Person.prototype.constructor==Person,根据构造函数而异,会给原型对象添加其他属性和方法。 自定义构造函数时,原型对象会默认获得constructor属性,**其他的方法都继承自Object** 每次调用构造函数创建一个实例,这个实例的[[prototype]]指向这个构造函数的原型对象。 脚本中没有访问这个[[prototype]]特性的标准方式,但是firefox,safari,chrome会在每个对象上暴露——proto_属性, 可以通过这个属性可以访问到原型,但是不会用这个去查早原型。为什么? 实例与构造函数原型之间有直接的联系,但实例与构造函数之 间没有。
/** * 构造函数可以是函数表达式 * 也可以是函数声明,因此以下两种形式都可以:
* function Person() {} *
let Person = function() {} */
function Person() {}
/** * 声明之后,构造函数就有了一个 * 与之关联的原型对象:*/
console.log(typeof Person.prototype);
console.log(Person.prototype);
/*
{
constructor: f Person(),
__proto__: Object
}
*/
/**
* 如前所述,构造函数有一个 prototype 属性
* 引用其原型对象,而这个原型对象也有一个
* constructor 属性,引用这个构造函数 * 换句话说,两者循环引用: */
console.log(Person.prototype.constructor === Person); // true
/**
* 正常的原型链都会终止于 Object 的原型对象
* Object 原型的原型是 null */
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Person.prototype.__proto__.constructor === Object); // true
console.log(Person.prototype.__proto__.__proto__ === null); // true
===================================================
prototype既然指向的是一个对象,这个对象的_proto_指向的是Object ,则这个对象也是一个实例,所在有constructor
//console.log(Person.prototype.__proto__);
// {
// constructor: f Object(),
// toString: ...
// hasOwnProperty: ...
// isPrototypeOf: ...
// ...
// }
===========================================================
let person1 = new Person(),
person2 = new Person();
/**
* 构造函数、原型对象和实例
* 是 3 个完全不同的对象:
*/
console.log(person1 !== Person); // true
console.log(person1 !== Person.prototype); // true
console.log(Person.prototype !== Person); // true
/**
* 实例通过__proto__链接到原型对象,
* 它实际上指向隐藏特性[[Prototype]] *
* 构造函数通过 prototype 属性链接到原型对象 *
* 实例与构造函数没有直接联系,与原型对象有直接联系 */
console.log(person1.__proto__ === Person.prototype); // true
conosle.log(person1.__proto__.constructor === Person); // true
/**
* 同一个构造函数创建的两个实例
* 共享同一个原型对象: */
console.log(person1.__proto__ === person2.__proto__); // true
/**
* instanceof 检查实例的原型链中
* 是否包含指定构造函数的原型: */
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
console.log(Person.prototype instanceof Object); // true
- 方法 isPrototypeof() 是否是谁的原型 Person.prototype.isPrototypeOf(person1)
Object.getPrototypeOf() 获取某实例的原型 Object.getPrototypeOf(person1)
setPrototypeOf() Object.setPrototypeOf(),可以向实例的私有特性[[Prototype]]写入一个新值。
Object.setPrototypeOf()可能会严重影响代码性能,?具体的还没有弄明白
不过可以使用Object.create()来创建一个新的对象,===同时为其指定原型=====
let biped = { numLegs: 2 };
let person = Object.create(biped);
person.name = 'Matt';
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true
-
原型层级
1、层级搜索,对象实例没有的,就去原型上找;实例有的话就不会去查护原型了,直接访问实例上的值。 2、===虽然通过实例能访问原型对象上的值,但是不能通过实例重写原型上的值,如不想使用原型上的值, 可以再对象实例上创建值进行覆盖=== 3、确定是实例上的值,就写在实例上,给定默认值,可以减少去原型上的查找。 -
hasOwnProperty(),判断某属性是不是在原型上,此方法是继承Object,回看上述理解对象
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() { console.log(this.name); };
let person1 = new Person();
let person2 = new Person();
console.log(person1.hasOwnProperty("name")); // false
- 原型和 in操作符 单独使用 in ,层级查询
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() { console.log(this.name); };
let person1 = new Person();
let person2 = new Person();
console.log("name" in person1); // true
for -in 可以通过对象访问 且 可以被枚举的属性,**包含 实例属性 和 原型属性 **,
遮蔽圆形中不可枚举([[Enmerable]]特性被设置为false)属性的实例属性也会在for-in返回(理解:也即是实力上有一个值的名字跟原型上的不了枚举的一样,把他覆盖了,实例上的这个属性是会返回的,返回的并不是原型上的这个值) Object.keys() ======获取对象上所有可枚举的===(实例属性)
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() { console.log(this.name); };
let keys = Object.keys(Person.prototype);
console.log(keys); // "name,age,job,sayName"
let p1 = new Person();
p1.name = "Rob";
p1.age = 31;
let p1keys = Object.keys(p1);
console.log(p1keys); // "[name,age]"
-
属性枚举顺序?
-
对象迭代?
-
其他原型的写法
上面的原型例子中,我们是以Person.prototype.name='nikole'这种方式,定义一个属性和方法,为了减少 代码冗余,直接将所有添加在原型上的属性和方法,写在一个字面量对象里,我们经常这样做,但是会 ** 有问 题**
但是会 ** 有问题**
原来的默认的Person.prototype={constructor:Person,...arg}被{}重写了,{}的constructor指向的是Object
function Person() {}
Person.prototype = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() { console.log(this.name); } };
et friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor == Person); // false
console.log(friend.constructor == Object); // true
下意识的就是解决问题
function Person() {}
Person.prototype = {
constructor:Person
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() { console.log(this.name); } };
//上述有提到,constructor是根据默认构造函数生成的,并且是不枚举的,而这里是通过添加属性的方式,默认的
[[Enumerable]]为true,
但是可以通过
Object.definedProperty(Person.prototype,'constructore',{value:Person,enumerable:false})
恢复
- 原型的动态性(重点) 从原型上搜索值得过程是动态的,即使实例在修改原型之前已经存在,任何时候对原型对象做的修改也会在实力上反映出来
function Person() {};
let friend = new Person();
Person.prototype.sayHi = function() { console.log("hi"); };
friend.sayHi(); // "hi",没问题!
//虽然 friend 实例是在添加方法之前创建的,但它仍然可以访问这个
//方法。之所以会这样,主要原因是实例与原型之间松散的联系
//在调用 friend.sayHi()时,首先会从 这个实例中搜索名为 sayHi 的属性。在没有找到的情况下,运行时会继续搜索原型对象
//因为实例和 原型之间的链接就是简单的指针,而不是保存的副本,所以会在原型上找到 sayHi 属性并返回这个属 性保存的函数
虽然随时能给原型添加属性和方法,并能够立即反映在所有对象实例上,但这跟重写整个原型是两 回事。,实例的[[Prototype]]指针是在调用构造函数时自动赋值的,这个指针即使把原型修改为不同 的对象也不会变。===重写整个原型会切断最初原型与构造函数的联系===,===但实例引用的仍然是最初的原型===。 记住,===实例只有指向原型的指针===
function Person() {} ;
let friend = new Person();
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() { console.log(this.name); } };
friend.sayName(); // 错误
//这是因为 firend 指向的原型还是最初的原型,而这个原型上并没有 sayName 属性。
-
原生对象原型
原型模式不仅仅体现在自定义类型上,还因为它也是实现所有原生引用类型的模式。所有的原生引用类型的构造 函数(包括Object,Array,String等)都在原型上定义了实例方法, 但是不推荐,可能造成误会,也有可能造成命名冲突,意外的重写了原生的方法 -
原型的问题
主要问题是原型的共享特性,当共享引用值得属性得时候
function Person() {}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
friends: ["Shelby", "Court"],
}
let person1 = new Person();
let person2 = new Person();
person1.friends.push("Van");
console.log(person1.friends); // "Shelby,Court,Van"
console.log(person2.friends); // "Shelby,Court,Van"
console.log(person1.friends === person2.friends); // true
2.1.4继承
-
原型链(ecmascript主要的继承方式) 思想是通过原型继承多个引用类型的属性和方法
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承
SuperType SubType.prototype = new SuperType();
//new SuperType()这个实例的——proto_ 指向的是SuperType的原型,所以SuperType的原型指向的是这个实例的原
//型,即继承了它的原型,重写了它的原型
SubType.prototype.getSubValue = function () {
return this.subproperty;
}
let instance=new SubType();
console.log(instance.getSuperValue()); // true
1、默认原型,
默认情况下,所有的引用类型都继承自Object,任何函数的默认原型都是一个Object的实例,也就是这个原型对象
是Object的实例,而这个实例是指向Object.prototype的,所以自定义类型能够继承toString(),valueOf()在
内的所有默认方法的原因。
-
盗用构造函数
原型本身你存在一定的问题,所以通过原型链去继承也会存在当继承引用类型的问题,而构造函数能解决这个问题 **基本思路很简单:**在子类 构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用 apply()和 call()方法以新创建的对象为上下文执行构造函数。
function SuperType() {
this.colors = ["red", "blue", "green"]; }
function SubType() {
// 继承 SuperType
SuperType.call(this); }
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green"
问题: 盗用构造函数的主要缺点,也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不 能重用。此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模 式。由于存在这些问题,盗用构造函数基本上也不能单独使用
- 组合继承(即使用原生继承(继承方法),又使用构造函数继承(继承属性)) 组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继 承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"]; }
SuperType.prototype.sayName = function() {
console.log(this.name); };
function SubType(name, age){ /
/ 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
console.log(this.age);
};
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27
- 原型式继承?
- 寄生式继承?
- 寄生式组合继承?
2.1.5类后面还要再吸收一遍
-
定义类
块级作用域 没有变量提升
class Person{}
const animal=class{}
-
类的构成
可以包含 构造函数,实例方法,获取函数,设置函数,静态类方法,非必须,可以是空类,默认情况下是严格模式。 写在构造函数里面的是实例的属性 写在类块里,则是实例共享的方法 类自己的 static
// 空类定义,有效
class Foo {}
// 有构造函数的类,有效
class Bar { constructor() {} }
// 有获取函数的类,有效
class Baz { get myBaz() {} }
// 有静态方法的类,有效
class Qux { static myQux() {} }
- 继承
1、继承基础
使用extends关键字,就可以继承任何拥有[[constructor]]和原型的对象。
class Vehicle{} class Eus Extends Vehicle{};
2、构造函数、HomeObject和super()
可以通过super关键字引用它们的原型,这个关键字只能在派生类中使用,仅限于类构造函数,实例方法,静态方
法内部,在类构造函数中使用 super 可以调用父类构造函数。
```js
class Vehicle {
constructor() { this.hasEngine = true; } }
class Bus extends Vehicle {
constructor() {
// 不要在调用 super()之前引用 this,否则会抛出 ReferenceError super();
// 相当于 super.constructor()
console.log(this instanceof Vehicle); // true
console.log(this); // Bus { hasEngine: true } } } n
ew Bus(); 在静态方法中可以通过 super 调用继承的类上定义的静态方法:
class Vehicle {
static identify() { console.log('vehicle'); } }
class Bus extends Vehicle {
static identify() { super.identify(); } }
Bus.identify(); // vehicle
**在使用 super 时要注意几个问题 ** 1、super 只能在派生类构造函数和静态方法中使用 2、不能单独引用 super 关键字,要么用它调用构造函数,要么用它引用静态方法 3、调用 super()会调用父类构造函数,并将返回的实例赋值给 this 4、super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入 5、如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的 参数 6、在类构造函数中,不能在调用 super()之前引用 this 7、如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回 一个对象。
- 类构造函数
2.2 对象属性
ECMA-262 使用一些内部特性来描述属性的特征。这些特性是由为 JavaScript 实现引擎的规范定义 的。
因此,开发者不能在 JavaScript 中直接访问这些特性。为了将某个特性标识为内部特性,规范会用 两
中括号把特性的名称括起来,比如[[Enumerable]]
2.2.1 数据属性
数据属性包含(保存数据的位置),进行增删改查,数据属性有4个特性来描述增删改查的行为
1、[[configurable]]:
是否能通过delete删除并重新定义
是否可以修改特性
是否能改为访问器属性
(默认)所有**(直接定义在对象上)**的属性这个特性都是true
** (一个属性被定义为不可配置之后,就不能再变可配置)**
2、[[Enumerable]]:表示属性是否可以通过for-in循环返回
默认(所有直接定义在对象上的属性)这个特性都是true
3、[[writable]]:表示属性的值是否被修改
默认(所有直接定义在对象上的属性)这个特性都是true
4、[[value]]:属性实际的值,(保存数据的位置),
默认这个特性的默认值是undefined;
** for example**
let person={name:"nicholas"}
//confogurable:true,Enumerble:true,writable:true,value:"nicholas"
默认的属性特性是否能修改呢?(能)如下:
** writable**
let person={};
Object.definedProperty( person,
'name',
{
writable:false,
value:"nicholas"
});
//writable:false表示不被修改
// 即person.name='kittly'不会被修改
** configurable**
Object.definedProperty(person,'name',{
configurable:false,//先为false
value:"nicholas"
})
//configurable:false
Object.definedProperty(person,'name',{
configurable:true,//后为true报错
value:"nicholas"
})
/*
1、false属性不能被删除,变成不可配置后就不能再便会可配置
2、把configurable设置为false之后就会被受限制。
3、**(在调用object.definedProperty()时)**,configurable,enumerable和writable的值如果不
指定,默认都是fasle.
*/
2.2.2 访问器属性
**访问器属性不包含(数据值)**。包含getter函数和setter函数,这两个函数不是必需的,
在读取访问性属性时,会调用获取函数(getter函数),这个函数责任就是返回一个有效的值。
写入时,会调用,会调用设置函数(setter函数)并传入新值,
1、[[configurable]]:
表示属性是否可以通过 delete 删除并重新定义.
是否可以修改它的特 性
是否可以把它改为数据属性
默认情况下,**(所有直接定义在对象上的属性)**的这个特性 都是 true。
2、[[Enumerable]]:
表示属性是否可以通过 for-in 循环返回。
所有**(直接定义在对 象上的属性)**的这个特性都是 true
3、[[get]]:获取函数,在读取属性时调用。默认值为 undefined
4、[[set]]:设置函数,在写入属性时调用。默认值为 undefined。
访问性属性不能直接定义,必须使用Object.definedProperty()
let book={
year_:2022,
edition:1
}
Object.definedProperty(book,'year',{
get(){
return this.year_;
},
set(newValue){
if(newValue>2017){
this.year_=newValue;
this.editoin+=newValue-2017
}
}
})
book.year=2018
//即会调用setter函数
-
访问性属性getter和setter都必须要设置嘛?不是
获取属性和设置属性不一定都要定义, **只定义(获取函数则意味着只读)**,尝试修改会被忽略,严格模式下会报错, **只有一个设置函数**,属性不能读取,严格模式下报错
2.2.3 定义多个属性(Object.definedProperties)
Obeject.definedProperties()方法,接受两个参数,要给添加或者修改的对象,和另一个描述符对象,
//for Example
let book={};
Object.definedProperties(book,{
year_:{value:2017},
edition:{value:1},
year:{
get(){return this.year_},
set(newValue){
this.year_ = newValue; this.edition += newValue - 2017;
}
},
})
** 数据属性的特性都是false**
结合上面的 ** 默认(所有直接定义在对象上的属性)这个特性都是true**
可是这里不是直接定义的
和上述的:3、**(在调用object.definedProperty()时)**,configurable,enumerable和
writable的值如果不指定,默认都是fasle.
2.2.4 读取属性的特性(Object.getOwnPropertyDescriptor())
Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符,接收两个参数(属性所在的对
象,要取得其描述符的属性名),返回一个对象
数据属性包含 configurable,enumerable,writable,vlaue
访问性属性包含 configurable,enumerable,get,set
ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法。这个方法实际上 会在每个自有属性上调用 Object.getOwnPropertyDescriptor()并在一个新对象中返回它们
let book = {};
Object.defineProperties(book,
{ year_: { value: 2017 },
edition: { value: 1 },
year: {
get: function() { return this.year_; },
set: function(newValue){
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
} } } });
console.log(Object.getOwnPropertyDescriptors(book));
// {
// edition: {
// configurable: false,
// enumerable: false,
// value: 1,
// writable: false
// },
// year: {
// configurable: false,
// enumerable: false,
// get: f(),
// set: f(newValue),
// },
// year_: {
// configurable: false,
// enumerable: false,
// value: 2017,
// writable: false
// }
// }
2.1.5合并对象
1、es6 Object.assign()方法,接受一个目标对象和(一个或多个源对象作为参数),然后将**(每个源对象
中可枚举(Object.propertyIsEnumerbale()返回true))** 和自有(Object.hasOwnProperty()
返回true)属性赋值到目标对象,
** 以字符串和符号为键** 的属性会被复制。
对每个符合条件的属性,这个方法会使用原对象上的[[get]]取得属性的值,然后使用目标对象上的[[set]]
设置属性的值
let dest, src, result; /** * 简单复制 */
dest = {};
src = { id: 'src' };
result = Object.assign(dest, src);
// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result);
// true console.log(dest !== src);
// true console.log(result);
// { id: src } console.log(dest);
// { id: src }
/** * 多个源对象 */
dest = {};
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });
console.log(result);
// { a: foo, b: bar }
/** * 获取函数与设置函数 */
dest = { set a(val) { console.log(`Invoked dest setter with param ${val}`); } };
src = { get a() { console.log('Invoked src getter'); return 'foo'; } };
Object.assign(dest, src);
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo"
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来 console.log(dest);
// { set a(val) {...} }
/*
1、Object.assign()实际上是浅复制
2、多个源对象,有相同属性,则使用最后一个复制的值
3、从源对象访问器属性取得的值,比如函数,会作为一个静态值赋给目标对象,则不能在两个对象间转移获取函数和设置函数
*/
2.2.6 对象标识及相等判断(Object.is())
这个方法跟===有点像,但同时考虑到了一些边界情况
// 这些是===符合预期的情况
console.log(true === 1);
// false
console.log({} === {});
// false
console.log("2" === 2);
// false
// 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等
console.log(+0 === -0);
// true
console.log(+0 === 0);
// true
console.log(-0 === 0);
// true
// 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN()
console.log(NaN === NaN);
// false
console.log(isNaN(NaN));
// true
===============
console.log(Object.is(true, 1));
// false
console.log(Object.is({}, {}));
// false
console.log(Object.is("2", 2));
// false
// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0));
// false
console.log(Object.is(+0, 0));
// true
console.log(Object.is(-0, 0));
// false
// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN));
// true
2.2.7 增强对象写法
1、属性简写
2、可计算属性
3、简写方法名
2.2.8 对象解构,可多层解构
// 不使用对象解构
let person = { name: 'Matt', age: 27 };
let personName = person.name, personAge = person.age;
// 使用对象解构
let person = { name: 'Matt', age: 27 };
let { name: personName, age: personAge } = person;
//解构不存在属性 undefined
let person = { name: 'Matt', age: 27 };
let { name, job } = person;
console.log(name);
// Matt console.log(job); // undefined
//默认值
let person = { name: 'Matt', age: 27 };
let { name, job='Software engineer' } = person;
console.log(name);
// Matt console.log(job);
// Software engineer