1. 对象基础

7. 对象
之前写过对象基本了解,这里就不再继续写了

2.继承

  • 继承是面向对象编程中讨论最多的话题。
  • 很多面向对象语言都支持两种继承:
    • 接口继承
    • 实现继承。
  • 前者只继承方法签名,后者继承实际的方法。
  • 接口继承在 ECMAScript 中是不可能的,因为函数没有签名。
  • 实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。


    2.1 原型链

    ECMA-262 把原型链定义为 ECMAScript 的主要继承方式。

  • 其基本思想就是通过原型继承多个引用 类型的属性和方法。

  • 重温一下构造函数、原型和实例的关系:
    1. - 每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。
    2. - 如果原型是另一个类型的实例呢?那就意味 着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。
    3. - 这样就在实例和原型之间构造了一条原型链。
    4. - 这就是原型链的基本构想。

简单来说,对象实例都是由构造函数内部的对象原型来创造的,对象原型相当于一类实例对象的模板,由构造函数包含,但原型对象也可能是其他构造函数内原型对象的实例对象,这样就构成了两个构造函数之间的关系

在这里仅仅浅析一下什么是原型链,关于原型链的问题还是在有所了解后再深入


3. 类

ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。类(class)是 ECMAScript 中新的基础性语法糖结构,因此刚开始接触时可能会不太习惯。虽然 ECMAScript 6 类表面 上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。

3.1 定义类

与函数类型相似,定义类也有两种主要方式:类声明和类表达式。这两种方式都使用 class 关键字加大括号:

  1. // 类声明
  2. class Person {}
  3. // 类表达式
  4. const Animal = class {};
  • 与函数表达式类似,类表达式在它们被求值前也不能引用。
  • 不过,与函数定义不同的是,虽然函数声明可以提升,但类定义不能 ```javascript console.log(fn); // undefined var fn = function() {}; //fn为变量,只提升变量不提升函数 console.log(fn); // function() {}

console.log(fn1); // fn1() {} function fn1() {} //函数声明提升 console.log(fn1); // fn1() {}

console.log(aClass); // undefined var aClass = class {}; //类声明不进行提升 ,但声明的变量名提升 console.log(aClass); // class {}

console.log(otherClass); // ReferenceError: otherClass is not defined class otherClass {} // 类表达式声明都不会提升 console.log(otherClass); // class otherClass {}

  1. 另一个跟函数声明不同的地方是,函数受函数作用域限制,而类受块作用域限制:
  2. ```javascript
  3. {
  4. function newFun() {}
  5. class oClass {}
  6. }
  7. console.log(newFun); // newFun() {}
  8. console.log(oClass); // ReferenceError: oClass is not defined

和let一样的限制都在块{}括号内部存在


3.2 类的构成

  • 类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。 空的类定义照样有效。
  • 默认情况下,类定义中的代码都在严格模式下执行。
  • 与函数构造函数一样,多数编程风格都建议类名的首字母要大写,以区别于通过它创建的实例(比 如,通过 class Foo {}创建实例 foo): ```javascript // 类可以定义为空 class Foo {}

// 有构造函数的类,有效 class Bar { constructor() {} }

// 有获取函数的类,有效 class Baz { get myBaz() {} }

// 有静态方法的类,有效 class Qux { static myQux() {} }

  1. 类表达式的名称是可选的。在把类表达式赋值给变量后,可以通过 name 属性取得类表达式的名称 字符串。但不能在类表达式作用域外部访问这个标识符。
  2. ```javascript
  3. let Person = class PersonName { //类声明创建类,并将类赋值给一个变量
  4. identify() { //里面包含一个函数
  5. console.log(Person.name, PersonName.name);
  6. }
  7. }
  8. let p = new Person();
  9. p.identify(); // PersonName PersonName 类赋值给p 含有此函数可在外部运行
  10. console.log(Person.name); // PersonName
  11. console.log(PersonName); // ReferenceError: PersonName is not defined

外部调用类名无法调用需要将类赋值给变量,通过变量.name的方式来获取类名


3.3 类构造函数

  • constructor 关键字用于在类定义块内部创建类的构造函数。
  • 方法名 constructor 会告诉解释器 在使用 new 操作符创建类的新实例时,应该调用这个函数。
  • 构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。

    实例化


    使用 new 操作符实例化上面代码中 Person 的操作等于使用 new 调用其构造函数。唯一可感知的不同之处就是,JavaScript 解释器知道使用 new 和类意味着应该使用 constructor 函数进行实例化。使用 new 调用类的构造函数会执行如下操作。
    (1) 在内存中创建一个新对象。
    (2) 这个新对象内部的[[Prototype]]指针被赋值为构造函数的 prototype 属性。
    (3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
    (4) 执行构造函数内部的代码(给新对象添加属性)。
    (5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
  1. class Animal {} //创键一个类
  2. class Person { //创建第二个类
  3. constructor() {
  4. console.log('person ctor');
  5. }
  6. }
  7. class Vegetable { //创建第三个类
  8. constructor() {
  9. this.color = 'orange';
  10. }
  11. } //使用new操作符实例化类
  12. let a = new Animal();
  13. let p = new Person(); // person ctor
  14. let v = new Vegetable();
  15. console.log(v.color); // orange 类实例化后可在外部调用类的属性

类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的:

  1. class Person {
  2. constructor(name) {
  3. console.log(arguments.length);
  4. this.name = name || null; //或判断符,只有两个都为false才结束,输出第二个表达式null
  5. }
  6. }
  7. let p1 = new Person; // 0 0=false
  8. console.log(p1.name); // null
  9. let p2 = new Person(); // 0
  10. console.log(p2.name); // null
  11. let p3 = new Person('Jake'); // 1=teue 或运算符不再判断第二个表达式直接赋值给name
  12. console.log(p3.name); // Jake

类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操作符。而普通构造函数如果 不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。调用类构造函数时如果 忘了使用 new 则会抛出错误:

  1. function Person() {}
  2. class Animal {}
  3. // 把 window 作为 this 来构建实例
  4. let p = Person();
  5. let a = Animal();
  6. // TypeError: class constructor Animal cannot be invoked without 'new'

类构造函数没有什么特殊之处,实例化之后,它会成为普通的实例方法(但作为类构造函数,仍然 要使用 new 调用)。因此,实例化之后可以在实例上引用它:

  1. class Person {}
  2. // 使用类创建一个新实例
  3. let p1 = new Person();
  4. p1.constructor();
  5. // TypeError: Class constructor Person cannot be invoked without 'new'
  6. // 使用对类构造函数的引用创建一个新实例
  7. let p2 = new p1.constructor();