原型对象关系
关键重点(搞清楚这两个,对理解JavaScript原型至关重要)
prototype
proto(注意是前后都有双下划线)/ [[Prototype]](目前标准显示)
\
prototype
JavaScript中只有函数拥有prototype属性,属性值是一个对象,该对象又有一个constructor属性,指回函数本身,这个对象我们称为原型对象,是通过函数实例化出来的对象的原型对象,不是函数本身的原型对象。
proto
- __proto__属性由浏览器厂商提供实现,非官方标准属性,不再建议使用
- __proto__在官方标准中显示为[[Prototype]]
- 使用官方标准Object.getPrototypeOf()/Reflect.getPrototypeOf()方法获取原型对象
JavaScript中所有的对象都有对应的原型对象,可通过Object.getPrototypeOf()/Reflect.getPrototypeOf()方法来获取原型对象,获取到的原型对象其实就是其构造函数的prototype。
而又因为JavaScript中一切皆为对象,函数也是对象,所以函数既有prototype属性,又有函数本身对应的原型对象,所有你在JS中定义的函数的原型对象都是Function.prototype。
唯一特殊:Function构造函数的原型对象就是Function.prototype
原型链
对象的原型对象的原型对象的原型对象……直到Object.prototype就到尽头了,尽头就是null。 当我们访问一个对象的属性或方法时,JS首先在对象本身上查找是否存在,如果查找不到,就到对象的原型对象上找(Object.getPrototypeOf(对象)),如果找到就用它,如果没找到,那就继续在原型对象的原型对象上找(Object.getPrototypeOf(Object.getPrototypeOf(对象))),一直找到尽头都没有找到就返回undefined。
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);
//doSomeInstancing.prop: some value
//doSomeInstancing.foo: bar
//doSomething.prop: undefined
//doSomething.foo: undefined
//doSomething.prototype.prop: undefined
//doSomething.prototype.foo: bar
- 声明doSomething函数
- 在doSomething函数的prototype属性上添加一个foo属性,值为"bar"
- 根据doSomething函数实例化一个对象保存在doSomeInstancing 变量中
- 实例对象doSomeInstancing添加一个prop属性,值为"some value"
- 访问doSomeInstancing.prop,doSomeInstancing上有这个属性,直接输出some value
- 访问doSomeInstancing.foo,doSomeInstancing上没有这个属性,到对应原型对象上找(doSomething.prototype),doSomething.prototype上有foo属性,输出bar
- 访问doSomething.prop,没有,到原型对象上找(Function.prototype),显然一直到尽头null都没有,输出undefined
- 访问doSomething.foo,同上,doSomething对应的原型对象是Function.prototype,并不是doSomething.prototype,所以输出undefined
- 访问doSomething.prototype.prop,没有,到doSomething.prototype的原型对象上找(Object.prototype),显然一直到尽头null都没有,输出undefined
- 访问doSomething.prototype.foo,有,直接输出bar
以上用大白话简单总结:
函数特有prototype属性,作为此函数构造出来的对象的原型对象,函数本身也是对象,也有自身对应的原型对象(虽然不推荐使用__proto__,但也可理解函数既有prototype属性又有__proto__属性) 。所有对象都有对应的原型对象,就是该对象构造函数的prototype(包括原型对象也有原型) ,只要不是用你自定义的构造函数实例化出来的对象(字面量对象声明、new Object()) ,一般对应的原型对象就是Object.prototype。对象和原型对象间层层查找就是所谓原型链,原型链顶点是Object.prototype。
继承
不同于传统面向对象语言中使用class(类)实现对象继承,JavaScript是基于原型对象和原型链来实现继承的,也称为原型式继承。
// 定义一个Animal构造函数
function Animal() {
this.parent = "Animal";
}
// 在Animal的原型属性prototype上添加两个方法
Animal.prototype.say = function () {
console.log("我是动物,种类是:", this.name);
};
Animal.prototype.showSkill = function () {
console.log("我是动物,技能是:", this.skill);
};
// 定义一个Dog函数,并让继承自Animal
function Dog(name, skill) {
Animal.call(this); //利用call方法调用Animal构造函数,并把Animal中的this指向改为Dog内部this
this.name = name;
this.skill = skill;
}
// 修改Dog的原型属性prototype,利用Object.create方法创建以Animal.prototype对象为原型的对象
Dog.prototype = Object.create(Animal.prototype);
// 修正Dog.prototype对象中constructor属性的指向
Object.defineProperty(Dog.prototype, "constructor", {
value: Dog, //指向Dog构造函数本身
enumerable: false, //不可枚举,在for...in循环中不遍历constructor
writable: true,
});
// 实例化Dog对象
let hsq = new Dog("狗", "拆家");
console.log(hsq);
/*{
name: "狗"
parent: "Animal" // 继承了Animal中定义的parent属性
skill: "拆家"
[[Prototype]]: Animal
}*/
// 调用原型对象上的方法
hsq.say();
hsq.showSkill();
// 我是动物,种类是: 狗
// 我是动物,技能是: 拆家
// 给hsq实例对象添加say方法
hsq.say = function () {
console.log("我是哈士奇,我会" + this.skill);
};
hsq.say();
// 我是哈士奇,我会拆家
ES6继承(class)
ES6规范中新增class关键字,使JavaScript能用更简洁,更符合理解的方式实现面向对象。但实质上JavaScript的calss并不是传统面向对象语言中的calss,仅仅只是ES5规范的语法糖,其本质仍然是基于原型对象和原型链实现继承。
// 定义一个Animal类
class Animal {
// 里面有一个constructor函数,在实例化时会自动调用
constructor() {
this.parent = "Animal";
}
// 在类中添加两个方法
say() {
console.log("我是动物,种类是:", this.name);
}
showSkill() {
console.log("我是动物,技能是:", this.skill);
}
}
// 定义一个Cat类,用extends关键字表示继承自Animal
class Cat extends Animal {
constructor(name, skill) {
// 必须先调用super方法来调用父类Animal的constructor
super();
// 定义自己的实例属性
this.name = name;
this.skill = skill;
}
// 在Cat中也添加一个say方法
say() {
console.log("我是:", this.name);
}
}
// 实例化Cat对象
let Tom = new Cat("猫", "钓鱼");
console.log(Tom);
/*{
name: "猫"
parent: "Animal" // 继承了Animal中定义的parent属性
skill: "钓鱼"
[[Prototype]]: Animal
}*/
Tom.say();
Tom.showSkill();
// 我是: 猫
// 我是动物,技能是: 钓鱼
\
本文为记录自己学习前端知识的个人理解总结,不保证理解正确到位,欢迎评论指出和纠正错误,欢迎一起学习前端。