本文是学习coderwhy老师课程所整理 参考文章阮一峰
继承是什么?
为什么需要继承?
复用
继承的进化
原型链继承
// 父类: 公共属性和方法
function Person() {
this.name = "why"
this.friends = []
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student() {
this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
缺点:
- 1、打印stu对象, 继承的属性是看不到的
- 2、创建出来两个stu的对象
- 直接修改对象上的属性, 是给本对象添加了一个新属性
- stu1.name = "kobe"
- 获取引用, 修改引用中的值, 会相互影响
- stu1.friends.push("kobe")
- 3、在前面实现类的过程中都没有传递参数
- var stu3 = new Student("lilei", 112)
借用构造函数继承
解决上面那个方法的第三个缺点:(解决了第三个,12就随着都解决了)因为真是开发的时候,是要接收参数,创建不同的,所以这里不满足,解决办法有如下:
其实是想把2个东西继承下来,
- 1是父类中对属性赋值的相关代码
- 2是父类原型上的fun
这个属于公共的东西,放在子类里来写,是不合适不对的,
那应该怎么做呢?Person.call(this, name, age, friends)
// 父类: 公共属性和方法
function Person(name, age, friends) {
// this = stu
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
var stu = new Student("why", 18, ["kobe"], 111)
缺点:
- Person函数至少被调用了两次
- stu的原型对象上会多出一些属性, 但是这些属性是没有存在的必要
父类原型赋值给子类
// 直接将父类的原型赋值给子类, 作为子类的原型
Student.prototype = Person.prototype
这种方法可以运行吗?可以的,但是如果后期在子类原型上添加一些东西,就都加到父类上了,如果有一堆子类,也都加到父类上了,从道理来说,明明是给子类添加的,现在添加到父类了,也是不对的
原型式继承函数(对象)
不用new Father(),
新创建一个对象,用原来某个对象作为新创建对象的原型
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
// 原型式继承函数
function createObject1(o) {
var newObj = {}
Object.setPrototypeOf(newObj, o)
return newObj
}
function createObject2(o) {
function Fn() {}
Fn.prototype = o
var newObj = new Fn()
return newObj
}
// var info = createObject2(obj)
var info = Object.create(obj)
console.log(info)
console.log(info.__proto__)
弊端:
- 扩展的时候
寄生式继承(对象)
把原生式继承,放到一个工厂函数里,在工厂函数里对原来的某一个对象做扩展,
var personObj = {
running: function() {
console.log("running")
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.studying = function() {
console.log("studying~")
}
return stu
}
var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")
弊端:
- 方法在每个实例里都有一份,不ok
- 实例通过函数创建的,不是new出来的,不ok
寄生组合式继承
第一步,继承属性:
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function() {
console.log("running~")
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
Student.prototype.studying = function() {
console.log("studying~")
}
关键的来咯
Student.prototype = Object.create(Person.prototype);
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function () {
console.log("running~");
};
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
Student.prototype = Object.create(Person.prototype);
Object.defineProperties(Student.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Student,
});
Student.prototype.studying = function () {
console.log("studying~");
};
抽一下
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Objec.create(SuperType.prototype);
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType,
});
}
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function () {
console.log("running~");
};
Person.prototype.eating = function () {
console.log("eating~");
};
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
inheritPrototype(Student, Person);
Student.prototype.studying = function () {
console.log("studying~");
};
var stu = new Student("why", 18, ["kobe"], 111, 100);
console.log(stu);
stu.studying();
stu.running();
stu.eating();
思考
这种方法是创建了一个新对象,让这个新对象的原型指向父类的原型,
那么我的疑惑是:为什么不可以把子类的原型的原型直接指向父类的原型呢?
这样就可以少创建一个空对象
红的就是那个寄生组合继承,我一开始想的是黄的,但是因为__proto__不推荐,我改成了蓝色的那个
完全行得通,但是没有人这样做,为什么呢?
这样就可以少创建一个空对象啊。
补充:
JS中proto和 prototype 存在的意义是什么?
引用自:www.zhihu.com/question/56…
先有object还是现有function
class
定义
定义类(不存在变量提升,与继承有关,子类必须在父类后面)
// 类的声明
class Person {
}
//类的表达式
var Animal = class {
}
类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
构造函数
添加参数
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
-
当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
-
p1.在内存中创建一个新的对象(空对象);
-
p2.这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
-
p3.构造函数内部的this,会指向创建出来的新对象;
-
p4.执行构造函数的内部代码(函数体代码);
-
p5.如果构造函数没有返回非空对象,则返回创建出来的新对象;
实例方法
定义toString()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。
静态方法
类 相当于实例的原型,在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,就表示该方法不会被实例继承
而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。
父类的静态方法,可以被子类继承。
访问器方法
我们之前讲对象的属性描述符时有讲过对象可以添加setter和getter函数的,那么类也是可以的
静态属性
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
//——————————————————————
// 老写法
class Foo {
// ...
}
Foo.prop = 1;
// 新写法
class Foo {
static prop = 1;
}
实例属性
实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。
class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
上面代码中,实例属性this._count定义在constructor()方法里面。另一种写法是,这个属性也可以定义在类的最顶层,其他都不变。
class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
上面代码中,实例属性_count与取值函数value()和increment()方法,处于同一个层级。这时,不需要在实例属性前面加上this。
这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
class foo {
bar = 'hello';
baz = 'world';
constructor() {
// ...
}
}
私有方法和私有属性
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
new.target
new是从构造函数生成实例对象的命令。
ES6 为new命令引入了一个new.target属性
该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。
如果构造函数不是通过new命令或Reflect.construct()调用的
new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
上面代码确保构造函数只能通过new命令调用。
为什么呢?
可是为什么要有这么多不同的方法,各自的应用场景又是什么呢?老师没讲,我觉得只记住这几种不难,但是理解了为什么要有这么多不同的方法,可以理解本质
我搜索了大量文章,借鉴了如下,静态方法和实例方法的区别以及如何恰当使用
- 如果打算创建静态方法将使用的任何静态属性,请考虑创建实例方法。它将允许您使用多个实例。它还允许您控制何时可以释放绑定的内存。
- 如果您认为将来有机会添加条件逻辑,请使用实例方法。通过利用Redefinition,利用多态来使设计更加灵活
- 静态仅应用于对象创建的设计模式,例如Singleton,Factory Method,Abstract Factory,Singleton Factory,以方便对象创建。
- 静态应该用于纯实用程序类,而不用于助手类。
但是我没理解他说了点啥
class继承
super
super这个关键字,既可以当作函数使用,也可以当作对象使用。
super作为函数调用时
代表父类的构造函数。子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
constructor() {
super();
}
}
super()在这里相当于A.prototype.constructor.call(this)。
super()只能用在子类的构造函数之中,用在其他地方就会报错。
class A {}
class B extends A {
m() {
super(); // 报错
}
}
作为对象
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。