【笔记12】Javascript - 原型链
原型链
原型链的构成
先来看一段代码:
Grand.prototype.company = "Apple"; // grand 也有原型
function Grand() {
this.lastName ="Jobs";
}
var grand = new Grand();
Father.prototype = grand; // father 的原型上面还有原型
function Father() {
this.name = "Steve";
}
var father = new Father();
Son.prototype = father; // son 的原型上面有原型
function Son() {
this.hobbit = "smoke";
}
var son = new Son();
现在我们在控制台输出:
hobbit 是 son 自己的属性,name 是 son 的原型(father)的属性,lastName 是 son 原型的原型(Grand)的属性,最后 company 是 grand 的原型的属性,一层层都可以调用。
这样就把访问原型的顺序连成链,就叫做原型链。
那么 Grand.prototype 就是已知最上层的原型了么? 不。
Grand.prototype.__proto__ == Object.prototype // true
看控制台输出:
可以看到,整条原型链的最上层是 Object.prototype ,再往上就是 null 。
属性的修改
原型链跟原型一样,自己没有的属性就一直向上查。
对象自己可以 增加修改删除 自己原型的属性,但不能修改上层原型的属性。
示例1:尝试修改属性
我们给 Father 构造函数加一个引用值的属性:
Grand.prototype.company = "Apple";
function Grand() {
this.lastName = "Jobs";
}
var grand = new Grand();
Father.prototype = grand;
function Father() {
this.name = "Steve";
this.fortune = { // 新增一个引用值属性 fortune
card1: 'visa'
}
}
var father = new Father();
Son.prototype = father;
function Son() {
this.hobbit = "smoke";
}
var son = new Son();
在控制台修改 son.fortune :
这个操作不能修改 father.fortune 的属性,实际上这是 son 再给自己添加 fortune 属性。
换个改法:
严格来说,这不算是属性的修改,是引用值调用的修改。也仅限于引用值,原始值是不能修改的。
示例2:尝试属性++
Grand.prototype.company = "Apple";
function Grand() {
this.lastName = "Jobs";
}
var grand = new Grand();
Father.prototype = grand;
function Father() {
this.name = "Steve";
this.fortune = {
card1: 'visa'
}
this.num = 100; // 新增一个原始值属性
}
var father = new Father();
Son.prototype = father;
function Son() {
this.hobbit = "smoke";
}
var son = new Son();
控制台里,修改:
怎么解释 “++”的问题?
son.num ++ 相当于把 father.num 取过来再加 ++,使得自身多了一个属性 num ,值为101。而father.num 没变。
示例3:this的问题
Person.prototype = {
name: "Jack",
sayName: function () {
console.log(this.name);
}
}
function Person() {
}
var person = new Person();
看控制台输出:
sayName 中 this 的指向: 谁调用的这个方法,this就是指向谁。
person 调用的这个方法,就指向 person ,但 person 里没有 name,就向他的原型找。所以输出 Jack
我们改一下代码:
Person.prototype = {
name: "Jack",
sayName: function () {
console.log(this.name);
}
}
function Person() {
this.name = "Tom";
}
var person = new Person();
再看控制台输出:
这时 person 里面有了自己的 name 所以输出 Tom。
示例4:还是this
Person.prototype = {
height: 100
}
function Person() {
this.eat = function () {
this.height++;
}
}
var person = new Person();
看控制台输出:
调用 eat 方法并不能修改 原型上的原始值,只是在给自己取了一个 height ,并 ++ 了。
原型链终端
绝大多数对象的最终都会继承自 Object.prototype
常用的创建对象的方法:
var obj = {}; // 相当于 --> new Object();
var obj1 = new Object();
这两种创建对象的方法可以说是一样的。
常用的是,就用字面量的方式创建对象。
这两种方式创建的对象的原型也都是 Object.prototype 。
所以 obj 天生就有 toString() 方法,因为它继承了原型的方法。
示例1:常见对象
是不是所有的对象最终都继承自 Object.prototype 呢?
var obj = Object.create(null);
在控制台输出:
居然构造出一个没有原型的对象,那人为的给他加上一个原型试试:
原型添加成功了,但是属性却不能用。
所以说:绝大多数对象的最终都会继承自 Object.prototype,但有个别特例。
示例2:特殊类型
先看控制台:
undefined 和 null 这两种特殊的类型没有原型,也就不使用Object.prototype 的属性。
常见的原始值:
数字、布尔和字符串都可以使用 toString() 方法,是因为他们在使用方法前,都被隐式的“包装类”了。
var num = 123;
// num.toString(); (隐式的转换)--> new Number(num).toString
// 而他的原型 Number.prototype 本身就有 toString()方法。
Number.prototype.toString = function(){} // Number 自身原型重新了 toString 方法
// Number.prototype.__proto__ 的原型就指向 Object.prototype,所以这也原型链。
Number.prototype.__proto__ == Object.prototype
示例3:原型上的方法重写
先看代码:
Person.prototype ={
toString:function(){
return "surprise";
}
}
function Person(){
}
var person = new Person();
console.log(person.toString()); // surprise
这在构造函数的原型上新建了一个也叫 toString() 的方法,就把 Object.prototype 原型上的覆盖了,这种叫做“方法重写”,当然也可以在 Object.prototype 原型上修改 toString() 方法。
Object.prototype.toString = function (){
return "hello!";
}
不仅我们在重写,系统自己也在重写:包装类也重写了 toString 方法。
// Object.prototype.toString
// Number.prototype.toString
// Array.prototype.toString
// Boolean.prototype.toString
// String.prototype.toString
验证一下:
可以看到 Object.prototype.toString 输出的内容并没什么卵用。
document.write 打印在屏幕上的内容,也是使用的 toString 方法,验证一下?
var obj ={};
obj.toString = function(){
return "surprise";
}
document.write(obj); // surprise
Object.create(原型)
一种更加灵活的创建原型的方法。
// var obj = Object.create(原型);
var obj = {name : "Jack",age : 23};
var obj1 = Object.create(obj);
看控制台输出:
obj1 的原始指向了 obj , 也可以方便的使用原型的属性。
可以模拟 new Person() 创建对象。
Person.prototype.name = "Jack";
function Person(){
this.age = 30;
}
var person = Object.create(Person.prototype);
console.log(person.name); // Jack
console.log(person.age); // undefined
为啥说是模拟呢,因为把 person 的原型指向 Person.prototype 后,可以使用这个原型的属性,但不是构造函数的对象,所以 person.age 输出 undefined 。