【笔记11】Javascript - 原型

原型

原型是function对象的一个属性,它定义了构造函数创建出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。

先来一个构造函数:

function Person(){
}
ver person = new Person();

构造函数有个特点:命名是大驼峰式的,当然,这是人为规定的开发规范。

构造函数通过 new 来创建一个对象,可复重执行生成相似且独立的对象。

函数也是对象,对象就有属性和方法。构造函数在出生的时候就有一个对象 "prototype"。

// Person.prototype   --  原型

我们给这个原型上加个属性:

Person.prototype.name = "Jack"; 
function Person(){
}
ver person = new Person();

这个 person 是构造函数 Person 构建出来的对象,那他就能继承这个原型 Person.prototype 上的属性。

console.log(person.name);        // Jack

控制台输出:

Javascript(笔记11) - 原型_原型链

从这个结果可以看出,原来的 person 下是个空对象,但他确有个属性 name 叫 “Jack”。

原型描述的就是这种继承的关系。

那为何又说原型是公共祖先呢? 通过构造函数 Person ,我们再创建一个 person1 。

var person1 = new Person();
console.log(person1.name); // Jack

现在 person 和 person1 就有了共同的祖先(原型),叫 Person.prototype ,这俩对象都能调用这个原型上的属性。

如果构造函数上,也有一个 lastName,那么在调用这个 lastName 时候会怎样?

Person.prototype.lastName = "Jobs";
function Person(){
this.lastName = "Anderson";
}
var person = new Person();
console.log(person.lastName) // Anderson

他俩都有,优先用自己的属性,自己没有再往上找。

现在我们把构造函数写全一点:给原型加上一个方法。

Person.prototype.lastName = "Jobs";
Person.prototype.say = function (){
console.log('hello!');
}
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var person = new Person("Steve",40,"male");

通过这个构造函数,流程化生产,这个对象就生产出来了,有三个自己的属性,也有他从原型继承来的属性和方法,相当于访问自己的属性和方法。

控制台输出:

Javascript(笔记11) - 原型_构造函数_02


提取共有属性

再来看一个示例:

function Car(color,owner){
this.color =color;
this.owner = owner;
this.height = 1400;
this.lang = 4900;
this.brand = "BMW";
}
var car = new Car('black','Jack');

这个构造函数里,使用了很多 “this.属性”的写法,看着就很“冗余”,因为每创建一个对象,都要执行一遍,是不是可以把这些共有属性提取出来,放到原型里? 可以的。

Car.prototype.name = "BMW";
Car.prototype.lang = 4900;
Car.prototype.height = 1400;
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
var car = new Car('black', 'Jack');
var car1 = new Car('red','Rose')

这样生成出来的对象,就可以直播继承自原型,不用每次都生成一遍了。反正原型里的属性相对于对象来说都是一样的,不影响使用。

Javascript(笔记11) - 原型_原型链_03

通过原型的这个特点,我们可以提取共有属性。


新增、修改、删除

看回原来的示例

Person.prototype.lastName = "Jobs";
function Person(){
this.name = "Steve";
}
var person = new Person();

如何增加和修改原型的属性或方法?直接在原型上增加。

Person.prototype.gender = 'male';      // 增加属性
Person.prototype.say = function (){ // 增加方法
console.log('hello!');
}
Person.prototype.name = "James"; // 修改属性
function Person(){
this.name = "Steve";
}
var person = new Person();

直接在原型上增改是可以的,那如果在对象上增改,行不行?

person.lastName = "Jack";
console.log(person.lastName); // Jack

这样是给 person 对象自己添加一个 lastName 属性,并没有修改原型的 lastName 属性吧。

Javascript(笔记11) - 原型_原型_04

既然增改都不行,那能否在对象上把原型的属性删除呢?

Javascript(笔记11) - 原型_原型链_05

原型的 lastName 还在,之所以 delete 后控制台显示为 true ,是因为JS允许删除一个没有的属性,代表允许这么做而已,delete 一个 person 没有的属性,也会显示 true。

Javascript(笔记11) - 原型_原型_06


原型的另种写法

Car.prototype.name = "BMW";
Car.prototype.lang = 4900;
Car.prototype.height = 1400;
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
var car = new Car('black', 'Jack');

Car.prototype 本身也是对象,改用对象的写法更简洁,同样可在创建和继承:

Car.prototype = {
height : 1400,
brand : "BMW",
lang : 4900
}
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
var car = new Car('black', 'Jack');
console.log(car.brand); // BMW

constructor

原型上,还有个默认属性叫 : constructor (构造器),指向创建他的构造函数 Car。

Javascript(笔记11) - 原型_原型_07

这个属性在原型上:

Javascript(笔记11) - 原型_原型_08

如果在原型上修改了 constructor 指向的对象,那会怎样?

function Person(){
}
Car.prototype = {
constructor : Person // 修改成Person了
}
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
var car = new Car('black', 'Jack');

控制台输出:

Javascript(笔记11) - 原型_原型_09

这个 constructor 虽然是系统自动给的,但我们可以手动修改。


示例:

function Student(name){
this.name = name;
}
var s1 = new Student("Jack");
var s2 = new Student.prototype.constructor("Rose"); // 通过constructor指向也能创建新对象

即然,constructor 是构造器,指向构造函数的。就是说,Student 原型上的 constructor 就是指向它本身这个函数。那就一定可以这么用了。

控制台输出:

Javascript(笔记11) - 原型_原型_10

确实,这俩看起来一样的。

现在,我们在原型上增加一个属性:

Student.prototype.age = 30;
function Student(name){
this.name = name;
}
var s1 = new Student("Jack");
var s2 = new Student.prototype.constructor("Rose");

console.log(s1.age); // 30
console.log(s2.age); // 30

这样两个新对象都能正常继承原型的属性。

再改一下:

Student.prototype.age = 30;
Student.prototype={
sex: "male",
}
function Student(name){
this.name = name;
}
var s1 = new Student("Jack");
var s2 = new Student.prototype.constructor("Rose");

console.log(s1.sex); // male
console.log(s2.sex); // undefined

这里,我们要区分原型的属性和新的原型对象:

Student.prototype.age 是在原来原型里添加一个新的属性叫 age,原型没变;

Student.prototype = {} 是把原来的原型定义成了新对象,覆盖了原有原型;

看控制台输出:

Javascript(笔记11) - 原型_构造函数_11

s2 这个对象变成了 String,意思是由 String 构造而来,同时 sex 的属性也丢了。

这是因为新建的 Student.prototype 原型已经覆盖了最初的原型,最初的原型里的 constructor 指向的是 Student 构造函数,而新建的原型里没有 constructor ,就被系统默认指向了 String。

我们可以给他加个 constructor 指向,重新指向 Student 。

Student.prototype={
constructor:Student, // 手工修改 constructor 指向
sex: "male",
}
function Student(name){
this.name = name;
}
var s1 = new Student("Jack");
var s2 = new Student.prototype.constructor("Rose");

console.log(s1.sex); // male
console.log(s2.sex); // male


__proto__

隐式属性 "__proto__"

当我们访问对象的一个属性时,如何对象没有这个属性,他就会通过 __proto__指向的索引去找原型上有没有你想要的属性。 所以 __proto__ 相当于一个连接的作用,把原型与当前对象连接在一起了。

Person.prototype.name = "Jobs";          // 原型属性
function Person(){
// var this = {
// __proto__ : Person.prototype // 指向原型
//}
}
var person = new Person();
console.log(person.name); // Jobs

来看一下person的__proto__属性

Javascript(笔记11) - 原型_构造函数_12

这样就能确定对象的 __proto__ 属性指向构造函数的原型了吧。

如何我们强制修改了  person.__proto__ 的指向,会怎样?

Person.prototype.name = "Jobs";
function Person(){
}
var obj = { // 临时那个新对象,有name属性和值
name : "Jack"
}
var person = new Person();
person.__proto__ = obj; // 强制修改为新对象
console.log(person.name); // Jack

当我们强制修改了 person 对象的原型,person.name 取到新原型对象上的属性了。

这说明, Person 构造函数构造出的对象,所对应的原型未必非得是  Person.prototype ,它是可以被修改的。

Javascript(笔记11) - 原型_原型_13

偷梁换柱了。


示例:

来看个情况:

Person.prototype.name = "Jobs";
function Person() {
}
var person = new Person();
Person.prototype.name = "Jack";
console.log(person.name); // Jack

原型上的属性被修改后,输出结果也跟着变了。

再来看:

Person.prototype.name = "Jobs";
function Person() {

}
var person = new Person();
Person.prototype = {
name: "Jack"
}
console.log(person.name); // Jobs

请问,这里为什么会输出 Jobs? 

再看:

Person.prototype.name = "Jobs";
function Person() {

}
Person.prototype = {
name: "Jack"
}
var person = new Person();
console.log(person.name); // Jack

是不是有些不理解了?

首先:注意后两种写法。Person.prototype = {} 这种写法,可不是直接修改原型的属性值,而是把原型本身改了,换了个新对象。相当于房间都换了,原来的钥匙打不开了。

相当于:

var obj = {name:"a"};
var obj1 = obj;
obj ={name:"b"};
console.log(obj1); // {name:"a"} 没变

其次:注意 Person.prototype ={} 的位置,放在 new 的上面,是在创建新对象之前替换了 Person.prootype ,那么 person.name 必然就等于新原型的 name 了;放在 new 的下面,新对象都已经创建完了再去替换原型,取得自然是老原型的 name 了。

最后:那为什么上面 Person.prototype.name 是放在 new 下面的,属性值怎么也跟着变了呢? 那是因为原型没变,原型属性的引用值变了,相当于房间没变,原来的钥匙还能打开。