<>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
此种继承方法的特点:
* 子类与父类的关系为指向关系,实例是子类的实例,也是父类的实例
* 父类新增的原型方法或属性,子类都能访问
* 简单易用
缺点:
* 如果要为子类新增属性或者方法,只能在new Parent()
之后,并不能放在构造函数中,如上的代码示例,如果新增的方法放在改变子类原型的指向之前,改变指向之后新增的方法自然就没用了,子类的prototype已经指向了父类了
*
子类的所有实例,共用所有的父类属性,子类不能拥有自己的属性,如果有多个实例时,其中一个实例修改了父类引用类型的值,那么所有的实例都会发生改变,例如我只想其中的一个实例的eat方法改为“is
eating apple”,那么所有的实例该方法都会发生改变
* 不能多继承,因为是改变了原型链的指向,不能指向多个父类,因此只能单继承
* 创建子类时,无法向父类构造函数传参,因为在改变子类的原型链指向之后,子类的属性和方法是无效的
<>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(
childinstanceof Parent) //false console.log(child instanceof Child) //true
优点:
* 解决了原型链继承中的子类共享父类属性的问题
* 创建的子类实例可以向父类传递参数
* 可以实现多继承,call改变父类的this
缺点:
* 实例是子类的实例,不是父类的
* 只能继承父类的实例属性和方法,不能继承父类原型上的方法
*
无法实现函数复用,每个子类都有父类函数的属性和方法的副本,当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
优点:结合了原型链继承和构造继承的优点
* 子类可向父类传参
* 实例既是子类的实例,也是父类的实例
* 多个实例之间不存在公用父类的引用属性的问题
* 实例可以继承父类实例的属性和方法,也可以继承原型上的属性和方法
缺点:这种方式调用了两次父类的构造函数,生成了两份实例,相同的属性既存在于实例中也存在于原型中
<>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
优点:
* 支持多继承
缺点:
* 无法获取父类不可枚举的方法,这种方法是用for in 来遍历Parent中的属性,例如多选框的checked属性,这种就是不可枚举的属性
* 效率很低,内存占用高
<>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对象
<>原型拖拽
<style type="text/css"> *{ margin: 0; padding: 0; } body,html{ width: 100%;
height: 100%; } .box{ width: 100px; height: 100px; background-color:
palevioletred; position: absolute; color: #fff; display: flex; justify-content:
center; align-items: center; padding: 10px; } </style> <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.
offsetHeightthis.box.style.left=_left+'px' this.box.style.top=_top+'px' } } new
Drag('#box') new limitDrag('#box2')