False Begginer - 10. 原型与原型链的深入、对象的继承

108 阅读7分钟

10.1 原型链是什么?原型链和原型是什么关系?为什么要叫原型链呢?

function Car() {};
var car = new Car();
console.log(Car.prototype); // { constructor:function Car(){} }
console.log(car); // {}

// 我们知道,在new关键字通过对构造函数进行实例化的时候,在构造函数内创建一个this空对象,而this空对象中存在__proto__属性里面存放其构造函数的prototype属性。Car.prototype原型和car实例都是对象,那么Car.prototype有__proto__属性吗?

console.log(Car.prototype); // { constructor:function Car(){}, __proto__ : Object} 

10.1.1 每一个对象都默认有__proto__属性,其存放着构造函数的prototype属性。

10.1.2 每一个对象的原型都有原型,包括原型本身。

Professor.prototype.tSkill = 'Java';
function Professor() {};
var professor = new Professor();
Teacher.prototype = professor;
function Teacher() {
	this.mSkill = 'JS/JQ';
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student() {
	this.pSkill = 'HTML/CSS';
}
var student = new Student();
console.log(student);

// student继承关系:
student = {
	pSkill:'HTML/CSS',
  __proto__:teacher = {
  	mSkill:'JS/JQ',
    __proto__:professor = {
    	__proto__:Professor.prototype = {
      	tSkill:'Java',
        __proto__:Object.prototype = {
        	__proto__:null;
        }
      }
    }
  }
}

10.1.3 proto:沿着__proto__去找(原型)里的属性一层一层的去继承原型里的属性的这条链条叫做原型链,这层继承关系就叫做原型链。

10.2 原型链的终点是什么?

// 原型链的终点是Object.prototype
var obj = {};
console.log(obj.__proto__); // Object.prototype原型

console.log(Object.prototype.__proto__ === null); // true

10.3 特殊的原型Function

// 在底层,一个对象是有一个特性,我们可以叫做SLOT,这个SLOT就是[[prototype]],实际上存储的就是__proto__,也就是说对象底层都有__proto__属性
function Foo() {};
var f1 = new Foo();
console.log(f1.__proto__ === Foo.prototype); // true
console.log(Foo.prototype.constructor === Foo); // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype === null);

// 证明Function是函数
var Foo = new Function('a', 'b', 'return a + b');
Foo(1, 2); // 3

// Function是函数吗?Function是函数,是函数就是就是对象,那对象的__proto__是谁呢?Foo就是Function函数构造出来的实例,所以Foo.__proto__就是Function.prototype原型

console.log(Function.prototype.constructor === Function); // true
console.log(Foo.__proto__ === Function.prototype); // true



// 证明Object是函数
var obj = new Object();
console.log(obj); // {}

// Object是函数吗?Object是函数,是函数就是对象,那对象的__proto__是谁呢?那Object既然是函数,那么就是由Function构造出来的实例,所以Object.__proto__就是Function.prototype原型

console.log(Object.__proto__ === Function.prototype); // true



// 证明Number是函数
var num = new Number(1);
console.log(num);  // { 1 }

// Number是函数吗?Number是函数,是函数就是对象,那对象的__proto__是谁呢?那Number既然是函数,那么就是由Function构造出来的实例,所以Number.__proto__就是Function.prototype原型
console.log(Number.__proto__ === Function.prototype); // true


// 证明Function是函数
var Foo = new Foo('a', 'b', 'return a + b');
Foo(1, 2); // 3

// Function是函数吗?Function是函数,是函数就是对象,那对象的__proto__是谁呢?那Function既然是函数,那么就是由Function构造出来的实例,所以Function.__proto__就是Function.prototype原型

console.log(Function.__proto__ === Function.prototype); // true


// 那么Function.prototype.__proto__是谁呢?因为Function.prototype是对象,既然是对象那就是通过Object构造出来的实例,所以Funciton.prototype.__proto__就是Object.prototype

console.loog(Function.prototype.__proto__ === Object.prototype); // true

10.4 关于实例对象修改原型属性的问题?

10.4.1 引用类型值的属性

Professor.prototype.tSkill = 'Java';
function Professor() {};
var professor = new Professor();
Teacher.prototype = professor;
function Teacher() {
	this.mSkill = 'JS/JQ';
  this.students = {
  	alibaba:100,
    baidu:200
  }
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student() {
	this.pSkill = 'HTML/CSS';
};
var student = new Student();

// student.students不是实例对象,而是Teacher实例上的属性,所以当然可以修改成功。
student.students.alibaba = 200;
student.students.baidu = 100;
console.log(student); // { pSkill: 'HTML/CSS' }
console.log(teacher); // { mSkill: 'JS/JQ', students = { alibaba:200,baidu:100 } };

// 此时实例对象student中并没有students属性内部alibaba属性,如果直接设置alibaba属性就会报错,如果有students属性,那么就相当于给实例对象student的students属性内部的alibaba属性赋值。
var student = {};
student.students.alibaba = 300; // Uncaught TypeError: Cannot set properties of undefined;

10.4.2 原始类型值和引用类型值

// 实例对象是不能修改原型上的方法和属性的,也就是说子类不能修改父类上的属性和方法。只会在实例对象本身添加相应的属性和方法。

Professor.prototype.tSkill = 'JAVA';
function Professor() {};
var professor = new Professor();
Teacher.prototype = professor;
function Teacher() {
	this.mSkill = 'JS/JQ';
  this.students = 100;
  this.obj = {
  	num : 2
  }
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student() {
	this.pSkill = 'HTML/CSS';
}
var student = new Student();

// 修改原型上原始类型属性,先通过原型查找到属性,然后给实例对象添加计算后的属性,并不影响原型。
student.students++;
console.log(student); // { pSkill:'HTML/CSS', students:101 }
console.log(teacher); // { mSkill:'JS/JQ', students:100 }

// 修改原型上引用类型属性,相当于给实例对象添加属性,并不影响原型。
student.obj = { num: 3 };
console.log(student); // { pSkill:'HTML/CSS', obj:{ num:3 } };
console.log(teacher); // { mSkill:'JS/JQ',students:100, obj:{ num: 2 } };

// 实例对象不能修改原型上的原始类型属性,student.students++ ---> student.students + 1 ---> students = 100 + 1 = 101, 系统会将students属性添加到实例对象上,但是不会修改原型上的students属性。
function Person() {
	this.smoke = function() {
  	this.weight--;
  }
}
Person.prototype = {
	weight:130
}
var person = new Person();

person.smoke();
console.log(person); // { smoke:function(){}, weight : 129 };
console.log(Person.prototype); // { weight : 130 }

// this.weight-- ---> this.weight - 1 --> 130 - 1 = 129 ---> 添加到实力对象上,但不会改变原型上的weight属性

10.5 this指向问题

function Car() {
  this.brand = 'Benz';
}
Car.prototype = {
	brand : 'Mazada',
  intro : function() {
		console.log('我是' + this.brand + '车');
	}
}
var car = new Car();

// 打印的结果是什么?
car.intro();  // 我是Benz车

// 为什么是Benz呢?为什么this指代的实例对象呢?
// 因为在构造函数的时候,关键字new创建空对象this,然后car.intro() --> this.intro(),实例对象car并没有intro方法,但是会顺着原型链去原型里面找,然后原型intro方法中的this就指代实例对象本身,所以打印Benz;
function Car() {
	this = {
  	brand:'Benz',
    __proto__ : Car.prototype
  }
}

// 那怎么样说出原型的brand属性值呢?

Car.prototype.intro(); // 我是Mazada车

// 也就是说,谁调用this,this就指代哪个对象。

10.6 对象创建## 与继承

// 通常对象的创建有以下方式:
var obj = {};
var obj2 = new Object();

console.log(obj); // {}
console.log(obj2); // {}

obj.__proto__.constructor === Object // true
obj2.__proto__.constructor === Object // true

// 以上的两种创建方式,创建出来的对象没有任何区别,实际上,在通过var obj = {},obj是系统隐式的通过new Object()构造出来的实例对象。


// 自定义构造函数的实例对象的原型的constructor属性指向自定义构造函数本身。
function Foo() {};
var obj3 = new Foo();
obj3.__proto__.constructor === Foo; // true



// 原型的原型是由系统自带的Object构造函数构造出来的实例对象
function Obj() { }
var obj = new Obj();
console.log(obj.__proto__.__proto__.constructor); // Object构造函数

10.7 Object.create()方法

// Object.create();方法创建一个对象,并且指定该对象的原型。
// Object.create():
	// 参数1:可以是对象,可以是null,创建对象的原型对象
	// 参数2:属性对象,后期再补.....................................
  
function Obj() {};
Obj.prototype.num = 1;
var obj = new Obj(); 
console.log(obj); // {}

var obj2 = Object.create(Obj.prototype);
console.log(obj2); // {}
console.log(obj2.num); // 1

// 通过Object.create();方法,obj和obj2的__proto__属性都储存Obj.prototype的属性,也就是说两个实例对象同一个原型。

new的功能:
	1. 实例化对象
  2. 调用构造函数初始化属性和方法
  3. 指定实例对象的原型
//  Object.create();创建空对象

var obj = Object.create(null);
console.log(obj); // {} 没有__proto__属性

var obj2 = Object.create(obj);
console.log(obj2); // {}
console.log(obj2.__proto__); // { num: 1 } 没有__proto__属性 


// 不能自定义__proto__属性
var obj3 = Object.create(null);
obj3.__proto__ = {
	num : 1
}
console.log(obj3.num); // undefined
// 底层实现
Object.prototype.myCreate = function( proto ) {
    function F() {};
    F.prototype = proto;
    return new F();
}


// MDN上的Polyfill
if (typeof Object.create !== "function") {
    Object.create = function (proto, propertiesObject) {
        if (typeof proto !== 'object' && typeof proto !== 'function') {
            throw new TypeError('Object prototype may only be an Object: ' + proto);
        } else if (proto === null) {
            throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
        }

        if (typeof propertiesObject !== 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");

        function F() {}
        F.prototype = proto;

        return new F();
    };
}

1. 不是所有的对象都是继承于Object.prototype,Object.create(null)方法创建的对象没有__proto__属性。

2. 不能自定义__proto__属性

10.8 原型方法的重写和toString()方法

// document.write()方法需要隐式将参数转为字符串类型
var num = 1;
var obj = {};
var obj2 = Object.create(null);

document.write(num); // 1
document.write(obj); // [object Object] 对象类型的Object构造函数
document.write(obj2); // Uncaught TypeError:Cannot conver object to primitive value;

num.toString(); ---> 包装类 ---> new Number(num).toString();
obj.toString(); ---> obj.prototype.toString(); 
obj2 并没有__proto__属性,所以并没有办法将值转为字符串类型,所以报错。
// 原型方法的重写
// 从父类原型上继承的方法无法实现自己的需求,所以需要重写父类继承来的方法,例如toString()方法;

var num = new Number(1);
console.log(Object.prototype); 
console.log(num.prototype); 
// Number.prototype原型上有toString()方法,Object.prototype上也有toString()方法,两者存在继承关系,通过__proto__可以访问到,但是为什么要写两个toString()方法呢?

// Object.prototype.toString();方法是将数据类型转为字符串的形式,例如:[object Number] [object String] [object Boolean] [object Null] [object Function] [object Date] [object RegExp] [object Undefined]等

// Number.prototype.toString();只是将数字转为字符串形式,二者的功能不同,从父类继承的方法满足不了需求,所以需要重写从父类继承的方法。

10.9 call和apply方法

// call apply 方法更改this指向

function fn() {
	console.log('a');
}

fn(); // fn() ---> fn.call(); 非常重要,在调用函数时,系统自动加了.call();



// 更改this指向
function Car(brand, color) {
	this.brand = brand;
  this.color = color;
}
var newCar = {};
Car.call(newCar, 'benz', 'blue');
Car.apply(newCar, ['benz', 'red']);
---> 相当于
function Car(brand, color) {
	newCar.brand = brand;
  newCar.color = color;
}
console.log(newCar); // { brand:'benz', color:'red' };
// call和apply改变this的指向,继承实例对象上的方法。
function Compute() {
	this.plus = function(a, b) {
  	console.log(a + b);
  };
  this.minus = function(a, b) {
  	console.log(a - b);
  };
}

function FullCompute() {
	Compute.call(this);
  this.div = function(a, b) {
  	console.log(a / b);
  };
  this.mul = function(a, b) {
  	console.log(a * b);
  };
}

var fullCompute = new FullCompute();
fullCompute.plus(1, 2); // 3
fullCompute.minus(1, 2); // -1
fullCompute.div(1, 2); // 0.5 
fullCompute.mul(1, 2); // 2