【笔记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
控制台输出:
从这个结果可以看出,原来的 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");
通过这个构造函数,流程化生产,这个对象就生产出来了,有三个自己的属性,也有他从原型继承来的属性和方法,相当于访问自己的属性和方法。
控制台输出:
提取共有属性
再来看一个示例:
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')
这样生成出来的对象,就可以直播继承自原型,不用每次都生成一遍了。反正原型里的属性相对于对象来说都是一样的,不影响使用。
通过原型的这个特点,我们可以提取共有属性。
新增、修改、删除
看回原来的示例
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 属性吧。
既然增改都不行,那能否在对象上把原型的属性删除呢?
原型的 lastName 还在,之所以 delete 后控制台显示为 true ,是因为JS允许删除一个没有的属性,代表允许这么做而已,delete 一个 person 没有的属性,也会显示 true。
原型的另种写法
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。
这个属性在原型上:
如果在原型上修改了 constructor 指向的对象,那会怎样?
function Person(){
}
Car.prototype = {
constructor : Person // 修改成Person了
}
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
var car = new Car('black', 'Jack');
控制台输出:
这个 constructor 虽然是系统自动给的,但我们可以手动修改。
示例:
function Student(name){
this.name = name;
}
var s1 = new Student("Jack");
var s2 = new Student.prototype.constructor("Rose"); // 通过constructor指向也能创建新对象
即然,constructor 是构造器,指向构造函数的。就是说,Student 原型上的 constructor 就是指向它本身这个函数。那就一定可以这么用了。
控制台输出:
确实,这俩看起来一样的。
现在,我们在原型上增加一个属性:
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 = {} 是把原来的原型定义成了新对象,覆盖了原有原型;
看控制台输出:
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__属性
这样就能确定对象的 __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 ,它是可以被修改的。
偷梁换柱了。
示例:
来看个情况:
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 下面的,属性值怎么也跟着变了呢? 那是因为原型没变,原型属性的引用值变了,相当于房间没变,原来的钥匙还能打开。