ECMAScript - 原型与原型链、对象继承

一、原型与原型链

prototype VS __proto __

• 1.名称
• prototype -》原型
• _ proto_ -》原型链的链接点
• 2.从属关系
• prototype -》函数专有的一个默认属性,该属性指向一个对象
• _ proto_ -》所有Object(广义)对象都有的默认属性,该属性也指向一个对象
• 3.联系
• 所有对象的__proto__默认链接着其构造函数的prototype属性;
• 由于prototype本身也是对象,因此可以沿着__proto__ 一层一层的往上继承原型,从而形成了原型链;
• 4.原型链顶端
原型链的顶端是Object.prototype,而不是Object

function Test(){}
var test = new Test();

console.log(test.__proto__ === Test.prototype);// true

//Test.prototype也是一个Object对象,其构造函数为Object
console.log(Test.prototype.__proto__ === Object.prototype);// true 
console.log(test.__proto__.__proto__ === Object.prototype);// true

//Object.prototype为原型链的终点,该对象没有__proto__属性
console.log(Object.prototype.__proto__);//null

//Object不是原型链的终点,它的构造函数为Function
console.log(Object.__proto__ === Function.prototype);//true

//Test也是一个Object对象,其构造函数是Function
console.log(Test.__proto__ === Function.prototype);// true

//Function为最顶层的函数,构造函数就是它本身,因此它的__proto__指向它自己的prototype
console.log(Function.__proto__ === Function.prototype);// true

prototype构成

• 函数中的prototype属性也指向一个对象,该对象有两个默认属性:
1、constructor 构造器 -》默认指向 构造函数本身
2、__proto__ -》默认指向 Object.prototype

• 函数与constructor两者循环引用

console.log(Person.prototype.constructor === Person); // true

• 可以手动修改prototype对象中的constructor属性值,改变它的指向

function Person(name){
    this.name = name;
}
console.log(Person.prototype);
//{constructor: ƒ}
//  constructor: ƒ Person(name) // constructor 默认指向构造函数Person
//  __proto__: Object
function Animal(){}
Person.prototype = {
    constructor: Animal //手动修改constructor指向
}
console.log(Person.prototype);
//{constructor: ƒ}
//  constructor: ƒ Animal()  //指向新的构造函数Animal
//  __proto__: Object

• 构造函数(Person)、原型(Person.prototype)和实例(person)都是对象,三者是不同的对象

function Person(name){
    this.name = name;
}//构造函数
var person = new Person("jacky");//实例
console.log(person !== Person); // true
console.log(person !== Person.prototype); // true
console.log(Person.prototype !== Person); // true

prototype的作用

• 原型 prototype 是 构造函数 构造出来的 每个对象的公共祖先
• 需要参数进行传值的属性一般写在 this 中,固定的属性和方法写在原型 prototype上,让所有实例继承
• 查找某个属性和方法时,优先在this上查找;若this没有,则通过__proto__ 往上查找 prototype 上的属性和方法

function Person(name){
    this.name = name;
}//构造函数
Person.prototype = {
  name:"jacky",
  eat:function(food){
   console.log("I am eating "+ food);},
  },//为构造函数的prototype对象增加属性和方法
  
 var p1 = new Person('jessie');//实例化对象
 
 console.log(p1.name);// 'jessie'
 console.log(p1.eat("hamburger"));//I am eating hamburger

__proto__的作用

• __proto__的作用 -》形成原型链
• 每个Object(广义)对象都会默认创建__proto__属性,它可以看作一个容器,默认存储着构造函数.prototype
• prototype也是对象,它的__proto__指向Object.prototype -》因此所有实例都可以调用Object.prototype上的方法(除非手动指定__proto__指向null)

手动修改prototype

  • 修改属性或方法:构造函数.prototype.XX = 值或function;
  • 重写prototype:构造函数.prototype = 某对象 ;

重写prototype不会影响之前已生成的实例

• 在实例化之前,对prototype进行的修改保存在constructor所指向的构造函数所具有的prototype属性里
• 在实例化对象时,系统会从constructor里提取prototype的内容生成prototype对象,并存放到this对象的__proto__属性里
• 在生成实例化对象之后,重写prototype(注意:修改属性值 ≠ 重写),重写的prototype仍然会放入constructor里,但不会影响已生成的prototype对象

function Person(name){
    this.name = name;
}
Person.prototype.country = 'China';
var person1 = new Person('person1');
Person.prototype.country = 'Singapore';//此处是修改prototype的属性值,不是重写,因此会影响已经生成的实例化对象person1
Person.prototype = {
country:'Japan',
} //此处是重写prototype,因此不会影响已经生成的实例化对象person1
var person2 = new Person('person2');//实例化person2
console.log(person1.country); //Singapore
console.log(person2.country); //Japan

person1与person2

重写prototype后原型链顶端依然是Object.prototype(prototype = null除外)

function Professor(){
this.books = 100;
this.info = {
  age: 50,
  sex:'male',
  };
}
Professor.prototype.tSkill = 'JAVA';
var professor = new Professor();

function Student(){
this.lSkill = 'js';
}
Student.prototype = professor;//对Teacher.prototype的重写要放在实例化student之前,否则重写无效
var student = new Student();
console.log(student);
//student实例对象包含:lSkill + __proto__
//student.__proto__ === Student.prototype === professor; //手动指定
//professor实例对象包含:books、info + __proto__(链接到Professor.prototype)
//Professor.prototype包含:tSkill + constructor(指向构造函数Professor) + __proto__
//Professor.prototype.__proto__ === Object.prototype; //Professor.prototype的构造函数是Object

实例无法对原型链上的原始值进行修改,只能修改引用值

//继续以上代码
student.books+=1;
student.info.weight = 100;

console.log(student.books, professor.books);
// 101  100  不会修改原型链上的原始值,会在实例内部新建一个属性,并附上计算后的值
student.books === professor.books;//false
console.log(student.info.weight, professor.info.weight);// 100  100 可以修改上层的引用值
student.info.weight === professor.info.weight;//true

手动修改_proto__

• 可以手动修改__proto__存储的对象

function Person(name){
    this.name = name;
}
Person.prototype = {
    country:"China",
}
var p1 = new Person("张三");
console.log(p1.country);// 输出 China
var p2 = {
    country:"Singapore",
}
p1.__proto__ = p2; //手动改变__proto__的指向
console.log(p1.country); // 输出 Singapore

二、对象继承

Object.create(自定义原型)

  • 不是所有的对象都继承于Object.prototype,例如 var obj = Object.create(null); 且之后继承obj的变量也随之脱离Object.prototype
var a = {};
console.log(a);// {} __proto__: Object.prototype

var b = Object.create(null);
console.log(b);// {} No properties 不具备任何属性

b.num = 1;
var c = Object.create(b);  //自定义c的原型为b对象
console.log(c);// {}  __proto__: num:1,脱离Object.prototype

手动修改__proto__

  • 可以对__proto__进行修改(如改变指向),但不能自己创造一个__proto__,它只能是系统内置属性
var obj = Object.create(null);  //obj不具备任何属性
var obj1 = {
	count:2,
	}
obj.__proto__ = obj1;  //手动为obj添加__proto__属性
console.log(obj.count);// 输出undefined,说明手动创造的__proto__系统不认,不能沿着原型链查找属性

this和prototype继承

同时继承两者

  • 手动指定函数A.prototype = B的实例化对象b
    缺点:若B.prototype已被指定为实例化对象c,则a会通过原型链继承c的属性,这没有必要

只继承this

  • call_apply
    运用call/apply函数,让 a 实例对象继承构造函数B里的this属性和方法,此时A.prototype不会被改变
//call或apply的作用是让Car()执行时this指向person,仅此而已,person的__proto__不会被改变

function Person(name){this.name = name};
Person.prototype.walk = function(){console.log('walking')};
  
function Car(color){this.color = color};
Car.prototype.run = function(){console.log('running')};

var person = new Person('jacky');
Car.call(person,'red'); //或者用 Car.apply(person,['red']);

console.log(person);
//person同时具备name、color属性,但person.__proto__仍然指向Person.prototype,只具备walk方法,不具备run方法

只继承prototype

  • A.prototype = B.prototype
    缺点:两个原型的指向是同一个地址, 若修改A.prototype的属性, B.prototype会跟着一起改变
function Person(name){this.name = name};
Person.prototype.walk = function(){console.log('walking')};
  
function Car(color){this.color = color};
Car.prototype.run = function(){console.log('running')};
  
Person.prototype = Car.prototype;
Person.prototype.age = 25;

var person = new Person('jacky');
var car = new Car('red');

console.log(person,car);
// person和car的prototype上都具备run方法和age属性

圣杯模式:只继承prototype,且不让两个原型一起改变

  • 在A和B之间构造一个缓冲函数Buffer,令Buffer.prototype = B.prototype; 同时实例化一个buffer对象,令A.prototype = buffer;
function Person(name){this.name = name};
Person.prototype.walk = function(){console.log('walking')};
  
function Car(color){this.color = color};
Car.prototype.run = function(){console.log('running')};

function Buffer(){} //Buffer函数不需要任何的this属性,它只是过渡作用
Buffer.prototype = Car.prototype;
var buffer = new Buffer();

Person.prototype = buffer; //Person.prototype通过buffer.__proto__继承到Car.prototype
Person.prototype.age = 25; //这个属性会影响buffer实例对象,但不会影响Car.prototype

var person = new Person('jacky');
var car = new Car('red');

console.log(person,car);

封装圣杯模式

;(function(){
	function Buffer(){};
	var inherit = function(Target, Origin){
		Buffer.prototype = Origin.prototype;//对Buffer.prototype的重写一定要放在buffer实例化对象之前
    	var buffer = new Buffer();
    	Target.prototype = buffer; 
    	Target.prototype.constructor = Target;
    	//由于重写了prototype导致其不再具备constructor属性,因此要人为创造一个
    	Target.prototype.super_class = Origin;
    	//人为指定继承源
	}
	window.Inherit = inherit;
	//产生闭包,将内部函数返回给外界变量Inherit
})();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值