JavaScript深入理解 —— 原型、原型链和继承
2017-09-17 本文已影响40人
fehysunny
普通对象和函数对象
函数对象:使用函数声明
、函数表达式
、Function构造函数
创建的对象
函数实际上是对象,每个函数都是
Function
类型的实例,而且都与其他引用类型一样具有属性和方法。
普通对象:除了函数对象
以外的对象,都是普通对象。
示例:
// 定义函数的三个方法
function f1() {}; // 函数声明
var f2 = function() {}; // 函数表达式
var f3 = new Function("num1", "num2", "return num1 + num2"); // Function构造函数
// 创建对象
var o1 = {}; // 对象字面量
var o2 = new Object(); // Object构造函数
var o3 = new f1(); // f1构造函数
// 检测类型
console.log(typeof Function); //function
console.log(typeof Object); //function
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
构造函数
构造函数: 通过new
关键字方式调用的函数。
在构造函数内部(也就是被调用的函数内):
-
this
指向实例对象Object
; - 这个实例对象的
__proto__
属性指向构造函数的prototype
; - 这个实例对象的
constructor
属性指向构造函数
; - 如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 —— 也就是实例对象。
示例:
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
var person1 = new Person("Hysunny");
var person2 = new Person("Max");
console.log(person1 instanceof Person); // true
console.log(person1.constructor === person2.constructor); // true
// 实例的__proto__属性指向构造函数的prototype
console.log(person1.__proto__ === Person.prototype); // true
// 实例的constructor指向构造函数
console.log(person1.constructor === Person); // true
原型对象
在 JavaScript 中,每当定义一个对象(函数)时候,对象中都会包含一些预定义的属性。其中就包含prototype
属性,这个属性指向函数的原型对象
。
- 原型对象是一个
普通对象
(除Function.prototype
之外),存储所有实例对象共享的方法和属性; - 构造函数原型对象是
构造函数
的一个实例。 - 原型对象的
constructor
属性指向prototype
属性所在的函数; - 每个对象都有
__proto__
属性,但只有函数对象才有prototype
属性,这两个属性指向函数的原型对象。
示例:
function Person(name) {
this.name = name;
}
Person.prototype. sayName = function() {
console.log(this.name);
}
var person1 = new Person("Hysunny");
var person2 = new Person("Max");
// 构造函数、原型对象和实例之间有这样的联系
Person.prototype.constructor === Person;
person1.__proto__ === Person.prototype;
person1.constructor === Person;
console.log(person1.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.constructor === person1.constructor); // true
// => 原型对象是构造函数的一个实例
console.log(Person.prototype.constructor == Person); // true
// => 原型对象的constructor属性指向prototype属性所在的函数
console.log(person1.__proto__ === Person.prototype); // true
// => __proto__属性指向函数的原型对象
注: Function.prototype
虽为函数对象,但是是个空函数,没有prototype
属性。
console.log(typeof Function.prototype) // function
console.log(typeof Function.prototype.prototype) // undefined
原型链
从上面的分析我们可以知道:JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个__proto__
内置属性,用于指向创建它的函数对象的原型对象prototype
。
以上面的例子为例:
-
person1
具有__proto__
属性,指向Person.prototype
-
Person.prototype
具有__proto__
属性,指向Object.prototype
-
Object.prototype
具有__proto__
属性,指向null
console.log(person1.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
这些__proto__
串起来,就构成了原型链,原型链的顶端为null
。绘出原型链图如下:
简化如下:
prototype.png疑点解释:
1. Object.__proto__ === Function.prototype // true
// Object是函数对象,是通过new Function()创建,所以Object.__proto__指向Function.prototype。
2. Function.__proto__ === Function.prototype // true
// Function 也是对象函数,也是通过new Function()创建,所以Function.__proto__指向Function.prototype。
3. Function.prototype.__proto__ === Object.prototype // true
// Function本身也是一个构造函数,所以`Function.prototype.__proto__`指向`Object.prototype`
继承
继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式:
接口继承
和实现继承
。接口继承只继承方法签名,而实现继承则继承实际的方法.由于js中方法没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其实现继承
主要是依靠原型链来实现的.
为什么要实现继承呢?
最重要的原因之一就是为了抽象(复用代码)
。
将公共的代码封装成一个基类,其他子类继承基类,并发展自己特有的属性和样式。
关于实现继承
的方式,我们将在下一篇文章中进行讨论。
参考资料:
《JavaScript 高级程序设计》 第三版