一、原型与原型链
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
重写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
})();