js中原型、原型链、继承的理解(透彻)
1、前言
1.1 什么是函数对象
函数对象就是我们平时称呼的函数,构造函数也包含其中,在这里我们说一说构造函数与普通函数的区别。
构造函数也是一个普通函数,和普通函数的主要区别在于调用方式不一样,作用也不一样(构造函数用来构建实例对象)。
普通函数:
function person(){
console.log(this); // Window
}
person()
构造函数:
function Person() {
console.log(this); // Person{}
}
let p = new Person();
1.2 什么是实例对象
ES5:
// ES5:生成实例对象的传统方法是通过构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person("张三", 29);
var person2 = new Person("李四", 27);
ES6:
// ES6可通过class关键字定义类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
alert(this.name);
}
}
// ES6使用类时也是直接对类使用new命令
var person1 = new Person("Nicholas", 29);
var person2 = new Person("Greg", 27);
person1和person2都是Person类的实例对象(实例都是对象)
1.3 什么是原型对象
我们在创建函数的同时,浏览器会在内存中创建一个对象。这个函数中默认有一个prototype属性,指向了该对象。这个对象就是函数的原型对象,简称函数的原型。
每个构造函数都有一个原型对象存在,这个原型对象通过构造函数的prototype属性来访问。原型对象默认会有一个constructor属性指向构造函数。
1.4 构造函数、原型对象、实例对象的关系
构造函数、原型对象、实例对象的关系:
①每个构造函数都有一个prototype属性,这个属性指向了原型对象。
②每个实例对象都有一个__proto__属性,这个属性指向了对象的原型对象。
③在原型对象里有一个constructor属性,该属性指向了构造函数。
2、原型
在 JavaScript中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype属性,这个属性指向函数的原型对象,使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
3、原型链
每个对象拥有一个原型对象,通过 proto 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向
null(Object.proptotype.__proto__指向的是null)。这种关系被称为原型链(prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。
4、原型的相关属性及方法
三个属性:prototype、proto、constructor;
一个方法:hasOwnProperty()
★ prototype:每个函数都有一个prototype属性,这个属性指向函数的原型对象。
★ proto:每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。
★ constructor:每一个原型都有一个constructor属性,指向该关联的构造函数。
prototype 和 proto 区别是什么?
● prototype是构造函数的属性
● __proto__是每个实例都有的属性,可以访问 [[prototype]] 属性
● 实例对象的__proto__与其构造函数的prototype指向的是同一个对象
★ hasOwnProperty() 方法
它的作用是判断函数的原型所在位置。一个对象本身添加的属性返回true,在原型中的属性和不存在的属性都会返回false。
//创建构造函数
function Person(){
}
//使用Person.prototype直接访问到原型对象
// 给Person函数的原型对象中添加一个属性name,值是"小明"
// Person.prototype.name = "小明";
Person.prototype = {
constructor:Person, //让constructor重新指向Person函数
/*如果直接给Person的原型指定对象字面量(没有上面这一行),
这个对象的constructor属性不再指向Person函数*/
name: "小明"
};
//创建一个实例对象p1
var p1 = new Person();
//访问p1对象的属性name
p1.age = 18;
console.log(Person.prototype.constructor === Person); // true
//如果在Person.prototype中没有constructor:Person这一行代码则输出flase
//Person.prototype.name = "小明";这种写法输出也是true
console.log(p1.__proto__ === Person.prototype); // true
//如果在Person.prototype中没有constructor:Person这一行代码则输出flase
//Person.prototype.name = "小明";这种写法输出也是true
console.log(p1.name); // 小明
/*虽然在p1对象中没有明确的添加属性name,但是仍然可以成功打印的原因是:
p1的__proto__属性指向的原型中有name属性,所以可以访问到属性name值。*/
console.log(p1.hasOwnProperty("age")); // true
//因为age属性是直接在p1属性中添加的
console.log(p1.hasOwnProperty("name")); // false
//因为name属性是在原型中添加的
console.log(p1.hasOwnProperty("sex")); // false
//因为sex属性不存在
5、总结
- JavaScript中的对象,都有一个内置属性[Prototype],指向这个对象的原型对象。当查找一个属性或方法时,如果在当前对象中找不到,会继续在当前对象的原型对象中查找;如果原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也是对象,也有它自己的原型);直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。这个查找过程是一个链式的查找,每个对象都有一个到它自身原型对象的链接,这些链接组建的整个链条就是原型链。拥有相同原型的多个对象,他们的共同特征正是通过这种查找模式体现出来的。
- 在上面的查找过程,我们提到了最顶层的原型对象,这个对象就是Object.prototype,这个对象中保存了最常用的方法,如toString、valueOf、hasOwnProperty等,因此我们才能在任何对象中使用这些方法。