对象,类与面向对象编程
1.理解对象
let person = new Object();
person.name = "tom";
console.log(person.name);
属性的类型
js会使用一些内部特性来描述属性的特征,但是开发者不能再js中直接访问,为了将某个特性标识为内部特性,js会用两个中括号把特性的名称括起来,比如[[Enumerable]]
数据属性:
数据属性包含一个保存数据值的位置,值会从这个位置读取,也会写到这个位置,数据属性有四个特性描述:
[[Configurable]]
:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性,默认为值true[[Enumerable]]
:表示属性是否可以通过for-in循环返回,默认值为true[[Writable]]
:表示属性的值是否可以被修改,默认值为true[[Value]]
:包含属性实际的值,默认值为undefined
在添加属性后,都会被保存到[[value]]中,后续的修改都会被放到这里面
如果想要修改属性的默认特性,需要使用Object.defineProperty()
方法,接收三个参数:要给其添加属性的对象,属性的名称,一个描述符对象。最后一个参数,即描述符对象上的属性可以包含:configurable
,enumerable
,writable
,value
let person = {};
Object.defineProperty(person,"name",{
writable:false,
value:"tom"
});
console.log(person.name);
person.name = "jim";
//在严格模式下修改会抛出错误
console.log(person.name);
以上适合创建不可配置的属性
一个属性被定义为不可配置后,就不能在变回可配置得了
在调用Object.defineProperty()时,configurable,enumerable,writable的值如果不指定,则都默认为false
访问器属性:
访问器属性不包含数据值,包含一个获取函数和设置函数,不过两个函数不是必须的,在读取访问器属性时,会调用获取函数,这个函数的责任是返回一个有效的值,在写入访问器属性时,会调用设置函数并传入新值
访问器属性有个特性描述他们的行为:
[[Configurable]]
:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,是否可以把它改为数据属性,默认值为true[[Enumerable]]
:表示属性是否可以通过for-in循环返回,默认值为true[[Get]]
:获取函数,在读取属性时调用,默认值为undefined[[Set]]
:设置函数,在写入属性时调用,默认值为undefined
访问器属性需要通过Object.defineProperty()定义:
let book = {
year_:2017,
edition:1
};
Object.defineProperty(book,"year",{
get() {
return this.year_;
},
set(newValue) {
if(newValue>2017){
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.edition);
只定义获取函数意味着属性是只读的,尝试修改属性会被忽略,
定义多个属性
es提供了Object.defineProperties()方法,一次性通过多个描述符定义多个属性,两个参数:要为之添加或修改属性的对象,描述符对象,其属性要与添加或修改的属性一一对应
let book = {};
Object.defineProperties(book,{
year_: {
value:2017
},
edition: {
value: 1
},
year: {
get() {
return this.year_;
},
set(newValue) {
if(newValue>2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
})
读取属性的特性
使用Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符,两个参数:属性所在的对象,要取得其描述符的属性名。返回值是一个对象,
访问器属性包含:configurable,enumerable,get,set
数据属性包含:configurable,enumerable,writable,value
let book = {};
Object.defineProperties(book,{
year_:{
value:2017
},
edition:{
value:1
},
year:{
get:function() {
return this.year_;
},
set:function() {
if(newValue>2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
let descriptor = Object.getOwnPropertyDescriptor(book,"year_");
console.log(descriptor.value); //2017
console.log(descriptor.configurable); //false
console.log(typeof descriptor.get); //undefined
let descriptor1 = Object.getOwnPropertyDescriptor(book,"year");
console.log(descriptor1.value); //undefined
console.log(descriptor1.enumerable); //false
console.log(typeof descriptor1.get); //function
es2017新增了Object.getOwnPropertyDescriptors()静态方法,这个方法会在每个自有属性上调用Object.getOwnpropertyDescriptor()并在一个新对象中返回他们,即会以对象的形式展示每个属性的特性
合并对象(浅拷贝)
Object.assign()方法可以实现合并对象,接受一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象,以字符串和符号为键的属性会被复制,对于每个符合条件的属性,这个方法会使用源对象上的[[Get]]
取得属性的值,然后使用目标对象上的[[Set]]
设置属性的值
如果多个源对象都有相同的属性,则使用最后一个复制的值,此外,从源对象访问器属性取得的值,比如获取函数,会作为一个静态值赋给目标对象,不能再两个对象之间转移获取函数和设置函数
对象标识和相等判定
Object.is()
方法用于判定相等问题,接受两个参数
console.log(Object.is(true,1)); //false
console.log(Object.is({},{})); //false
console.log(Object.is("2",2)); //false
//正确的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
增强的对象语法
属性值简写:再给对象添加属性的时候,如果属性值和属性一样,那么可以把属性值省略
let name = "tom";
let person = {
name:name
}
//两者相同
let person = {
name
}
可计算属性:可以再对象字面中直接动态命名属性:
const nameKey = "name";
const ageKey = "age";
let person = {
[nameKey]:"tom",
[ageKey]:"20"
}
console.log(person); //{name:"tom",age:20}
中括号还可以加入复杂的表达式
简写方法名
//原来的写法
let person = {
sayname:function() {
console.log(name);
}
}
//es6的语法糖
let person = {
sayname(name) {
console.log(name);
}
}
简写方法名对于获取函数和设置函数也是适用的,而且简写方法名与可计算属性键相互兼容
对象解构
对象解构可以再一天语句中适用嵌套属性实现一个或多个赋值操作,简单来说,就是使用与对象匹配的结构来实现对象属性赋值
let person = {
name:"tom",
age:27
};
let personname = person.name;
let personage = person.age;
//使用对象解构
let person = {
name:"tom",
age:27
}
let {name:personname,age:personage} = person;
使用解构可以再一个类似对象字面量的结构中,声明多个变量,同时执行多个赋值操作,如果想让变量直接使用属性的名称,那么可以使用简写语法
解构赋值不一定与对象的属性匹配,赋值的时候可以忽略某些属性,而如果引用的属性不存在,则变量的值为undefined
解构在内部使用函数ToObject()
(不能再运行环境访问),把元数据结构转换为对象,这意味着,在对象解构的上下文中,原始值会被当成对象
解构不要求变量必须在解构表达式中声明,不过,如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中:
let personname,personage;
let person = {
name:"tom",
age:20
};
({name:personname,age:personage} = [person]);
console.log(personname,personage);
嵌套解构
解构对于引用嵌套的属性或赋值目标没有限制,为此,可以通过解构来赋值对象属性
let person = {
name:"tom",
age:20,
job:{
title:"titleName"
}
};
let personCopy = {};
({
name:personCopy.name,
age:personCopy.age,
job:[personCopy.job]
} = person);
//因为一个对象的引用被复制给personCopy,所以修改person.job对象的属性也会影响personCopy
//使用嵌套解构
let { job: {title} } = person;
在外层属性没有定义的情况下不能使用嵌套结构,无论是源对象还是目标对象都一样
let person = {
job:{
title:"titleNmae"
}
}
//类似这样的属于没有外层属性
参数上下文匹配
在函数参数列表中也可以进行解构赋值,对参数的解构赋值不会影响arguments
(参数列表)对象,但可以在函数签名中声明在函数体内部使用局部变量
function print(foo,{name:personname,age:personage}) {
console.log(arguments);
console.log(personname,personage);
}
2.创建对象
工厂模式
工厂模式是一种设计模式,用于解决创建多个类似对象的问题
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("tom",20,"student");
构造函数模式
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("tom",20,"student");
person1.sayName();
上面的构造模式替代了工厂模式,在这种情况下,使用new操作符,执行如下操作:
- 在内存中创建一个新对象
- 这个新对象内部的
[[Prototype]]
特性被赋值为构造函数的prototype - 构造函数内部的this被赋值为这个新对象(即this指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象
这些对象既是Object的实例,也是person的实例
构造函数也是函数:
构造函数与普通函数唯一的区别就是调用方式不同,构造函数使用new操作符调用,不使用new操作符调用的就是普通函数,如果在调用函数而没有明确指定this(即没有座位对象的方法说明)值的时候,this始终指向global(window)对象
构造函数的问题:
构造函数的问题在于,其定义的方法在每个实例上都创建一遍,在创建的几个实例中,每个实例都有sayName方法,但是不是同一个function实例,最主要的是以这种方式创建函数会带来不同的作用域链和标识符解析,但创建新function实例的机制是一样的
解决方案是:把函数定义转移到 构造函数外部:
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
这样写虽然解决了函数重复定义的问题,但是全局作用域也被搞乱了,因为那个函数实际上只能在一个对象上调用,如果同时要实现多个方法,就很难讲代码聚集,以上问题可以通过原型模式实现
原型模式
每个函数都会创建一个propotype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法,实际上,这个对象就是啥通过调用构造函数创建的对象的原型,使用原型对象的好处是在它上面定义的属性和方法可以被对象实例共享,原来在构造函数中直接给对象实例的值,可以直接赋值给他们的原型
function Person() {
Person.prototype.name = "tom";
Person.prototype.age = 20;
}
let person1 = new Person();
console.log(person1.name);
只要创建一个函数,就会按照特定的规则为这个函数创建一个属性prototype(指向原型对象),默认情况下,所有原型对象都会获得一个constructor的属性,指回原来的构造函数
|---->propotype
| |(propotype是原型对象的引用)
| |
构造函数->原型对象(constructor)--->实例对象([[propotype]],__proto__访问)
| | | |
<------- <-------------------------------
在自定义构造函数时,原型对象默认只获得constructor属性,其他属性从object继承,每次调用构造函数创建一个新实例,这个实例的内部[[propotype]]指针都会被赋值为构造函数的原型对象,(代码无法直接访问[[propotype]],但chrome中会在每个对象上暴露__proto__
属性)
console.log(Person.propotype.constructor === Person);
正常的原型链会终止于Object的原型对象,object原型的原型是null
实例通过__proto__
连接到原型对象,他实际上指向隐藏特性[[prototype]]
构造函数通过prototype属性连接到原型对象
实例与构造函数没有直接联系,与原型对象有直接联系
- 虽然不是所有实现都对外暴露了[[Prototype]],但可以使用isPrototypeOf()方法确定两个对象之间的关系,本质上,isPrototypeOf()会在传入参数的[[Prototype]]指向调用他的对象时返回true
console.log(Person.Prototype.isPrototypeOf(person1)); //true
- es的Object类型 有一个方法Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值
使用这个方法可以很方便的取得一个对象的原型
console.log(Object.getPrototypeOf(person1) == Person.prototype); //true
-
Object.setPrototypeOf()
方法可以向实例的是有特性[[Prototype]]写入一个新值,这样可以重写一个对象的原型继承关系 -
Object.create()
方法可以创建一个新对象,同时为其制定原型,(参数为要指定的原型)
**注意:**在访问属性时,会先搜索实例本身的属性,然后搜索原型上的内容,如果有重名属性,那么原型上的属性会被忽略,如果想要访问原型上的属性,可以使用delete
删除实例上的这个属性
haswOwnProperty()
用于确定某个属性是在实例上还是原型对象上,属性存在于实例上时返回true
console.log(person1.hasOwnProperty("name"));
在for-in循环中使用in操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性,
Object.keys()
方法可以获得对象上所有可枚举的实例属性(默认情况下开发者定义的属性都是可枚举的,包括实例属性和原型属性和遮蔽原型中不可枚举)Object.getOwnPropertySymbols()
方法用于获取symbol符号属性
对象迭代
对象内容转换为序列化数组
Object.values()
:返回对象值的数组Object.entries()
:返回键值对的数组
这两个方法使用时,非字符串属性会被转换为字符串输出,另外这两个方法执行对象的浅复制
3.继承
原型链
基本思想:原型是另一个类型的实例,意味着这个原型本身有一个内部指针指向另一个原型,响应的另一个原型也有一个指针指向另一个构造函数,这样就实现了一条原型链
function super1() {
this.super1property = true;
}
super1.prototype.getValue = function() {
return this.property;
};
function super2() {
this.super2property = false;
}
//继承super1
super2.prototype = new super1();
super2.prototype.getValue = function() {
return this.super2property;
};
let instance = new super2();
console.log(instance.getValue());
上述例子定义了两个类型,super1
和super2
,super2
通过创建super1
的实例并将其赋值给自己的原型super2.prototype
,实现了对super1
的继承,这个赋值重写了super2
最初的原型,将其替换为super1
的实例,这意味着super1
的属性和方法也会存在于super2.prototype
,这样继承后,代码紧接着给super2.prototype
,最后创建了super2的实例并调用了他继承的getValue()
方法
默认原型:
所有引用类型都继承自Object,这是通过原型链实现的,任何函数的默认原型都是一个Object的实例
原型与继承关系
原型与实例关系可以通过两种方式来确定,
- 第一种是使用
instanceof
操作符,如果一个实例的原型链中出现过相应的构造函数,则返回true
console.log(instance instanceof Object);
console.log(instance instanceof super1);
console.log(instance instanceof super2);
- 第二种是使用
isPrototypeof()
方法,原型链中的每个原型都可以调用方法,只要原型链中包括这个原型,就返回``true`
console.log(Object.prototype.isPrototypeOf(instance)); //true
console.log(super1.prototype.isPrototypeOf(instance)); //true
console.log(super2.prototype.isPrototypeOf(instance)); //true
关于方法
- 子类有时候需要覆盖父类的方法,或者增加父类没有的方法
function super1() {
this.super1property = true;
}
super1.prototype.getValue = function() {
return this.property;
};
function super2() {
this.super2property = false;
}
//继承super1
super2.prototype = new super1();
//新方法
super2.prototype.get2 = function() {
return this.super2property;
};
//覆盖已经有的方法
super2.prototype.getValue = function() {
return false;
}
let instance = new super2();
console.log(instance.getValue());
- 以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链:
function super1() {
this.super1property = true;
}
super1.prototype.getValue = function() {
return this.property;
};
function super2() {
this.super2property = false;
}
//继承super1
super2.prototype = new super1();
//通过对象字面量添加新方法,这会导致上一行失效
super2.prototype = {
getValue2() {
return this.super2property;
},
some1() {
return false;
}
};
let instance = new super2();
console.log(instance.getValue2());
子类的原型再被复制为super1的实例之后,又被一个对象字面量覆盖了,覆盖后是object的实例,之前的原型链断了
原型链存在的问题
- 因为父类实例的属性是共享的,在子类中实例化对象时,多个实例中一个修改从父类继承过来的属性时,其余子类实例中的属性也会同步改变
- 子类型在实例化时不能给父类型的构造函数传参
盗用构造函数
盗用构造函数是为了解决原型包含引用值导致的继承问题
基本思路:在子类构造函数中调用父类构造函数,因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()
和call()
方法以新创建的对象为上下文执行构造函数
通过使用call()和apply()方法,super1构造函数在为super2的实例创建的新对象的上下文中执行了,这相当新的super2对象上运行了super1函数中的所有初始化代码
- 优点:可以在子类构造函数中向父类构造函数传参
function super1(name) {
this.name = name;
}
function super2() {
//继承super1并传参
super1.call(this,"tom");
//实例属性
this.age = 20;
}
let instance = new super2();
console.log(instance.name);
console.log(instance.age);
- 缺点:必须在构造函数中定义方法,因此函数不能重用,而且子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式
组合继承
综合继承综合了原型链和盗用构造函数
基本思路:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性,这样既可以把方法定义在原型上已实现重用又可以让每个实例都有自己的属性
function super1(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
super1.prototype.sayName = function() {
console.log(this.name);
}
function super2() {
//继承属性
super1.call(this,name);
//实例属性
this.age = age;
}
//继承方法
super2.prototype.sayAge = new super1();
super2.prototype.sayAge = function() {
console.log(this.age);
}
let instance = new super2("tom",20);
原型式继承
适合不需要单独创建构造函数,但仍然需要在对象之间共享信息的场合,但是记住,属性中包含的引用值始终会在相关对象间共享
使用Object,create()
:两个参数:第一个是作为新对象原型的对象,第二个可选参数是新对象定义额外属性的对象
寄生式继承
创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象
function create() {
let clone = object(original);
clone.say = function() { //增强对象
console.log("hi");
};
return clone;
}
寄生式组合继承
寄生式组合继承通过盗用构造函数几成熟性,但是用混合式原型链继承方法,基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本,说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型
function inpro(super,super1) {
let prototype = object(super2.prototype); //创建对象
prototype.constructor = super; //增强对象
super.prototype = prototype; //赋值对象
}
寄生式组合继承算是引用类型继承的最佳模式
4.类
类是es新增的语法糖
类定义
- 类声明
class Person{};
- 类表达式
class ani = class{}
与函数表达式类似,类表达式在他们被求值前也不能引用
函数声明可以提升,但是类定义不能
函数受函数作用域限制,类受块作用域限制
类的构成:构造函数,实例方法,获取函数,设置函数,静态类方法。
默认情况下,类定义的代码都在严格模式下执行
类表达式的名称是可选的,再把类表达式赋值给变量后,可以通过name属性取得类表达式的名称字符串,但不能在类表达式作用域外部访问这个标识符
let Person = class PersonName{
id() {
console.log(Person.name,PersonName.name);
}
}
let p = new Person();
p.id(); //PersonName PersonName
console.log(Person.name); //PersonName
console.log(PersonName); //抛出错误
类构造函数
constructor关键字用于在类定义块内部创建类的构造函数,在创建实例时,会调用这个函数,构造函数不是必须的
**实例化:**使用new调用类的构造函数会执行如下操作:
- 在内存中创建一个新对象
- 这个新对象内部的
[[prototype]]
指针被赋值为构造函数的prototype
属性 - 构造函数内部的this被赋值为这个新对象
- 执行构造函数内部代码
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象
类实例化时也可以传入参数
class Person {
constructor(name){
console.log(arguments.length);
this.name = name;
console.log(this.name);
}
}
let person1 = new Person("tom");
类构造函数与构造函数的主要区别是,代用类构造函数必须使用new操作符,普通构造函数如果不使用new调用,那么就会以全局的this作为内部对象
类是特殊函数
类标识符也有prototype属性,而这个原型也有一个constructor属性指向自身:
class Person{}
console.log(Person.prototype); //{ constructor:f() }
console.log(Person === Person.prototype.constructor); //true
也可以使用instanceof操作符检查构造函数原型是否存在于实例的原型链中:
class Person{}
let p =new Person();
console.log(p instanceof Person); //true
在类的上下文中,类本身在使用new调用时就会被当成构造函数,类中定义的constructor方法不会被当成构造函数,在对他使用instanceof操作符时会返回false,但是,如果在创建实例时直接将类构造函数当成普通构造函数来使用,那么instanceof操作符的返回值会反转:
class Person{}
let p1 = new Person();
console.log(p1.constructor === Person); //true
console.log(p1 instanceof Person); //true
console.log(p1 instanceof Person.constructor); //false
let p2 = new Person.constructor();
console.log(p2.constructor === Person); //false
console.log(p2 instanceof Person); //false
console.log(p2 instanceof Person.constructor); //true
类也可以当做参数传递
类也可以立即实例化:
let p = new class FOO{
constructor(x) {
console.log(x);
}
}('bar'); //bar
console.log(p); //Foo()
实例,原型,类成员
实例成员
每个实例都对应一个唯一的成员对象,意味着所有成员都不会在原型上共享
class Person {
constructor() {
this.name = new String("tom");
this.say = () => {
console.log(this.name);
}
}
}
let p1 = new Person();
let p2 = new Person();
console.log(p1.name === p2.name); //false
原型方法和访问器
为了在实例之间共享方法,类定义语法把在类块中定义的方法作为原型方法:
class Person {
constructor(){
//添加到this的值都会存在于不同的实例中
this.say = ()=>console.log("tom");
}
//在类快中定义内容,会定义到原型上
locate() {
console.log("原型上的内容");
}
}
let p = new Person();
p.say(); //tom
p.locate(); //原型上的内容
Person.prototype.locate(); //原型上的内容
可以吧方法定义在类构造函数或者类快中,但不能再类块中给原型添加原始值或对象作为成员数据
类方法等同于对象属性,可以使用字符串,符号,计算的值作为键
类定义也可以支持获取和设置访问器:
class Person {
set name (newName) {
this.name_ = newName;
}
get name() {
return this.name_;
}
}
let p = new Person();
p.name = "tom";
console.log(p.name);
console.log(p.name_);
静态类方法
可以再类上定义静态方法,这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例与原型成员类似,静态成员每个类上只能有一个
静态类成员在类定义中使用static关键字作为前缀,在静态成员中,this引用类自身:
class Person {
constructor() {
//添加到this的所有内容都会存到不同的实例上
this.locate = ()=> console.log("instance",this);
}
//定义在类的原型对象上
locate() {
console.log('prototype',this);
}
//定义在类本身上
static locate() {
console.log('class',this);
}
}
let p = new Person();
p.locate(); //instance,Person{}
Person.prototype.locate(); //prototype,{constructor:...}
Person.locate(); //class,class Person()
非函数原型和类成员
虽然类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,可以手动添加
迭代器与生成器方法
类定义语法支持在原型
和类本身上
定义生成器方法:
class Person {
//在原型上定义生成器方法
*createIterator1() {
yield "tom1";
yield "tom2";
yield "tom3";
}
//在类上定义生成器方法
static *createJobIterator() {
yield "zzz1";
yield "zzz2";
yield "zzz3";
}
}
let jobIter = Person.createJobIterator();
console.log(jobIter.next().value); //zzz1
console.log(jobIter.next().value); //zzz2
console.log(jobIter.next().value); //zzz3
let p = new Person();
let nick = p.createIterator1();
console.log(nick.next().value); //tom1
console.log(nick.next().value); //tom2
console.log(nick.next().value); //tom3
继承
es6支持单继承,使用extends关键字,可以继承任何拥有[[Constructor]]
和原型的对象
继承基础
class Vehicle{}
//继承类
class Bus extends Vehicle {}
let b = new Bus();
console.log(b instanceof Bus); //true
console.log(b instanceof Vehicle); //true
function Person() {}
//继承普通构造函数
class Engineer extends Person() {}
let e = new Engineer();
console.log(e instanceof Engineer); //true
console.log(e instanceof Person); //true
派生类都会通过原型链访问到类和原型上定义的放法,this的值会反应调用相应方法的实例或者类:
构造函数,HomeObject,super
派生类可以使用super关键字引用他们的原型,super只能在派生类中使用,而且仅限于类构造函数,实例方法,静态方法内部,在类构造函数中使用super可以 调用父类构造函数
class Vehicle {
constructor() {
this.hasEngine = true;
}
}
class Bus extends Vehicle{
constructor() {
//不要在调用super之前引用this,否则会抛出错误
super();
console.log(this instanceof Vehicle); //true
console.log(this); //Bus { hasEngine:true }
}
}
new Bus();
注意:
es6给类构造函数和静态方法添加了内部特性
[[HomeObject]]
,这个特性是一个指针,指向定义该方法的对象,这个指针是自动赋值的,而且只能在jas引擎内部访问,super始终会定义为[[HomeObject]]的原型
- super只能在派生类构造函数和静态方法中使用
- 不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法
- 调用super()会调用父类构造函数,并将返回的实例赋值给this
- super()的行为如同调用构造函数,如果需要给父类构造函数传参,否则需要手动引入
- 如果没有定义类构造函数,在实例化派生类时会调用super(),而且会传入所有传给派生类的参数
- 在类构造函数中,不能再调用super()之前引用this
- 如果在派生类中显式定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象
抽象基类
抽象基类:可以给其他类继承,但是本身不能实例化,可以通过new.target实现。
new,target保存通过new关键字调用的类或函数,通过在实例化时检测new。target是不是抽象基类,可以组织抽象基类的实例化
class Vehicle{
constructor() {
console.log(new.target);
if(new.target === Vehicle) {
throw new Error("有错误")
}
}
}
//派生类
class Bus extends Vehicle{}
new Bus(); //class Bus()
new Vehicle(); //class Vehicle{}
继承内置类型
可以继承内置引用类型,如array等
类混入
把不同的类的行为集中到一个类很常见,虽然es6没有实现多继承,但通过现有特性可以轻松的模拟这种行为
Object,assign()方法是为了混入对象行为而设计的,只有在需要混入类的行为时,才有必要自己实现混入表达式,如果只是需要混入多个对象的属性,那么使用Object.assign()就可以了
class Vehicle{}
function getParentClass() {
console.log('eval');
}
class Bus extends getParentClass() {}
//可求值的表达式
extends关键字后面是一个js表达式,任何可以解析为一个类或一个构造函数的表达式都是有效的,这个表达式会在求职类定义时被求值
混入模式可以通过在一个表达式中连缀多个混入元素来实现,这个表达式最终会被解析为一个可以被继承的类,如果person类需要组合A,B,C,则需要某种机制实现B继承A,C继承B,而person在继承C,从而把A,B,C组合到这个超类中