JS-原型链继承

272 阅读4分钟

原型

prototype是function对象的一个属性,构造函数实例化以后出现的结果,定义构造函数构造出的每个实例对象的公共祖先。一般配置项写在构造函数里,不变的属性和方法写在原型里,并且无法通过实例化对象影响原型上的属性和方法。

constructor:原型上的构造器,指向构造函数。可以通过这个属性更改指向的构造函数,必须要经过实例化才会生效。要注意Test.prototype.method = function(){}和Test.prototype = {}的区别就是,后者会改变constructor,前者还是原来的constructor。

proto:属于实例化对象,必须构造函数实例化之后,才会出现这个属性,指向原型。可以通过这个属性更改指向的原型。

function Car(){}
Car.prototype.name = 'Mazda';
var car = new Car();

Car.prototype.name = 'Benz';
console.log(car.name); //'Benz'
console.log(Car.prototype);

这么做就是对属性的重新赋值(修改的同一个实例对象的prototype),放new前面和后面都没有关系

function Car(){}
Car.prototype.name = 'Mazda';
var car = new Car();

Car.prototype = {
    name: 'Benz' //修改的是constructor里面的值,而constructor要实例化之后才会生效
}
console.log(car.name); //'Mazda'

这么做就不一样了,Car.prototype = {}这种形式的原型编写是把整个原型都重写了。并且本例中的原型修改是在实例化之后修改的(constructor不一样),也就是说两个prototype不是同一个实例化对象的原型

时间线梳理:

  1. 没有实例化之前:Car.prototype.constructor -> Car() -> Prototype -> name: Mazda
  2. 实例化之后:constructor生效
this{
	__proto__: Car.prototype {
		name: 'Mazda'
	}
}
  1. 重写原型(constructor改变):Car.prototype.constructor -> Car() -> Prototype -> name: Banz
  2. 未实例化,所以打印的结果是第一次实例化的结果

再次实例化可以看到两个结果

function Car(){}
Car.prototype.name = 'Mazda';
var car1 = new Car();

Car.prototype = {
    name: 'Banz'
}
var car2 = new Car();
console.log(car1, car2);

原型链

原型链:沿着__proto__找原型,形成链条式的继承关系

原型链的顶端:Object.prototype( 不是Object!!!)

Object.prototype里面有toString()方法

子级可以更改父级的引用值属性,原始值不行(复制处理)

//教授
Professor.prototype.tSkill = 'JAVA';
function Professor(){}
var professor = new Professor();

//老师
Teacher.prototype = professor;
function Teacher(){
	this.mSkill = 'JS/JQ';
	this.students = 200;
	this.data = {
		name: 'jackson'
	}
}
var teacher = new Teacher();

//学生
Student.prototype = teacher;
function Student(){
	this.pSkill = 'HTML/CSS'
}
var student = new Student();

//student没有这个属性,但是会复制并在处理之后进行添加,teacher原始值不变
student.students++; //201 200
student.data.name = 'eden';// eden eden 引用值都发生了改变
console.log(student, teacher);

就相当于:

var obj = {
    name: 'win'
}
obj.age = 5;
console.log(obj);

这样就会有age属性了

this谁调用指向谁

function Car(){
	this.brand = 'benz'
}
Car.prototype = {
	brand:'Mazda',
	intro: function(){
		console.log(this.brand);
	}
}
var car = new Car();
car.intro();//'benz'
car.prototype.intro();//'mazda'
function Person(){
	//{
	//	weight: 129 //从原型中复制,经过方法计算后,添加进this
	//}
	this.smoke = function(){
		this.weight--;
	}
}
Person.prototype = {
	weight: 130 //不变
}
var Person = new Person();

打印person -> 129

打印Person.prototype -> 130

Object.create(对象,null) 创建对象

可以自定义原型,构建对象

function Obj(){}
Obj.prototype.num = 1;
var obj1 = Object.create(Obj.prototype); //以Obj.prototype为原型创建实例对象
var obj2 = new Obj();
console.log(obj1, obj2);

做出来的两个obj是一模一样的

new方法:

  1. 实例化obj2
  2. 调用构造函数Obj的初始化属性和方法
  3. 指定实例对象的原型Obj.prototype

Object.create():

var obj1 = Object.create(null); //创建obj1空对象
console.log(obj1); //真·什么都没有·对象
obj1.num = 1;
var obj2 = Object.create(obj1);  //原型是obj1,自定义原型
console.log(obj2);
console.log(obj2.num); //1

可以把其他对象作为原型继承

Object.create(null)的原型

并不是所有对象都继承于Obj.prototype,Object.create(null)就不是

自己指定的__proto__是没用的,必须要用系统的

可以更改,但不能自造

var obj1 = Object.create(null);
obj1.num = 1;
var obj1 = {
    count: 2
}
obj.__proto__ = obj1;
console.log(obj.count);  //undefined

继承方法:

1. 原型链继承

Professor.prototype.tSkill = 'JAVA';
function Professor(){}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher(){}
var teacher = new Teacher();
console.log(teacher);

让构造函数作为原型,但是没有必要把Professor和Professor.prototype的东西全部继承

2. call/apply

其实这种方法不是继承,而是借用,不会继承Teacher的原型

Teacher.prototype.wife = 'Ms.Liu';
function Teacher(name, mSkill){
    this.name = name;
    this.mSkill = mSkill;
}
function Student(name, mSkill, age){
    Teacher.apply(this, [name, mSkill]);
    this.age = age;
}
var student = new Student(
    'Mr.Zhang', 'JS', 20
);
console.log(student)

3. 公共原型

原型相等Student.prototype = Teacher.prototype,改一个,另外一个跟着改

function Teacher(){
    this.name = 'Ms.Liu';
    this.mSkill = 'JS';
}
Teacher.prototype = {
    pSkill: 'JAVA'
}
var t = new Teacher();
console.log(t);

function Student(){
    this.name = 'eden';
}

Student.prototype = Teacher.prototype;

Student.prototype.age = 18;
var s = new Student();
console.log(s);

4. 添加缓冲函数(圣杯模式)

function Teacher(){
    this.name = 'Jackson';
    this.tSkill = 'JAVA';
}
Teacher.prototype = {
    pSkill: 'JS'
}
var t = new Teacher();
console.log(t);

function Student(){
    this.name = 'eden';
}

//建立一个缓冲函数,将student和teacher隔离开来
function Buffer(){}

//Buffer什么都没有,但是原型继承自Teacher.prototype
//只要Buffer.prototype不变,Teacher.prototype也不会变
Buffer.prototype = Teacher.prototype;

var buffer = new Buffer();
Student.prototype = buffer;

var s = new Student();
console.log(s)

s -> Student -> Student.prototype = buffer -> Buffer.prototype = Teacher.prototype

这样做既不会继承Teacher构造函数上的属性,也不会让student和teacher之间相互影响。

封装

function inherit(Target, Origin){
    function Buffer(){}
    Buffer.prototype = Origin.prototype;
    Target.prototype = new Buffer();
    //构造器指向纠正
    Target.prototype.constructor = Target;
    // 继承源(超类
    Target.prototype.super_class = Origin;
}

改良封装的函数:立即执行函数会有自己的作用域,自己的命名空间

var inherit = (function(){
    var Buffer = function(){}; //私有变量
    return function (Target, Origin){
        Buffer.prototype = Origin.prototype;
        Target.prototype = new Buffer();
        Target.prototype.constructor = Target;
        Target.prototype.super_class = Origin;
    }
})();