简介
对象基础参考:Javascript之对象
对象继承参考:Javascript之对象继承
原型链是一种机制,指的是 JavaScript
每个对象都有一个内置的 __proto__
属性指向创建它的构造函数的 prototype
(原型)属性
原型链的作用是为了实现对象的继承
function
关键字或 Function
构造函数创建的对象都是函数对象
constructor 构造函数
- 函数还有一种用法,就是把它作为构造函数使用
- 构造函数本身也是函数,只不过可以用来创建对象而已
- 按照惯例,构造函数始终都应该以一个大写字母开头
- 构造函数与其他函数的唯一区别,就在于调用它们的方式不同
- 任何函数,只要通过
new
操作符来调用,那它就可以作为构造函数- 箭头函数不能用作构造函数
每个对象都有一个constructor
指针,这个指针指向的是创建该对象实例的构造函数
每个构造函数都有一个原型对象,该原型对象有一个constructor
属性,指向创建对象的函数本身
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("Stone", 28, "Software Engineer");
var person2 = new Person("Sophie", 29, "English Teacher");
console.log(person1.sayName == person2.sayName); // false
// 当作构造函数使用
var person = new Person("Stone", 28, "Software Engineer");
person.sayName(); // "Stone"
// 作为普通函数调用
Person("Sophie", 29, "English Teacher"); // 添加到 window
window.sayName(); // "Sophie"
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Tommy", 3, "Baby");
o.sayName(); // "Tommy"
prototype 原型
- 我们创建的每个函数都有一个
prototype
(原型)属性。- 函数独有的,从一个函数指向一个对象(函数的原型对象),也就是这个函数所创建的实例的原型对象
- 这个属性是一个指针,指向一个对象(函数的原型对象)
- 函数原型对象的作用:包含实例共享的属性和方法,可以让所有对象实例
共享它所包含的属性和方法
。- 不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型中
- 我们对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此
- 默认有两个属性,
constructor
属性和__proto__
属性
- 原型示例
function Person(){}
Person.prototype.name = "Stone";
Person.prototype.age = 28;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
person1.sayName(); // "Stone"
var person2 = new Person();
person2.sayName(); // "Stone"
console.log(person1.sayName == person2.sayName); // true
person1.name = "Sophie";
console.log(person1.name); // "Sophie",来自实例
console.log(person2.name); // "Stone",来自原型
delete person1.name;
console.log(person1.name); // "Stone",来自原型
- 原型的另一种写法
function Person(){}
Person.prototype = {
constructor : Person,
name : "Stone",
age : 28,
job: "Software Engineer",
sayName : function () {
console.log(this.name);
}
};
// 重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
// Object.defineProperty(Person.prototype, "constructor", {
// enumerable: false,
// value: Person
// });
var friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor === Person); // false
console.log(friend.constructor === Object); // true
- 原型的动态性
var friend = new Person();
Person.prototype.sayHi = function(){
console.log("hi");
};
friend.sayHi(); // "hi"(没有问题!)
// 当我们调用 person.sayHi() 时,首先会在实例中搜索名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。
// 因为实例与原型之间的连接只不过是一个指针,而非一个副本,因此就可以在原型中找到新的 sayHi 属性并返回保存在那里的函数
// 重写整个原型,就等于切断了构造函数与最初原型之间的联系
Person.prototype = {
// constructor: Person,
name : "Stone",
age : 28,
job : "Software Engineer",
sayName : function () {
console.log(this.name);
}
};
friend.sayName(); // Uncaught TypeError: friend.sayName is not a function
- 构造函数和原型结合(目前在
JavaScript
中使用最广泛、认同度最高的一种创建自定义类型的方法)
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["ZhangSan", "LiSi"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person("Stone", 28, "Software Engineer");
var person2 = new Person("Sophie", 29, "English Teacher");
person1.friends.push("WangWu");
console.log(person1.friends); // "ZhangSan,LiSi,WangWu"
console.log(person2.friends); // "ZhangSan,LiSi"
console.log(person1.friends === person2.friends); // false
console.log(person1.sayName === person2.sayName); // true
proto
为什么在构造函数的 prototype
中定义了属性和方法,它的实例中就能访问呢?
那是因为当调用构造函数创建一个新实例后,该实例的内部将包含一个指针 __proto__
,指向构造函数的原型
为了确保浏览器兼容性问题,不要直接使用 __proto__
属性
- 当我们访问一个对象的属性时,如果这个属性不存在,那么就会去
__proto__
里找 - 这个
__proto__
又会有自己的__proto__
,于是就这样一直找下去,直到找到为止。 - 在找不到的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
function Person(){}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
构造函数、原型对象和实例对象之间的关系如下图
原型链
- 每个对象都有一个原型
__proto__
,这个原型还可以有它自己的原型,以此类推,形成一个原型链 - 查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找
- 这个操作被委托在整个原型链上,这个就是我们说的原型链
const Person = function(name) {
this.name = name || 'wmm'
}
Person.prototype.sayName = function() {
console.log('prototype.sayName: ', this.name)
}
const p = new Person()
console.log(p.__proto__ === Person.prototype) // true
console.log(Person.prototype.constructor === Person) // true
// p没有constructor属性,沿着原型链往上找,找到Person.prototype,如果不覆盖的话默认会有constructor属性
console.log(p.constructor === Person) // true
console.log(p.name) // wmm66 man
// 实例原型链
// 实例 p 的 __proto__ 指向 构造函数的原型对象
console.log(p.__proto__ === Person.prototype) // true
// p的构造函数的原型Person.prototype,还有它自己的构造函数(普通对象,是由Object构造的)的原型对象
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(p.__proto__.__proto__ === Object.prototype) // true
// Object是顶级的了,往上没有了。
// Object.prototype这个对象没有原型对象
console.log(Object.prototype.__proto__ === null) // true
继承
const Person = function(name) {
this.name = name || 'wmm'
}
Person.prototype.sayName = function() {
console.log('prototype.sayName: ', this.name)
}
const p = new Person()
const Man = function() {
Person.call(this, 'wmm66')
this.sex = 'man'
}
// 如果直接用p的话,容易污染,后面不方便演示
// 这里使用Person作为构造函数新建一个对象用来继承。使用new 或者 Object.create
// const p1 = new Person()
const p1 = Object.create(Person.prototype)
Man.prototype = p1
// Man.prototype直接赋值,下面的constructor被覆盖掉了。建议重新指回去
Man.prototype.constructor = Man
const m = new Man()
console.log(p.__proto__ === Person.prototype) // true
console.log(Person.prototype.constructor === Person) // true
// p没有constructor属性,沿着原型链往上找,找到Person.prototype,如果不覆盖的话默认会有constructor属性
console.log(p.constructor === Person) // true
console.log(m.constructor === Man) // true
console.log(m.__proto__ === Man.prototype) // true
console.log(Man.prototype.constructor === Man) // true
console.log(m.name, m.sex) // wmm66 man
// 实例原型链
// 实例 m 的 __proto__ 指向 构造函数的原型对象
console.log(m.__proto__ === Man.prototype) // true
// m的构造函数的原型对象 Man.prototype 就是p1, p1的 __proto__ 指向 p1 的构造函数的原型
console.log(Man.prototype === p1) // true
console.log(p1.__proto__ === Person.prototype) // true
console.log(Man.prototype.__proto__ === Person.prototype) // true
// p的构造函数的原型Person.prototype,还有它自己的构造函数(普通对象,是由Object构造的)的原型对象
console.log(Person.prototype.__proto__ === Object.prototype) // true
// Object是顶级的了,往上没有了。
// Object.prototype这个对象没有原型对象
console.log(Object.prototype.__proto__ === null) // true
关卡
// 1.定义一个构造函数 Animal,它有一个 name 属性,以及一个 eat() 原型方法。
// 2.eat() 的方法体为:console.log(this.name + " is eating something.")。
// 3.new 一个 Animal 的实例 tiger,然后调用 eat() 方法。
// 4.用 __proto__ 模拟 new Animal() 的过程,然后调用 eat() 方法。
var Animal = function(name){
this.name = name;
};
Animal.prototype.eat = function(){
console.log(this.name + " is eating something.");
};
var tiger = new Animal("tiger");
tiger.eat();
var tiger2 = {};
tiger2.__proto__ = Animal.prototype;
Animal.call(tiger2, "tiger2");
tiger2.eat();
// 1.定义一个构造函数 Bird,它继承自 Animal,它有一个 name 属性,以及一个 fly() 原型方法。
// 2.fly() 的方法体为:console.log(this.name + " want to fly higher.");。
// 3.new 一个 Bird 的实例 pigeon,然后调用 eat() 和 fly() 方法。
// 4.用 __proto__ 模拟 new Bird() 的过程,然后用代码解释 pigeon2 为何能调用 eat() 方法。
var Bird = function(name){
this.name = name;
}
Bird.prototype = new Animal();
Bird.prototype.fly = function(){
console.log(this.name + " want to fly higher.");
};
var pigeon = new Bird("pigeon");
pigeon.eat();
pigeon.fly();
var pigeon2 = {};
pigeon2.__proto__ = Bird.prototype;
Bird.call(pigeon2, "pigeon2");
console.log(pigeon2.__proto__.__proto__.eat === Animal.prototype.eat);
// 1.定义一个构造函数 Swallow,它继承自 Bird,它有一个 name 属性,以及一个 nesting() 原型方法。
// 2.nesting() 的方法体为:console.log(this.name + " is nesting now.");。
// 3.new 一个 Swallow 的实例 yanzi,然后调用 eat()、fly() 和 nesting() 方法。
// 4.用 __proto__ 模拟 new Swallow() 的过程,然后用代码解释 yanzi2 为何能调用 eat() 方法。
var Swallow = function(name){
this.name = name;
}
Swallow.prototype = new Bird();
Swallow.prototype.nesting = function(){
console.log(this.name + " is nesting now.");
};
var yanzi = new Swallow("yanzi");
yanzi.eat();
yanzi.fly();
yanzi.nesting();
var yanzi2 = {};
yanzi2.__proto__ = Swallow.prototype;
Swallow.call(yanzi2, "yanzi2");
console.log(yanzi2.__proto__.__proto__.__proto__.eat === Animal.prototype.eat);
发表评论