原型继承关系
1.p1是Person的实例对象
2.obj是Object的实例对象
3.Function/Object/Foo都是Function的实例对象(__proto__ 都等于 Function.prototype)
4.原型对象默认创建时, 隐式原型都是指向Object的显式原型的(Object指向null)
* 推导另外一个结论: Object是Person/Function的父类
<script>
var obj = {} // 等价于--->new Object()
obj.__proto__ // 隐式原型指向的是 Object.prototype(Object的显式原型)
function foo(){}// 等价于--->new Function()
console.log(foo.length,foo.name)
console.log(foo.__proto__)// foo的隐式原型指向的是Function的显式原型Function.prototype
console.log(foo.__proto__ === Function.prototype) // true
console.log(Person.__proto__ === Function.prototype) //true
console.log(foo.__proto__ === Person.__proto__) // true
console.log(Object.__proto__ === Function.prototype)// true
console.log(Function.__proto__ === Function.prototype)// true
// 构造函数
function Person(){}
var p1 = new Person()
var p2 = new Person()
</script>
构造函数的类方法和实例方法
类方法:是直接添加到对应的构造函数对象上面
实例方法:是直接添加到原型上面的
<script>
function Person(name,age){
this.name = name
this.age = age
}
Person.totalCounter = "70亿"
// 添加到原型上的方法称之为实例方法
Person.prototype.running = function(){
console.log(this.name,"跑步!")
}
Person.prototype.eating = function(){
console.log(this.name,"吃东西!")
}
//添加到Person本身的方法叫做---->类方法
var names = ["abc","cba","nab","bba"]
Person.randomPerson = function(){
var randomName = names[Math.floor(Math.random()*names.length)]
return new Person(randomName,Math.floor(Math.random()*100))
}
// 实例对象
// var p1 = new Person("hdc",18)
// var p2 = new Person("kebo",30)
// p1.running()
// 没有实例对象情况下,能不呢调用函数
// Person.running()// 这样是没办法调用的
Person.prototype.running()//这样可以调用但是没有意义
// 类方法可以调用
var p = Person.randomPerson()
console.log(p)
</script>
认识class定义类
◼ 我们会发现,按照前面的构造函数形式创建 类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。
在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;
但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已;
所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系;
◼ 那么,如何使用class来定义一个类呢?
可以使用两种方式来声明类:类声明和类表达式;
class Person {
}
var Student = class {
}
◼ 注意:类中定义的多个内容不需要使用,进行分割;
案例:
<script>
// 编程:高内聚低耦合
class Person{
// 1. 类中的构造函数
// 当我们通过new关键字去调用一个Person类是会默认调用class中的constructor函数方法
constructor(name,age){
this.name = name
this.age = age
}
// 2. 类中的实例方法
// 本质是放在Person.prototype 上
running(){
console.log(this.name,"在跑步")
}
eating(){
console.log(this.name,"在吃饭")
}
}
//创建实例对象
var p1 = new Person("hdc",21)
// 使用构造函数
console.log(p1.name,p1.age)
// 使用实例方法
p1.running()
p1.eating()
// 研究内容
console.log(Person.prototype === p1.__proto__) // true
console.log(Person.running)//undefined
console.log(Person.prototype)//{running: ƒ, eating: ƒ}
</script>
类的构造函数
◼ 如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢?
每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor;
当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor;
每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常;
◼ 当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
1.在内存中创建一个新的对象(空对象);
2.这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
3.构造函数内部的this,会指向创建出来的新对象;
4.执行构造函数的内部代码(函数体代码);
5.如果构造函数没有返回非空对象,则返回创建出来的新对象;
案例:
class Person{
// 1. 类中的构造函数
// 当我们通过new关键字去调用一个Person类是会默认调用class中的constructor函数方法
constructor(name,age){
this.name = name
this.age = age
}
}
类的实例方法
◼ 在上面我们定义的属性都是直接放到了this上,也就意味着它是放到了创建出来的新对象中:
在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享;
这个时候我们可以直接在类中定义;
class Person{
// 1. 类中的构造函数
// 当我们通过new关键字去调用一个Person类是会默认调用class中的constructor函数方法
constructor(name,age){
this.name = name
this.age = age
}
// 2. 类中的实例方法
// 本质是放在Person.prototype 上
running(){
console.log(this.name,"在跑步")
}
eating(){
console.log(this.name,"在吃饭")
}
}
对象访问器方法
<script>
// access
// 针对对象
// 方式一:描述符方法
var obj = {
name:"hdc"
}
Object.defineProperty(obj,"name",{
configurable:true,
enumerable:true,
set :function(){
},
get:function(){
}
})
// 方式二:直接在对象定义访问器
// 监听_name什么时候被访问,什么时候设置新的值
var obj = {
_name : "hdc",
// setter方法
set name(value){
this._name = value
},
// getter 方法
get name (){
return this._name
}
}
</script>
类的访问器方法
◼ 我们之前讲对象的属性描述符时有讲过对象可以添加setter和getter函数的,那么类也是可以的:
<script>
//访问器的编写方式
class Person{
constructor(name,age){
this._name = name
}
set name(value){
console.log("设置name")
this._name = value
}
get name(){
console.log("获取name")
return this._name
}
}
var p1 = new Person("hdc",21)
p1.name = "kebo"
console.log(p1.name)
// 2.访问器的应用场景
class Rectangle{
constructor(x,y,width,height){
this.x = x
this.y = y
this.width = width
this.height = height
}
get position(){
return{x:this.x,y:this.y}
}
get size(){
return {width:this.width,height:this.height}
}
}
var rect1 = new Rectangle(10,20,100,200)
console.log(rect1.position)
console.log(rect1.size)
</script>
类的类方法(静态方法)
◼ 静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义:
<script>
function Person(){}
// 实例方法
Person.prototype.running = function(){}
// 类方法
Person.eatting = function(){}
var p1 = new Person()
// 实例方法调用
p1.running()
// 类方法调用
Person.eatting()
// class定义类
class Student{
constructor(){}
// 实例方法
running(){}
eatting(){}
// 类方法(静态方法)
static studying(){}
}
var stu1 = new Student()
// 调用实例方法
stu1.running()
// 调用类方法
Student.studying()
</script>
ES6类的继承 - extends
◼ 前面我们花了很大的篇幅讨论了在ES5中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的。
◼ 在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承:
super关键字
◼ Class为我们的方法中还提供了super关键字:
执行 super.method(...) 来调用一个父类方法。
执行 super(...) 来调用一个父类 constructor构造方法(只能在我们的 constructor 中)
◼ 注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
◼ super的使用位置有三个:子类的构造函数方法、实例方法、静态方法;
实现继承案例
<script>
// 共同类
class Person{
constructor(name,age){
this.name = name
this.age = age
}
running(){
console.log(this.name,"跑步")
}
eatting(){
console.log(this.name,"吃东西")
}
}
// 子类1
class Student extends Person{
constructor(name,age,sno,score){
super(name,age)
this.sno = sno
this.score = score
}
studying(){
console.log("学习")
}
}
var stu1 = new Student("hdc",21,111,100)
stu1.running()
stu1.eatting()
stu1.studying()
// 子类2
class Teacher extends Person{
constructor(name,age,title){
super(name,age)
this.title = title
}
teaching(){
console.log(this.name,"教学")
}
}
var tea1 = new Teacher("hhh",50,"001")
tea1.running()
tea1.teaching()
</script>
super的其他用法
案例:
<script>
class Animal{
running (){
console.log("跑")
}
eating (){
console.log("吃")
}
// 静态方法(类方法)
static sleep(){
console.log(" animal在睡觉")
}
}
class Dog extends Animal{
// 子类如果对父类继承过来的方法不满意
// 子类可以重新实现一遍(父类方法的重写)
running(){
console.log("dog有四条腿")
// 如果想重写一部分代码可以调用super
super.running()
// console.log("跑")
// console.log("dog是四条腿在跑")
}
static sleep(){
console.log("趴着")
super.sleep()
}
}
var dog = new Dog()
dog.running() //dog有四条腿 跑
dog.eating() // 吃
Dog.sleep()// 趴着 animal在睡觉
</script>
继承内置类
◼ 我们也可以让我们的类继承自内置类,比如Array:
// 创建一个新的类继承Array进行扩展
// class HDCArray extends Array{
// get lastItem(){
// return this[this.length-1]
// }
// }
// var arr = new HDCArray(10,20,30)
// console.log(arr)
// console.log(arr.length)
// console.log(arr[0])
// console.log(arr.lastItem)
// 2.直接对Array进行扩展
Array.prototype.lastItem = function(){
return this[this.length-1]
}
var arr1 = new Array(10,20,30,40)
console.log(arr1.lastItem())
类的混入mixin
◼ JavaScript的类只支持单继承:也就是只能有一个父类
那么在开发中我们我们需要在一个类中添加更多相似的功能时,应该如何来做呢?
这个时候我们可以使用混入(mixin);
案例:
<script>
// JavaScript 只支持单继承
// 实现多个继承的方式
function mixinAnimal(BaseClass){
return class extends BaseClass{
running(){
}
}
}
function mixinRunner(BaseClass){
return class extends BaseClass{
flying(){}
}
}
class Bird{
eating(){}
}
var newBird = mixinRunner(mixinAnimal(Bird))
class newBird extends mixinRunner(mixinAnimal(Bird)){
}
var bird1 = new newBird()
// 不支持继承多个父类
// class Bird extends Animal,Flyer{
// }
</script>