JavaScript 原型与原型链详细解析
说明
JavaScript 是基于原型实现的面向对象,这篇文章整理了一下对象创建、原型、原型链、继承等的理解
文章内容
- 创建对象基础方法
- 原型与原型链详解
- 构造函数创建对象及实现继承的方法详解
创建对象基础方法
1. 对象字面量
创建对象最简单的方法
var obj = {
name: 'name',
getName: function () {
console.log(this.name);
}
}
2. 工厂方法
用于创建多个类型一致的对象
function createObj(name) {
return {
name: name,
getName: function () {
console.log(this.name);
}
};
}
var obj1 = createObj('xiaobai');
var obj2 = createObj('xiaolan');
obj1.getName(); // xiaobai
obj2.getName(); // xiaolan
3. 通过克隆其他对象
Object.create()
var obj = {
name: 'name',
getName: function () {
console.log(this.name);
}
}
var o = Object.create(obj);
o.name = 'name o';
o.getName(); // name o
4. 构造函数方式
这里是比较原始的构造函数方式,后边会根据原型进行修改
function Anm(name) {
this.name = name;
this.test = function () {
console.log(this.name);
}
}
var anm1 = new Anm('xiaobai');
anm1.test(); // xiaobai
原型与原型链
javascript 中没有类,js 是基于原型来实现的面向对象
1. __proto__
属性 与 prototype
属性
重点
- 每一个对象都由一个
__proto__
的隐藏属性,这个属性指向了这个对象构造函数的原型对象 - 每一个函数都由一个
prototype
的属性,这个属性指的就是这个函数的原型对象 - 原型对象也是一个对象,既然是对象就会有名为
__proto__
的隐藏属性 - JavaScript 通过
__proto__
链接形成了一条基于原型的链,原型链的尽头是null
- javascript 中函数也是一个对象,既然是对象就会有
__proto__
属性,函数的__proto__
属性指向他的构造函数的原型对象 (Function.prototype
) Object
和Function
也是构造函数,他们的原型对象有些特殊
2. 代码详解
1. 每一个对象都由一个 __proto__
的隐藏属性,这个属性指向了这个对象构造函数的原型对象
检查一下是否有
__proto__
属性(Chrome 开发者工具)
// 字面量对象
var o = {name: 'o'};
console.log(o);
// 构造函数创建的对象
var Anm = function (name) {
this.name = name;
}
var anm = new Anm('anm');
console.log(anm);
检查
__proto__
是否指向构造函数的原型对象
// 构造函数创建的对象
var Anm = function (name) {
this.name = name;
}
// 手动将构造函数 Anm 的原型对象至为一个对象
Anm.prototype = {
test: function () {}
}
var anm = new Anm('anm');
console.log(anm.__proto__); // {test: function}
// 查看是否指向 Anm.prototype
console.log(anm.__proto__ === Anm.prototype); // true
2. 每一个函数都由一个 prototype
的属性,这个属性指的就是这个函数的原型对象
通过控制台打印
var Anm = function (name) {
this.name = name;
}
Anm.prototype.test = function () {}
var anm = new Anm('anm');
console.log(Anm.prototype);
普通函数的
prototype
function test() {
console.log('test');
}
console.log(test.prototype);
可以看到 每个原型对象中都默认有一个属性
constructor
,这个属性指向这个原型对象的构造函数
console.log(Anm.prototype.constructor === Anm); // true
console.log(test.prototype.constructor === test); // true
3. 原型对象也是一个对象,既然是对象就会有名为 __proto__
的隐藏属性
接着上一个例子,我们在控制台打印原型对象时会看到原型对象除了 默认添加了
constructor
属性,同时还有__proto__
属性存在,前边讲过__proto__
指向创建当前对象的构造函数的 原型对象,下边具体测试一下。
// 先找出构造 Anm 原型对象的构造函数
console.log(Anm.prototype.__proto__.constructor); // function Object() { [native code] }
// 从打印出的值来看 构造函数是 Object,下边测试一下
console.log(Anm.prototype.__proto__ === Object.prototype); // true
4. JavaScript 通过 __proto__
链接形成了一条基于原型的链,原型链的尽头是 null
当调用对象的某个属性或者方法时,如果这个对象没有这个属性或者方法,他会把调用请求委托给它自己的原型,也就是通过
__proto__
将请求传递下去,如果它的原型有这个方法或者属性就会调用,否则继续向下传递在 JavaScript 中一切皆对象,所以通过原型链传递,最后会传递到
Object.prototype
如果Object.prototype
上页也没有需要的方法,则会最终传递给Object.prototype.__proto__ === null
// 创建父类
var Anm = function (name) {
this.name = name;
}
Anm.prototype.test = function () {
console.log(this.color);
}
// 创建子类
var Cat = function (color) {
this.color = color;
}
// 子类的原型指向父类的一个实例;
Cat.prototype = new Anm('cat');
// 将子类原型的构造函数指向自己
Cat.prototype.constructor = Cat;
var cat = new Cat('red');
console.log(cat);
// 可以用 对象的 hasOwnProperty 方法判断一个对象是否含有某个属性
// 调用 test 方法
cat.test(); // red
// 查看 cat 对象是否有 test 方法
console.log(cat.hasOwnProperty('test')); // false
// 查看 cat 的原型对象是否有 test 方法
console.log(cat.__proto__.hasOwnProperty('test')); // false
// 查看 cat 的原型对象 的原型对象是否有 test 方法
console.log(cat.__proto__.__proto__.hasOwnProperty('test')); // true
// 原型链的尽头
console.log(Object.prototype.__proto__) // null
5. javascript 中函数也是一个对象,既然是对象就会有 __proto__
属性,函数的 __proto__
属性指向他的构造函数的原型对象 (Function.prototype
)
控制台打印普通函数的原型
var test = function () {
console.log('test');
}
console.log(test.__proto__); // function () { [native code] }
function () { [native code] }
这样不方便查看,所以我换了一种方式,对象中的方法也是一个函数,直接在控制台查看
var o = {
test: function () {
console.log('test');
}
}
console.log(o);
可以看到 test 函数是有
__proto__
属性的,它的constructor
指向Function
,下边来证实一下
console.log(o.test.__proto__.constructor === Function); // true
console.log(o.test.__proto__ === Function.prototype); // true
6. Object
和 Function
也是构造函数,他们的原型对象有些特殊
文章的开始有一个原型链的表示图,在javascript 中无论是通过构造函数还是还是通过字面量方法创建的对象都遵守以上规则,但是
Object
和Function
作为比较底层的,与其他对象或者函数有一些区别
var f = function () {};
// 函数的 __proto__ 指向 Function.prototype
console.log(f.__proto__ === Function.prototype); // true
// Function 的原型对象 指向他自己的 __proto__
console.log(Function.prototype === Function.__proto__); // true
// Function 原型的 __proto__ 指向 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true
// Object 原型的 __proto__ 指向 null
console.log(Object.prototype.__proto__ === null); // true
// Object 也是构造函数所有他的 __proto__ 指向 Function.prototype
console.log(Object.__proto__ === Function.prototype ); // true
这部分有点绕,这里有一张图说明Javascript的原型链图
构造函数创建对象及实现继承的方法详解
1. 组合式继承
// 创建父类
var Anm = function (type) {
// 创建属性
this.type = type;
}
// 原型上定义方法,这样子类、创建的对象就都可以使用这个方法
Anm.prototype.getType = function () {
console.log(this.type);
}
// 创建子类
var Cat = function (name) {
// 私有属性
var catType = 'cat';
// 通过改变this指针的指向获得使用父类函数中定义的属性
Anm.call(this, catType);
// Anm.apply(this, arguments);
this.name = name;
}
// 将子类的原型转向一个父类的实例
Cat.prototype = new Anm();
// 将 Cat 原型的 constructor 构造函数重新指向自己
Cat.prototype.constructor = Cat;
// 定义子类的方法
Cat.prototype.getName = function () {
console.log(this.name);
}
var cat = new Cat('xiaobai');
cat.getName(); // xiaobai
cat.getType(); // cat
2. 其他的方式实现继承
// 定义一个继承函数
function extend (subClass, supClass) {
// 定义一个临时函数
var F = function () {};
// 临时函数的原型指向父类的原型
F.prototype = supClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
}
var Anm = function () {};
Anm.prototype.getName = function () {
console.log(this.name);
}
var Cat = function (name) {
this.name = name;
};
extend(Cat, Anm);
var cat = new Cat('xiaobai');
cat.getName(); // xiaobai
其他
1. Object.create()
创建对象的问题
Object.create()
复制的对象会将被复制的对象作为新对象的 proto
var Anm = function () {};
Anm.prototype.getName = function () {
console.log(this.name);
}
var Cat = function (name) {
this.name = name;
};
Cat.prototype = new Anm();
Cat.prototype.constructor = Cat;
var cat = new Cat('xiaobai');
// 使用 Object.create() 复制对象
var dog = Object.create(cat);
dog.getName(); // xiaobai
// 新对象的原型指向被复制的对象
console.log(dog.__proto__ === cat); // true
// 新对象的原型上没有 constructor 属性,这个属性是从被复制对象的原型上继承的
console.log(dog.__proto__.constructor === Anm); // true
console.log(dog.__proto__.hasOwnProperty('constructor')); //false
console.log(dog.__proto__.__proto__.hasOwnProperty('constructor')); // true
2. ES6 Class
ECMAScript 6 带来了新的 Class 语法,这让javascript 看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象
class Anm {
constructor(type) {
this.type = type;
}
getType() {
console.log(this.type);
}
}
class Cat extends Anm {
constructor(name) {
const catType = 'cat';
super(catType);
this.name = name;
}
getName() {
console.log(this.name);
}
}
var cat = new Cat('xiaobai');
console.log(cat); // {type: "cat", name: "xiaobai"}
console.log(cat.__proto__ === Cat.prototype); // true
console.log(cat.__proto__.constructor === Cat); // true
console.log(cat.__proto__.__proto__ === Anm.prototype); // true