【对象,继承,类】为了了解对象,我将红宝书上的代码又整理了一遍。

222 阅读12分钟

1.如何创建一个对象,并将他的属性age设置为不可删除,age的默认值为18?

let a =  {name: "haha"};
Object.defineProperty(a, "age", {
    configurable: false,
    value: 18,
})
console.log(a) // {name: "haha", age: 18}
delete a.name
delete a.age
console.log(a) //{age: 18}

一般属性分为数据属性和访问器属性,在数据属性上有4种属性:

  • [[Configurable]] 是否能通过delete删除重新定义

    • 一个属性被定义为不可配置以后,不能再变回可配置了。
  • [[Enumerable]] 是否能通过for - in 循环返回

  • [[Writeable]] 表示属性是否能被修改

  • [[Value]] 包含属性的值

2.如何为该对象添加上相关的方法,使得当你修改year属性的时候,当该值大于2017时,修改_year,并且修改edition = edition - 2017。

let book = {
    year_: 2018,
    edition: 1
}
// TODO
book.year = 2019
console.log('book', book); // book { year_: 2019, edition: 2 }
Object.defineProperty(book, "year" ,{
    get() {
      return this.year_;
    },
    set(newValue) {
      if(newValue > 2017) {
        this.year_ = newValue;
        this.edition = newValue - 2017
      }
    }
})

访问器不包含数据值,相反包含一个获取getter,和设置setter的函数.

  • [[Configurable]] 是否能通过delete删除重新定义
  • [[Enumerable]] 是否能通过for - in 循环返回
  • [[Get]] 获取函数,读属性时调用
  • [[Value]] 设置函数,写入函数时调用

访问器必须使用Object.defineProperty(),不能直接定义

3. 如何读取一个对象的属性描述符?

let a =  {};
Object.defineProperty(a, "age", {
    configurable: false,
    value: 18,
})
// TODO 获取age的属性描述符
let description = Object.getOwnPropertyDescriptor(a, "age");
console.log("description", description)

Object.getOwnPropertyDescriptor()可以获取指定属性的属性描述符. ES7中新增了Object.getOwnPropertyDescriptors()的静态方法,获取一个对象的所有自身属性的描述符。

4. 关于合并对象,写出以下代码的结果。

let src , dest, result ;
dest = {id: 'dest'}
src = Object.assign(dest, {id: 'src1,' ,a: 'foo'}, {id: 'src2', b: 'bar', c: {}})
console.log('src', src);
console.log('flag', src.c === dest.c)
src {id: "src2", a: "foo", b: "bar", c: {}}
flag true
  • Object.assign,目标对象和多个源对象的参数,将每一个源对象可枚举(Object.propertyIsEnumerable() 为true)的 属性和自有属性(Object.hasOwnProperty 为 true)复制到目标对象上。
  • 如果有多个源对象都有相同属性,只复制最后一个。
  • 浅复制,只会复制对象的引用
  • 复制期间出错,只会完成部分的复制。

5. 如何正确的判断一个对象是否相同,如何判断+0,-0,NaN。

function compare(a, b) {
    
}
compare(NaN,NaN) // true
compare(+0, -0) // false
compare(+0, 0) // true
compare({}, {}) // false
compare(true, 1) // false
function compare(a, b) {
   return Object.is(a, b)
}

6. 如何简化以下代码

let name = "haha";
let person = {
    name: name,
    sayName: function(name) {
        console.log('my name is ' + name);
    }
}
let name = "haha";
let person = {
    name,
    sayName(name) {
        console.log('my name is ' + name);
    }
}

增强对象的语法

  • 属性简写
  • 可计算属性
const nameKey = 'age';
let person = {
    [nameKey]: 18
}
  • 简写方法名

7. 写出以下代码的结果

let person = {
    name: "haha",
    age: 18
};

const {age,name,job,other="otherthings"} = person;
console.log("age", age);
console.log("name", name);
console.log("job", job);
console.log("other", other);

let {length} = 'fooo';
console.log('length', length);

let {constructor: c} = 4;
console.log(c === Number)

let { s } = null;
let { s2 } = undefined;

age 18
name haha
job undefined
other otherthings
length 4
true
VM274:18 Uncaught TypeError: Cannot destructure property 's' of 'null' as it is null.
    at <anonymous>:18:7

ES6新增了对象解构语法,可以在一条语句中使用其那套数据实现一个或多个赋值操作。

  • 赋值不一定与对象属性匹配,如果引用的属性不存在就为undefined
  • 也可以在解构赋值的同时定义默认值

解构的内部使用了函数ToObject()(不能在函数运行时环境访问)把源数据解构转化为对象。意味着在对象解构的上下文中,原始值会被当成对象,意味着(根据ToObject()的定义),null和undefined不能被解构,否则会抛出错误。

8.写出以下代码的输出结果

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("p1", 18, "teacher")
let person2 = new Person("p2", 27, "doctor")

person1.sayName();
person2.sayName();

console.log(person1.sayName == person2.sayName)
console.log(person1.constructor == Person)
console.log(person2.constructor == Person)
console.log(person1 instanceof Object)
console.log(person2 instanceof Object)
console.log(person1 instanceof Person)
console.log(person2 instanceof Person)
// 结果
p1
p2
false
true
true
true
true
true
true

通过构造函数去创建一个对象的时候,其实是:

  • 没有显示地创建了对象
  • 属性和方法直接赋值给了this
  • 没有return

如何实现new

  • 在内存中创建一个新对象
  • 整个新对象地内部地[[prototype]]特性被赋值为构造函数地prototype属性
  • 构造函数内部地this被赋值给这个新对象(即this指向新对象)
  • 执行构造函数内部地代码(给新对象添加属性)
  • 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的对象。

constructor 和 实例

  • constructor是用来标识对象的类型的。
  • instanceof是用来确定对象类型更可靠的方式
  • 每一个对象都是Object的实例,同时也是person的实例。

几个问题

  • 实例对象上都是有一个constructor指向构造函数吗?
  • 实例是不是就是指向谁创造而来的呢?
  • 那么person1.constructor指向的能否是对象呢?Object

构造函数与普通函数

  • 唯一区别就是调用方式的不同,任何函数只要使用new 操作符号调用就是构造函数

构造函数的问题

  • 构造函数的定义的方法会在每一个实例上都再创建一遍。
  • 函数是对象,每一次定义函数的时候,都会初始化一个对象。
function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function() {
		console.log(this.name)
	}
    // 其实等同于以下的
    this.sayName = new Function("console.log(this.name)")
}
  • 因此person1.sayName 和person2.sayName 虽然同名但不是同一个了。

9.写出以下代码的输出结果

function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
}

Person.prototype.sayName = function() {
    console.log('name', this.name)
}


let person1 = new Person("p1", 18, "teacher")
let person2 = new Person("p2", 27, "doctor")

person1.sayName();
person2.sayName();

console.log(person1.sayName == person2.sayName)

name p1
name p2
true

原型模式

  • 每一个函数都会创建一个prototype的属性,这个属性是一个对象,包含由特定引用类型的实例共享的属性和方法。
  • 实际上,这个对象 就是通过调用构造函数创建对象的原型。
  • 无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)
  • 默认情况下,所有原型对象自动会获取一个名为constructor的属性,指回与之关联的构造函数
  • 在自定义构造函数是,原型对象默认只回获取constructor属性,其他的所有方法都继承自Object
  • 每一次调用构造函数创建一个新实例,这个实例的内部[[prototype]]指针就回被赋值为构造函数的原型对象。

问题

  • 实例和构造函数原型之间有直接联系,但实例和构造函数之间没有。

10. 写出以下结果

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();
           
person1.name = "Greg";
console.log(person1.name);   
console.log(person2.name);  
           
delete person1.name;
console.log(person1.name);   
console.log(person1.name);   // "Greg" - from instance
console.log(person2.name);   // "Nicholas" - from prototype
           
delete person1.name;
console.log(person1.name);   // "Nicholas" - from the prototype 
  • 通过对象访问属性时,会先搜索对象实例本身,如果找到了,就返回,如果没有找到就沿着指针进入原型对象,返回该值。
  • 给对象实例添加一个属性,这个属性就会遮蔽原型对象上的同名属性。
  • 即使将属性设置为null,也不会恢复联系,但可以通过delete 完全删除属性,让标识符解析继续搜索对象。

11. Object.create

let biped = {
	numLegs: 2
}

let person = Object.create(biped);
person.name = "Matt"

console.log(person.name)
console.log(person.numLegs)
console.log(Object.getPrototypeOf(person) === biped)
Matt
2
true

Object.create 会创建一个新对象,并为其指定原型。

12. 写出以下结果

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();
           
console.log(person1.hasOwnProperty("name")); 
console.log("name" in person1);  

let keys = Object.keys(Person.prototype);
console.log(keys);   
p1.name = "Rob";
p1.age = 31;
let p1keys = Object.keys(p1);
console.log(p1keys); 
console.log(person1.hasOwnProperty("name"));  // false
console.log("name" in person1);  // true
console.log(keys);    // "name,age,job,sayName"
console.log(p1keys);  // "name,age" 

两种使in的操作符的方式

  • 单独使用 和 在for - in 中使用
  • in 会返回该属性是由在对象上,原型属性和实例属性都会返回true

hasOwnProperty

  • 只有属性存在实例上才会返回true

对象属性

  • for in 遍历时,可以通过对象范文并可以被枚举的属性都会返回,包括实例属性和原型属性
  • 遮蔽原型中[[Enumerable]]特性被设置为false的属性也会在for-in循环中被返回
  • 获取对象上所有可枚举的实例属性,用Object.keys
  • 列出对象上所有实例属性,无论是否可枚举用 Object.getOwnPropertyNames()
  • ES7还增加了两个方法Object.values() 以及Object.entries()

constructor的改变不会影响instanceof

13. 写出以下代码

function Person() {}
           
let friend = new Person();
    
Person.prototype = {
  constructor: Person,
  name: "Nicholas",
  age: 29,
  job: "Software Engineer",
  sayName() {
    console.log(this.name);
  }
};
           
friend.sayName(); 
VM73:15 Uncaught TypeError: friend.sayName is not a function
    at <anonymous>:15:8

原型的动态性

  • 原型的搜索值时动态的,在实例修改前原型已经存在,在任何时候对原型对象的修改也会马上反映出来
  • 重写原型和添加方法是两回事,实例的[[Prototype]]指针是在调用构造函数时自动赋值的,这个指针即使把对象修改为不同的对象也不会改变。重写整个原型会切断最初原型和构造函数的关系。

原生对象的原型

  • 所有原型引用类型的构造函数(包括Object,Array,String)都在原型上定义了实例方法。
  • 通过原生对象的原型可以取得所有默认方法的引用,也可以给原生类型的实例定义新的方法。可以修改自定义对象原型一样修改原生对象原型,因此可以随时修改添加方法。
  • 像字符串,在读取属性时,后台会自动加上String的包装实例,从而找到String.prototype上的方法调用。

什么时包装实例?

14.原型链的问题

function SuperType(name){
	this.property = true
}
           
SuperType.prototype.getSuperValue = function() {
  return this.property
};
           
function SubType(){  
	this.subProperty = false
}
           
// inherit methods
SubType.prototype = new SuperType();
           
SubType.prototype.getSubValue = function() {
   return this.subProperty
};
           
let instance1 = new SubType();
console.log(instance.getSuperValue())

继承的思想

  • 通过原型继承多个引用类型的属性和方法
  • 每一个构造函数都有以一个属性对象,原型有一个属性指向回构造函数,而实例内部有一个指针指向原型
  • 如果一个原型是另一个类型的实例?那就意味着,整个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。

默认原型

  • 默认情况下,所有引用类型都继承Object。

  • 任何函数地默认原型都是一个Object的实例。也就是说这个实例有一个内部指针指向Object.prototype

15.如何实现一个寄生式组合继承

function obejct(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType) {
  let prototype = object(superType.prototype);  // create object
  prototype.constructor = subType;              // augment object
  subType.prototype = prototype;                // assign object
}    
    
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);      // second call to SuperType()
  this.age = age;
}
           
inheritPrototype(SubType, SuperType)

SubType.prototype.sayAge = function() {
  console.log(this.age);
};

  • 继承父类的实例方法 super.call

    • 在子类的构造函数中调用父类的构造函数。因为函数其实就是在特定上下文中执行代码的简单对象
  • 原型式继承:有一个对象,想要在已有的基础上再创建一个对象。

    • ES5 新增的 Object.create()
  • 为什么不用super.prototype = new SuperType()?

    • 这样会导致父类的构函数始终被两用两次,一次是再原型创建子类原型时调用,另一次时再子类构造函数时调用。
  • 而inheritPrototype 方法时,通过父类的一个原型副本来实现。

16. 类

定义提升

  • 函数声明可以提升,但类定义不行

构成

  • 构造函数
  • 实例方法
  • 获取函数 get
  • 设置函数 set
  • 静态方法 static

类构造函数 和 函数构造函数

  • 调用类构造函数必须使用new ,普通构造函数如果不用new 就是以全局的this作为普通对象
  • 调用类如果没有new 会抛出异常
  • 类中定义的constructor不会被当作构造函数,对它使用instanceof会返回false。但是如果创建实例直接将类构造函数当成普通构造函数来使用,那么instanceof就会不同了。

17. 类的语法

实例成员

每次通过new调用类 的时候,都会执行类的构造函数。函数内部会创建新实例this,添加自有属性。

每一个实例都对应唯一一个成员对象。

原型方法与访问器

在类块中定义的所有内容都会被定义在类的原型上

不能在类块中给原型添加原始值或者对象作为成员数据

静态方法

每一个类上只有一个静态成员。

静态成员中this指向引用类本省

非函数原型和类成员

可以在类的外部定义

迭代器和生成器方法

类定义语法支持在原型和类本身定义生成器方法

18. 继承

实现基础

通过extends来实现

构造函数和HomeObject 和super()

  • 静态方法中调用super可以继承类上定义的方法

super的注意点

  • 只能在派生类构造函数和静态方法中使用
  • 不能单独引用super关键字,要么用它调用构造函数,要么引用它的静态方法
  • 调用super()会调用父类构造的函数,并将返回的实例赋值给this
  • 在类构造函数中,不能再super()之前引用this
  • 如果在派生类中显示定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回了一个对象。

抽象基类

  • 它可供其他类继承,但本身不会被实例化
  • 通过new.target

继承内置类型

类混入

  • extends 关键字后面是一个Javascript表达式。任何可以解析为一个类或者一个构造函数的表达式都是有效。