js中的原型链,6种继承方式及其案例

js中的原型链,6种继承方式及其案例

我们知道面对对象(OOP)的三大特点是:继承,封装,多态(重载,重写)

js并不是严格的面对对象的语言,因为js的面对对象都是基于原型链实现的。

原型链

原型链是由__proto__串联起来的链状结构。每一个函数都有一个prototype属性,也有__proto__,每一个对象都只有一个 __proto__属性;一般我们把属性方法构造函数中,把方法挂在prototype下面,对象的 __proto__指向构造函数的prototype,原型链的终点为Object.prototype,再往上Object.prototype.__proto__=null

1.实例没有具体的构造函数

let obj={}
console.log(obj.__proto__== Object.prototype) //true
console.log(Object.prototype.__proto__)       //null

2.实例的构造函数为自定义的构造函数

function Persion(){}//构造函数
let p=new Persion()//实例
console.log(p.__proto__==Persion.prototype) //true
console.log(Persion.prototype.__proto__==Object.prototype) //true
console.log(Object.prototype.__proto__) //null

3.实例的构造函数为内置构造函数

let p = new Array()
console.log(p.__proto__ == Array.prototype) //true
console.log(Array.__proto__ ==Function.prototype)//true
console.log(Function.__proto__ == Function.prototype)//true ,Function 是构造函数,也是对象
console.log(Function.prototype.__proto__==Object.prototype)//true
console.log(Object.prototype.__proto__)//null

Function构造函数的构造函数就是他本身,Function作为实例化对象访问Function构造函数是可行的,所以Function.__proto__可以访问Function.prototype;任何函数都继承Function的所有方法以及属性,而Function是内置的构造函数,也是对象,都是继承Object的所有属性和方法,Function.prototype.__proto__=Object.prototype

首先在这里写一个Persion父类,该类有一个属性为name,有一个实例方法introduce,有一个原型方法hobby

 function Parent(name){
     this.name=name
     this.introduce=function(){
         console.log("my name is"+this.name)
     }
 }
Parent.prototype.hobby=function(hobby){
    console.log(this.name+" like "+hobby)
}

1.原型链继承

子类的原型指向父类的实例(子类的.prototype=new 父类())

function Child() {} //子类
// Child.prototype.sleep=function(){
//     console.log(this.name+"is sleeping")
// }1
Child.prototype = new Parent("yzh")
//为了不破坏原型链,将Male的constructor指向本身
Child.prototype.constructor=Child
Child.prototype.sleep=function(){
    console.log(this.name+" is sleeping")
}//2
Parent.prototype.eat=function(){
    console.log(this.name+" is eating")
}

var child=new Child()
console.log(Child)
child.introduce()
child.hobby("唱歌")
child.sleep()
child.eat()
console.log(child instanceof Parent) //true
console.log(child instanceof Child) //true

注:如上如果Male的原型方法sleep放在1位置时,会报错child.sleep is not a function,放在2位置,输出 yzh is sleeping

此种继承方法的特点:

  1. 子类与父类的关系为指向关系,实例是子类的实例,也是父类的实例
  2. 父类新增的原型方法或属性,子类都能访问
  3. 简单易用

缺点:

  1. 如果要为子类新增属性或者方法,只能在new Parent() 之后,并不能放在构造函数中,如上的代码示例,如果新增的方法放在改变子类原型的指向之前,改变指向之后新增的方法自然就没用了,子类的prototype已经指向了父类了
  2. 子类的所有实例,共用所有的父类属性,子类不能拥有自己的属性,如果有多个实例时,其中一个实例修改了父类引用类型的值,那么所有的实例都会发生改变,例如我只想其中的一个实例的eat方法改为“is eating apple”,那么所有的实例该方法都会发生改变
  3. 不能多继承,因为是改变了原型链的指向,不能指向多个父类,因此只能单继承
  4. 创建子类时,无法向父类构造函数传参,因为在改变子类的原型链指向之后,子类的属性和方法是无效的

2.构造继承(call,apply继承)

把 父对象的所有属性和方法,拷贝进子对象

function Child(name){
    Parent.call(this,name)
}
var child=new Child("yzh")
console.log(child.name) //yzh
child.introduce()
child.hobby("sing")
console.log(child instanceof Parent) //false
console.log(child instanceof Child) //true

优点:

  1. 解决了原型链继承中的子类共享父类属性的问题
  2. 创建的子类实例可以向父类传递参数
  3. 可以实现多继承,call改变父类的this

缺点:

  1. 实例是子类的实例,不是父类的
  2. 只能继承父类的实例属性和方法,不能继承父类原型上的方法
  3. 无法实现函数复用,每个子类都有父类函数的属性和方法的副本,当child调用Parent上的方法时,Parent内部的this指向的是child,Parent内部的this上的属性和方法都被复制到了child上面,如果每个子类的实例都复制一遍父类的属性和方法,就会占用很大的内存,而且当父类的方法发生改变了时,已经创建好的子类实例并不能更新方法,因为已经复制了原来的父类方法当成自己的方法了。

3.组合继承(原型链继承与构造继承)

function Child(name) {
    Parent.call(this,name) //构造继承 ,第二次调用父类
}
//原型链继承
Child.prototype=new Parent()
Child.prototype.constructor=Child

var child=new Child("yzh") //子类的实例向父类传递参数,第一次调用父类
console.log(child.name) 
child.introduce()
child.hobby("sing")
console.log(child instanceof Parent) //true
console.log(child instanceof Child) //true

优点:结合了原型链继承和构造继承的优点

  1. 子类可向父类传参
  2. 实例既是子类的实例,也是父类的实例
  3. 多个实例之间不存在公用父类的引用属性的问题
  4. 实例可以继承父类实例的属性和方法,也可以继承原型上的属性和方法

缺点:这种方式调用了两次父类的构造函数,生成了两份实例,相同的属性既存在于实例中也存在于原型中

4.拷贝继承

function Child(name){
    var parent = new Parent(name);
    for(var key in parent){
        Child.prototype[key] = parent[key];
    }
}
var child=new Child("yzh")
console.log(child.name) //yzh
child.introduce()
child.hobby("sing")
console.log(child instanceof Parent) //false
console.log(child instanceof Child) //true

优点:

  1. 支持多继承

缺点:

  1. 无法获取父类不可枚举的方法,这种方法是用for in 来遍历Parent中的属性,例如多选框的checked属性,这种就是不可枚举的属性
  2. 效率很低,内存占用高

5.寄生组合继承

寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法.

思路:不必为了指定子类的原型而调用父类的构造函数,我们所需要的无非就是父类原型的一个副本而已.本质上,就是使用寄生式继承来继承父类的原型,然后在将结果指定给子类的原型

其实就是在不需要实例化父类构造函数的情况下,也能得到父类的属性和方法,就是直接实例化一个临时的父类的副本,实现原型链继承

function Child(name) {
	Parent.call(this,name) //构造继承            
}

(function(){
    //创建一个临时的类
    var Temp=function(){}
    Temp.prototype=Parent.prototype
    //子类的原型指向父类的实例
    Child.prototype=new Temp()
})()

var p=new Child("yzh")
console.log(p.name)//yzh
p.hobby("sing")//yzh like sing
p.introduce()//my name is yzh

优点:结合组合继承的所有优点,此方法比较完美

6.ES6继承

 class Parent{
     constructor(name){
         this.name=name
     }
     introduce(){
         console.log("my name is " + this.name)
     }
     hobby(hobby){
         console.log(this.name + " like " + hobby)
     }
 }
class Child extends Parent{
    constructor(name,age){
        super(name) //构造继承,可以继承Parent构造函数上的属性
        this.age=age
    }
}
var  p = new Child("yzh")
p.introduce() //my name is yzh
p.hobby("apple")//yzh like apple
console.log(p instanceof Parent) // true
console.log(p instanceof Child) //true

我们用es6的写法让对象的写法更加的清晰,更像面对对象的编程;在这个方法中使用extends 和 super 两个关键字,在子类的constructor方法中调用super方法,用来继承父类的this对象

原型拖拽



<div class="box" id="box">box1不限定范围div>
<div class="box" id="box2">box2限定范围div>

//没有限定范围的拖拽
class Drag{
    constructor(ele){
        this.box=document.querySelector(ele)
        this.addHandler()
    }
    addHandler(){
        this.box.onmousedown=(e)=>{
            let _x=e.offsetX //鼠标按下距离元素上边界的距离
            let _y=e.offsetY
            e.preventDefault()
            //滑动与抬起事件绑定在document上
            document.onmousemove=(e)=>{
                let _left=e.clientX-_x   //e.clientX 鼠标相对浏览器顶部的距离
                let _top=e.clientY-_y
                // console.log(_left,_top) //距离文档的距离
                this.move(_left,_top)
            }
            document.onmouseup=()=>{
                document.onmousemove=null
            }
        }
    }
    move(_left,_top){
        this.box.style.left=_left+'px'
        this.box.style.top=_top+'px'
    }
}
//限定范围   继承Drag
class limitDrag extends Drag{
    constructor(ele) {
        super(ele)
    }
    //重写move函数 
    move(_left,_top){
        if(_left<0)_left=0
        if(_top<0)_top=0
        if(_left>document.body.offsetWidth-this.box.offsetWidth) 			   						_left=document.body.offsetWidth-this.box.offsetWidth
        if(_top>document.body.offsetHeight-this.box.offsetHeight) 									_top=document.body.offsetHeight-this.box.offsetHeight
        this.box.style.left=_left+'px'
        this.box.style.top=_top+'px'					
    }
}
new Drag('#box')
new limitDrag('#box2')

你可能感兴趣的:(JavaScript)