继承与原型

42 阅读4分钟

复杂数据类型的引用

function person(){...}
var x = person;  // 不会创建 person 的副本,是引用
//使用new才会创建副本

如果修改 x ,person 的属性也会改变:

var person = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"}
var x = person;
x.age = 10;           //  x.age 和 person.age 都会改变
创建对象三种方法
  1. var a={ }
  2. var a=new Object( )
  3. 构造函数

原型对象 prototype

what

所有的 JavaScript 对象都会有一个 prototype 属性,其指向 prototype 对象(原型对象)

所有对象都会从原型对象中继承属性和方法

prototype作用

prototype可以用来存放共享方法,因为如果方法写在构造器中,每次创建实例都会为方法开辟空间。

属性添加位置
function Person(first, last) {
  this.firstName = first;//实例成员
  this.lastName = last;//实例成员
}
var p=new Person();
Person.nationality = "English";//静态成员
console.log(Person.nationality)//English,静态成员通过函数名访问
console.log(p.nationality)//undifined,静态成员无法通过实例访问

所以要添加一个新的属性需要在构造器函数中添加:

function Person(first, last) {
  this.firstName = first;
  this.lastName = last;
  this.nationality = "English";
}

prototype,__ proto __

__ proto __,构造函数prototype属性都指向prototype原型对象,

(prototype)原型对象中有constructor,__ proto__,和其他手动添加的属性和方法,

注意区分:

实例中包含 在构造器中添加的属性和方法,__ proto__ ;//实例. __ proto__

而prototype是构造函数的属性,指向prototype对象,实例没有prototype;//构造函数.prototype

image.png

注: 在chrome,Firefox,safari中__ proto__就是[[ Prototyoe ]],其他环境下没有[[ Prototyoe ]]的标准方式

new的过程

1.在内存中创建空对象

2.让this指向空对象

3.执行构造函数的代码,给空对象加属性和方法

4.返回这个对象(所以构造函数里不需要 return ;如果构造函数写了return,按写的return执行)

var obj = {};//创建一个空对象(即{});
obj.__proto__= ClassA.prototype;//为创建的对象添加属性__proto__,将该属性指向构造函数的prototype对象;
const result= ClassA.call(obj);  //新对象调用函数,函数中的this被指向新实例对象;
//最后将初始化完毕的新对象地址return,保存到等号左边的变量中
if(result&&typeof result==='object'||typeof result==='function'){
    return result;
}//考虑到构造函数ClassA中是否有return,若有对象或函数return,就return result
return obj;

new 和不 new的区别:

  • 如果 new 了函数内的 this 会指向当前这个 person 并且就算函数内部不 return 也会返回一个对象。
  • 如果不 new 的话函数内的 this 指向的是 window。
function person(firstname,lastname,age,eyecolor)
{
    this.firstname=firstname;
    this.lastname=lastname;
    this.age=age;
    this.eyecolor=eyecolor;
    return [this.firstname,this.lastname,this.age,this.eyecolor,this] //
}
var myFather=new person("John","Doe",50,"blue");
var myMother=person("Sally","Rally",48,"green");
console.log(myFather) // this 输出一个 person 对象
console.log(myMother) // this 输出 window 对象

prototype.constructor指向原来的构造函数

// 构造器内定义属性
function Fun(a, b) {
  this.a = a;
  this.b = b;
}
// 原型属性定义方法
Fun.prototype.c = function() {
  return this.a + this.b;
}
var a=new Fun(1,2);
    //prototype.constructor指向原来的构造函数,如:
console.log(Fun.prototype.constructor)//function Fun(a, b) {this.a = a;this.b = b;}

慎用字面量方式来定义 prototype 属性和方法

// 注意,千万不要使用字面量方式来定义属性和方法,否则原有属性和方法会被重写:
function Fn() {};
// 定义属性
Fn.prototype.a = 1;
Fn.prototype.b = 2;
// 字面量定义方法,原型被重写,原有属性和方法被更新
Fn.prototype = {
    //使用    constructor:Fn;     手动补救,可以指回原来的构造函数
  c : function() {
    return this.a + this.b;
  }
}
var foo = new Fn();
foo.c();  // NaN
foo.a;  // undefined
foo.b;  // undefined

class类

class本质是构造函数的语法糖

class demoName{
    constructor(){
        a=10,
         name="hai"
    },
    demoMethods1(){
        this.demoMethod2();//this不能忘了加
    },
    demoMethods2(){
    }
}

继承

1.原型链继承

个人理解:把父类的东西备份一个到子类中,需要的时候就沿着链去备份中找,而且实例共用一个备份

function Animal(){
    this.colors=["black","white"];
}
Animal.prototype.getColor=function(){
    return this.colors;
}
function Dog(){};
Dog.prototype=new Animal();//dog1和dog2引用了同一个原型对象
let dog1=new Dog();
dog1.colors.push("brown");//将Dog.prototype改变,但不会改变Animal的colors
let an=new Animal();
console.log(an.colors);//["black", "white"]
let dog2=new Dog();
console.log(dog2.colors)//  ["black", "white", "brown"]

缺点:

  1. 原型中的引用将被所有实例共享,实例更改相互影响,如上colors的更改
  2. 子类在实例化时不能给父类构造函数传入参数,如上:new Dog( )不能给Animal( )传参

2.盗用构造函数实现继承

理解:相当于把父类构造器的内容搬到子类构造器运行

如:Animal.call(this,name)

function Animal(){
    this.name=name;
    this.getName=function(){//前面说过,方法最好不要写在constructor中,下个方法会优化
        return this.name;
    }
}
Animal.prototype.getName=function(){
    return "hello";
}
function Dog(name){ //可以传入参数了
    Animal.call(this,name);//继承父类constructor中的内容
}
/*  实例中包含   constructor中添加的属性和方法,__proto__  */
Dog.prototype=new Animal(); //继承父类 实例的__proto__指向的 prototype的内容//以下是一些尝试,看看构造函数和实例里面都有什么
let r=new Dog("r");
console.log(Dog.prototype.__proto__.getName);
//Dog.prototype是实例,别用prototype,上面没有
console.log(Dog.prototype.__proto__.getName===r.getName);//false
console.log(r.__proto__.__proto__.getName);//即Dog.prototype.__proto__.getName

3.盗用构造函数结合原型链实现继承

function Animal(){
    this.name=name;
}
Animal.prototype.getName=function(){    //实例共用方法,不用反复创建
    return this.name;
}
function Dog(name){ 
    Animal.call(this,name);//继承父类constructor中的内容
}
Dog.prototype=new Animal(); //继承父类prototype的内容
Dog.prototype.constructor=Dog;//补上constructor;

4.寄生组合式继承

//原来
Dog.prototype=new Animal();
Dog.prototype.constructor=Dog;
//寄生组合式
Dog.prototype=Object.create(Animal.prototype);
//过滤了Animal中的constructor部分,不用重复继承多余的constructor
Dog.prototype.constructor=Dog;
Object.create()//已简化

function inheeritPrototype(son,father){
    function obj(father_proto){
        function f(){};
        f.prototype=father_proto;
        return new f();
    };
    let prototypeS=new obj(father.prototype);
    prototypeS.constructor=son;
 //prtotypeS相当于father,但使用不同空间,避免了相互影响
    son.prtotype=prototypeS;
}
inheeritPrototype(Dog,Animal);