JavaScript——原型,原型链及原型继承(学习笔记)

 1:构造函数

// 自定义构造函数
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayName = function () {
    console.log(this.name);
  }
}

var person1 = new Person('zhangsan', 29, 'male');
var person2 = new Person('lisi', 19, 'female');

person1.sayName(); // zhangsan
person2.sayName(); // lisi

(1) 在内存中创建一个新对象。

(2) 这个新对象内部的[[Prototype]] 特性被赋值为构造函数的 prototype 属性。

(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。

(4) 执行构造函数内部的代码(给新对象添加属性)。

(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

2:什么是原型

每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。

function Person(){}
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person();
person1.sayName(); // zhangsan 
var person2 = new Person();
person2.sayName(); // zhangsan 
console.log(person1.sayName == person2.sayName); // true

 

 这里,所有属性和 sayName()方法都直接添加到了 Person 的 prototype 属性上,构造函数体中什么也没有。但这样定义之后,调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此 person1 和 person2 访问的都是相同的属性和相同的 sayName()函数。

 

理解原型:
创建函数时,会创建一个prototype属性(指向原型对象);默认情况下,原型对象都会有一个 constructor 的属性(指向与之关联的构造函数),就上面的例子而言就是:Person.prototype.constructor === Person,而其自带的其他方法都继承于Object(如toString)。

每次调用构造函数创建新实例时,实例内部的[[Prototype]]指针就会赋值为构造函数的原型对象,而脚本中没有访问这个[[Prototype]]特性的标准方式,但一些浏览器Firefox、Safari和Chrome会在每个对象中暴露了__proto__属性,通过这个属性可访问到原型对象。

 

function Person(){}

console.log(Person.prototype.constructor === Person) // true

/**
*	正常的原型链都会终止于Object的原型对象
* Object的原型对象是null
*/
console.log(Person.prototype === Object.prototype) // true
console.log(Person.prototype.constructor === Object) // true
console.log(Person.prototype.__proto__.__proto__ === null) // true

let person1 = new Person()

/**
*	构造函数、原型对象和实例,是三个完全不同的对象
*/
console.log(person1 !== Person) // true
console.log(person1 !== Person.prototype) // true
console.log(Person.prototype !== Person) // true

/**
*	实例的__proto__,指向原型对象(实际上指向隐藏特性[[Prototype]])
* 构造函数的prototype,链接到原型对象(可以理解成Prototype就是原型对象)
* 实例与构造函数没有直接联系,与原型对象有直接联系
*/
console.log(person1.__proto__ === Person.prototype) // true
console.log(person1.__proto__.constructor === Person) // true

let person2 = new Person()

/**
*	同一个构造函数创建的多个实例,会共享同一个一个原型对象
*/
console.log(person2.__proto__ === Person.prototype) // true
console.log(person1.__proto__ === person2.__proto__) // true

 3:原型层级

  在通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身。如果在这个实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值。

function Person() { }
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "lisi";
console.log(person1.name); // lisi,来自实例
console.log(person2.name); // zhangsan,来自原型

 解析: person是Person()的实例,是一个Person对象,它拥有一个属性值__proto__,并且__proto__是一个对象,包含两个属性值constructor和__proto__

 只要给对象实例添加一个属性,这个属性就会遮蔽(shadow)原型对象上的同名属性,也就是虽然不会修改它,但会屏蔽对它的访问。即使在实例上把这个属性设置为 null,也不会恢复它和原型的联系。不过,使用 delete 操作符可以完全删除实例上的这个属性,从而让标识符解析过程能够继续搜索原型对象。

function Person() { }
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
// 通过hasOwnProperty()可以查看访问的是实例属性还是原型属性
console.log(person1.hasOwnProperty('name')); //false

person1.name = "lisi";
console.log(person1.name); // lisi,来自实例
//只在重写 person1 上 name 属性的情况下才返回 true,表明此时 name 是一个实例属性,不是原型属性
console.log(person1.hasOwnProperty('name')); //true

console.log(person2.name); // zhangsan,来自原型

console.log(person2.hasOwnProperty('name'));//false

delete person1.name;
console.log(person1.name); // zhangsan,来自原型

console.log(person1.hasOwnProperty('name'));//false

4:原型与in操作符

function Person() { }
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();

// 无论属性是在实例上还是在原型上,都可以检测到
console.log("name" in person1); // true
console.log("name" in person2); // true
// 判断一个属性是否是原型属性
function hasPrototypeProperty(object, name) {
  //不在实例中但是可以访问到的属性属于原型属性
  return !object.hasOwnProperty(name) && (name in object);
}
console.log(hasPrototypeProperty(person1, 'name'));//true

5:原型链

 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。

   function Animal() {
            this.name = 'animal';
        }
        Animal.prototype.getAnimalName = function () {
            console.log(this.name + 'getAnimalName');
        }
        // 创建Dog
        function Dog() {
            this.name = 'dog';
        }
        // Dog继承自Animal  将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了Dog的原型对象
        // 如此 Dog原型对象 就能通过 Animal 对象的实例中的[[prototype]](__proto__) 来访问到 Animal原型对象 中的属性和方法了。
        Dog.prototype = new Animal();
        console.log(Dog.prototype);
        // console.log(Animal.prototype);
        Dog.prototype.getDogName = function () {
            console.log(this.name + 'getDogName');
        }
        var d1 = new Dog();
        console.log(d1);
        d1.getAnimalName() //doggetAnimalName
        d1.getDogName() //doggetDogName

1、所有的引用类型都有一个’_ _ proto_ _'属性(也叫隐式原型,它是一个普通的对象)。

2、所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。

3、所有引用类型,它的’_ _ proto_ _'属性指向它的构造函数的’prototype’属性。

4、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。

 6:原型继承

 当我们从 object (对象)中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这被称为“原型继承”。

 基本思路很简单:在子类构造函数中调用父类构造函数。

function Animal() {
  this.categorys = ["cat", "rabbit"];
}
function Dog() {
  // 继承 Animal 
  Animal.call(this);
}
 在var d1 = new Dog()时,是d1调用Dog构造函数,所以其内部this的值指向的是d1,所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()。最后,d1去调用Animal方法时,Animal内部的this指向就指向了d1。那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的categorys属性副本。他们互不影响。
var d1 = new Dog();
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys); // [ 'cat', 'rabbit' ]

 相比于使用原型链,经典继承函数的一个优点就是可以在子类构造函数中向父类构造函数传参。

function Animal(name) {
  this.name = name;
}
function Dog() {
  // 继承 Animal 并传参
  Animal.call(this, "zhangsan");
  // 实例属性
  this.age = 29;
}
var d = new Dog();
console.log(d.name); // zhangsan
console.log(d.age); // 29

 在这个例子中,Animal 构造函数接收一个参数 name,然后将它赋值给一个属性。在 Dog构造函数中调用 Animal 构造函数时传入这个参数,实际上会在 Dog 的实例上定义 name 属性。为确保 Animal 构造函数不会覆盖 Dog 定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值