javaScript高级程序设计(红宝书)之第八章

115 阅读21分钟

第八章 对象、类与面向对象编程

对象:

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

image.png

下意识的就是解决问题


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、构造函数、HomeObjectsuper()
    可以通过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 删除并重新定义.
                     是否可以修改它的特 性
                     是否可以把它改为数据属性
                     默认情况下,**(所有直接定义在对象上的属性)**的这个特性 都是 true2[[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