赞
踩
es5其实从严格意义上来说并没有类的写法,也没有继承的写法,如果想实现类还需要手写各种模式的构造函数(结合原型),已经十分过时了。
在es6后续语法中将其统一,提供了官方的类的声明。
虽然刚刚说es5的相关写法已经十分过时了,但架不住面试会问啊,我放在后面补充。
看代码:
class Parent { constructor(name, age) { this.name = name; // 意思是给类添加一个name属性,值为实例化后传进来的name this.age = age; } showName() { // 写方法 console.log(this.name); } // 以下是直接赋值法(这个初学者很容易忽略) cando = '吃饭' // 直接实例化对象添加属性 candoFn = function () { console.log('candoFn'); } // 直接给实例化对象添加一个方法属性 // 静态方法 static nocandoFn() { // 静态方法,给类本身添加一个方法,实例化对象拿不到 console.log('nocandoFn') } } let p1 = new Parent('xiaoqiang', 123); // 实例化一个类,获取一个新对象 p1.showName() // xiaoqiang console.log(p1.cando); // 吃饭 p1.candoFn() // candoFn // p1.nocandoFn() // 报错 console.log(Parent.cando); // undefined // Parent.candoFn() // 报错 Parent.nocandoFn() // nocandoFn
咱们把p1打印出来看看
结合图片总结下
class
能够直接声明一个类
constructor
是指定的构造函数,当实例化以后被自动执行,里面写属性,实例化后的属性都给到对象本身
类中定义的方法最终是挂在对象的原型上的,实例化对象调用时,也是通过隐式引用到原型上执行
如果是直接赋值法定义的属性和方法,就被定义到实例化对象本身上了。且类是无法拿到这些东西的,必须实例化
静态方法只有类能直接拿到
继承上面的父类:
class Parent {
constructor(name, age) {
this.name = name;
this.age = age;
this.weight = '120kg'
}
showName() {
console.log(this.name);
}
showWeight() {
console.log(this.weight);
}
}
class Child extends Parent{
}
let c1 = new Child('a')
c1.showName() // a
class Child extends Parent {
constructor(name) {
super(name) //super 必须先调这个,且当想在实例化的时候用到父类中某些定义的属性,需要手动写入参
}
}
let c1 = new Child('b')
c1.showName() // a
c1.showWeight() // 120kg
class Child extends Parent{
constructor(name, age, gender) {
super(name, age);
this.gender = gender // 子类自己的方法
}
// 可以添加子类新方法...
showGender(){
console.log('gender', this.gender)
}
}
let c1 = new Child('a', 13, 'male')
c1.showName() // a
打印出子类实例:
总结下:
extends
声明继承关系,自动继承父类方法与所有constructor
里的属性
所以如果子类不需要新增属性,那么constructor
可以省略不写
如果子类需要新增属性,写constructor
的同时,在里面必须第一个调用super()
去继承父类的this
,因为子类自己是没有this
的,并且还可以通过入参指定继承父类哪些属性;
父类的方法自动继承到子类实例的原型的原型上(如果子类改写了父类的方法,也只是添加到子类实例的原型上,原型的原型上父类的方法还在)
补充:static
的静态方法子类也是能够继承的。
继承的好处:
如果想在子类的方法中调用父类的方法,可以这样子:
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { super.speak(); //调用父类的speak方法 console.log(this.name + ' barks.'); } } let d = new Dog('Mitzie'); d.speak(); //输出:"Mitzie makes a noise." 和 "Mitzie barks."
这里有个讨论,super究竟是什么?这兄弟总结的还不错可以看看,地址
class Person { constructor(name) { this.name = name } fn = function() { console.log(this) } fn1(){ console.log(this, this.name) } fn2 = () => { console.log(this) } // 2 箭头函数,指向实例 fn3 = () => { console.log(this) } static fn4() { console.log(this) } // 4 静态属性,指向class本身 } const man = new Person('小夏') man.fn() // 打印实例 man.fn1() // 打印实例 ‘小夏’ man.fn2() // 打印实例 man.fn3.call(this) // 箭头函数不能改变指向,打印实例 Person.fn4() // class本身
总结:
有一个知识可以看看,如果是man.__proto__.fn1()
去调用,this会指向什么?
this只会是指向原型自己,因为是__proto__
自己去调用了,所以this.name
为undefined。
那有人会说man调用fn1()
也是通过原型去调的,为什么this指向就没问题,是因为JS底层帮你把this指向处理好了。
typeof 类 === 'function' // true
说明class其实是由函数包装而来的一种语法糖;
如何用原型来实现继承?这个问题主要的考察点还是你对原型对象和构造函数的理解。
可以先看我这篇文章,理解原型的概念。
接下来,让我来细细给你讲明白~
首先咱们先写个父类:
function Animal(name) {
this.name = name; // 只有一个属性,name
}
这个时候咱们去new一个变量,this就会指向这个新的变量,name就会赋值给这个变量。
let A = new Animal('dog')
console.log(A);
想加方法咋办呢?前面我们也知道es6 class里的方法最终是加在了实例对象的原型上,这个原型其实就是构造函数Animal的原型。
下面我为了图方便,把实例对象上的__proto__也写成prototype哈哈,能明白就行
所以咱们加方法的话就是直接给Animal的原型加上即可:
Animal.prototype.sayHello = function () {
console.log("Hello, my name is " + this.name);
};
console.log(A);
咱们可以用class写个同样的父类然后实例化打印出来看看:
class Animal1 {
constructor(name) {
this.name = name
}
sayHello() {
console.log("Hello, my name is " + this.name);
}
}
let animal1 = new Animal1('animal1')
console.log('animal1', animal1);
看起来差不多
来写子类,并且先只考虑继承父类的属性name
function Dog(name, breed) {
Animal(name) // Animal本质是个函数
this.breed = breed;
}
// 相当于
function Dog(name, breed) {
this.name = name
this.breed = breed;
}
但是!!!!咱们这里要考虑this的指向,当我们执行Animal()
时,他里面的this是指向window的。
this的指向看我这篇文章,很好懂的
这时候你去实例化子类
function Animal(name) {
console.log('Animal+this', this); // 父级补充个打印吧
this.name = name;
}
let dog = new Dog('wang', '1')
console.log('dog', dog);
因为this并不是指向新对象上,所以可以看到name没有加在实例化子类对象上,加到window上了哈哈哈哈。
咋办?改写this指向呗!
function Dog(name, breed) {
Animal.call(this, name); // 把Animal里的this强行指向子类
this.breed = breed;
}
完美还差一步,咋继承父类的方法?
咱都知道原型链可以找到不属于自己身上的属性或方法,你想想,是不是可以在Dog.prototype
和最高层的Object的原型中间夹一层Animal.prototype
呢?
说干就干!
Dog.prototype = Object.create(Animal.prototype); // 以Animal.prototype为一个空对象的原型,创建一个新的空对象,赋值给Dog.prototype
是不是有点晕,画个图给你看就明白了!
你会发现打印出来后,新的原型对象原型少了constructor,咱给加上:
Dog.prototype.constructor = Dog; // 还原 constructor
这个新的原型对象就是Dog的新原型对象,他的prototype属性指向父类的原型对象。
所以这时候就可以直接在dog对象上调用父类方法了,利用的就是原型链模拟继承。
所以以后给子类加新方法直接:
Dog.prototype.bark = function () {
console.log("Woof!");
};
对比下class写法:
class Animal1 { constructor(name) { this.name = name } sayHello() { console.log("Hello, my name is " + this.name); } } // let animal1 = new Animal1('animal1') // console.log('animal1', animal1); class Dog1 extends Animal1 { constructor(name, breed) { super(name) this.breed = breed; } bark() { console.log("Woof!"); } } let dog1 = new Dog1('dog1', '1') console.log('dog1', dog1);
基本一样了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。