原型链
实例对象与Object原型对象之间的链条称为原型链
原型模式的访问机制(原型搜索机制)
- 读取实例对象的属性时,先从实例对象本身开始搜索。如果在实例中找到了这个属性,则返回该属性的值;
- 如果没有找到,则继续搜索实例的原型对象,如果在原型对象中找到了这个属性,则返回该属性的值
- 如果还是没找到,则向原型对象的原型对象查找,依此类推,直到Object的原型对象(最顶层对象);
- 如果再Object的原型对象中还搜索不到,则抛出错误;
备注:实例对象属性–>其原型对象–>原型对象的原型对象-…>Object的原型对象
继承
继承是面向对象中一个非常重要的特征。指的是:子类继承父类的属性和方法。
我们可以通过继承的方式, 在父类的属性和方法基础上, 让子类也拥有这些属性和方法, 并可以扩展。
一、继承方式
(一).原型链继承:子类的原型对象指向父类的实例对象
缺点:无法直接在实例化子类的同时,给父类的属性赋值
function Animal(jiaosheng){
this.jiaosheng = jiaosheng;
}
Animal.prototype.eat = function(){console.log("吃")}
function Dog(name,jiaosheng){
// this:狗的实例对象
Animal.call(this,jiaosheng);
this.name = name;
}
Dog.prototype = new Animal();
Dog.prototype.tian = function(){console.log("舔就对了")};
Dog.prototype.fanzhi = function(){console.log("胎生")};
var wangcai = new Dog("wangcai","旺");
//狗的实例wangcai->(Dog.prototype)new Animal()->Animal.prototype->Object.prototype
console.log(wangcai);
(二).借用构造函数:
将父类的构造函数拿到子类的构造函数中,改变this的执行,同时执行该函数,记得传参。
1、call ==> 改变函数中this的指向,同时执行该函数
格式:父类构造函数.call(子类实例,参数1,参数2,参数3...)
2、apply ==> 改变this的指向,同时执行函数,但是函数的参数要为数组
格式:父类构造函数.apply(子类实例,[参数1,参数2,参数3...])
应用:
1.借用别人的方法
(1.1)借用其他构造函数的原型对象中定义的方法
(1.2)自己本身存在某个方法,但想使用原型链上其他原型对象的某个方法
2.利用apply方法,可以将参数转成数组传入
//aplly用法:借用方法
//类数组要使用map方法
var arr = [20,2,40,33,21,8,22,46,32]
//null是不改变this指向
Math.max.apply(null,arr)
//或者
function Animal(jiaosheng,age){
this.jiaosheng = jiaosheng;
this.age = age;
}
Animal.prototype.eat = function(){console.log("吃")}
function Dog(name,jiaosheng,age){
===================================================
// apply方法,参数一定要用数组包含起来。
Animal.apply(this,[jiaosheng,age]);
this.name = name;
}
Dog.prototype = new Animal();
Dog.prototype.tian = function(){console.log("舔就对了")};
Dog.prototype.fanzhi = function(){console.log("胎生")};
var wangcai = new Dog("wangcai","旺",2);
区别:call与apply的唯一区别:传参方式不同,call多个参数,apply只有两个参数,第二个参数为数组
bind()/call()/apply()区别
1、bind()改变函数中this的指向,返回新函数
2、call()改变函数中this的指向,同时执行该函数
3、apply()改变this的指向,同时执行函数,但是函数的参数要为数组
优点:解决了子类拷贝了父类的所有属性
缺点:父类存在多余的属性
组合式继承:原型链继承方法+借用构造函数拷贝属性
(三)、组合继承
由于以上继承方法的缺点,实际开发中不可能单纯的只使用一种继承方法,而是利用它们的优点,规避它们的缺点,所以就有了组合继承法
1、继承属性:借用构造函数
(1.1)只在构造函数中定义属性
2、继承方法:原型链继承
(2.1)把所有的方法写入原型对象
组合继承是最常用的继承模式。
1、缺点(原型链继承法的缺点):
(1.1)在原型对象中生成多余的属性
(1.2)多次执行父类构造函数
//原型链继承基础上补充借用构造函数
function aa(name){
this.name = name;
}
aa.prototype.eag = function(){
console.log(18);
}
function bb(home,jj){
aa.call(this,jj);//==> 让bb继承aa的属性 ==只在构造函数中定义属性
this.home = home;
}
bb.prototype = new aa();//==> 让bb继承aa的方法 ==把所有的方法写入原型对象
bb.prototype.beijing = function(){
console.log("北京")
}
bb.prototype.shengzhen = function(){
console.log("深圳")
}
var hz = new bb("贺州","小明")
console.log(hz)
// bb函数要继承aa函数的属性,需要在bb形参内加上一个形参名,并在函数内加上( 要继承的函数名.call/apply(this,形参名) ==>备注:形参名可随意写,相当于是aa中的实参。
(四)、原型式继承
1、原型式继承
核心:创建空的构造函数F,将F.prototype指向父类的prototype,将子类的prototype指向F的实例。
优点:解决原型链继承法的缺点:生成多余的属性
备注:封装的方法,可直接调用
function object(o){
function F(){}
F.prototype = o;
return new F();
}
子类的原型对象 = object(父类的原型对象);
2.借用构造函数
(2.1)将父类的构造函数拿到子类的构造函数中,改变this的指向为子类的实例,同时执行该函数,记得传参。
(2.2)寄生组合式继承:原型式继承方法+借用构造函数拷贝属性
(五)寄生组合继承法
完美的继承方法
- 核心:
- 继承属性:借用构造函数
- 继承方法:原型式继承
二、es6继承
(一).class定义类
1、写在类里面的方法实际是给Person.prototype添加方法
(1.1) constructor方法是类的默认方法,相当于在构造函数内生成属性
//用class定义
class Animal{
constructor(jiaosheng,age){
this.jiaosheng = jiaosheng;
this.age = age;
};
static eat(){
console.log("吃");
}
}
//用extends继承父类方法
class Dog extends Animal{
constructor(name,js,age){
//super方法(借用构造函数)
super(js,age);
this.name = name;
};
//加上static关键字,变为静态方法
static fanzhi(){
console.log("胎生");
};
tian(){
console.log("舔狗");
}
}
var d = new Dog("wangcai","旺",2);
Dog.eat();
Animal.eat();
(二)extends继承
格式:class 子类函数名 extends 父类函数名{}
1、子类继承了父类,在子类构造函数中必须调用super方法(借用构造函数)
2、子类的constructor方法没有调用super之前,不能使用this关键字,否则报错,而放在super方法之后就是正确的。
(三)静态方法
1、如果在一个方法前,加上static关键字,这就称为“静态方法”
static fanzhi(){
console.log("胎生");
};
(1.1) 静态方法方法不会被实例继承,而是直接通过类来调用Person.getInfo()
(1.2)父类的静态方法,可以被子类继承Man.getInfo()
闭包
1.概念:外函数内部嵌套内函数,同时将内函数返回。内部函数引入外部函数的变量及参数,不会被垃圾回收机制所收回
2.优缺点:
(2.2)可以让一个变量长期驻扎在内存当中不被释放。过度使用闭包,会占用过多的内存,造成性能问题
(2.1)闭包内的变量不会被函数外使用到
案例:有10个按钮
// 点击按钮,打印索引值
// 2.解决方案:
// * 把var改用let声明
// * 给每个按钮元素绑定一个属性
for(var i=0;i<btn.length;i++){
btn[i].idx = i;//把i绑定给idx
btn[i].onclick = function(){
console.log(this.idx);//10
}
}
*用闭包,用for循环遍历之后,外部函数,用匿名函数,生成直接使用,
再在内部生成函数,return出去使用。
for(var i=0;i<btn.length;i++){
btn[i].onclick = (function(idx){
return function(){
console.log(idx);
}
})(i);//函数后面加()就是直接使用
}
//备注:如果使用for循环遍历,直接打印,会直接显示10,因为for循环很快,你一点击就直接到10个,而为什么不是9是10,是因为到9时,i还是小于10,还是会加一次,所以会显示10。
for(let i=0;i<btn.length;i++){
btn[i].onclick = function(){
console.log(i)
}
}