一、原型链
对于原型链,会从创建对象的方式、原型、构造函数、实例、原型链、instanceof 的原理、new 运算符这几个方面分析
对于创建对象的方式,如下所示:
- 字面量的方式,如下所示:
var o1 = {name: 'o1'};
var o2 = new Object({name: 'o2'});
- 构造函数的方式,如下所示:
var M = function (name) { this.name = name; };
var o3 = new M('o3');
- Object.create 的方式,如下所示:
var p = {name: 'p'};
var o4 = Object.create(p);
对于原型、构造函数、实例、原型链这几个的关系,如下所示:
- 在 JavaScript
中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype
属性,这个属性指向函数的原型对象,使用原型对象的好处是所有对象实例共享它所包含的属性和方法 - 原型链解决的主要是继承问题。每个对象拥有一个原型对象,通过 proto
指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向
null(Object.proptotype.proto 指向的是null)。这种关系被称为原型链(prototype
chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法 - prototype 是构造函数的属性,proto 是每个实例都有的属性,可以访问 [[prototype]] 属性,实例的
proto 与其构造函数的 prototype 指向的是同一个对象 - 原型链是原型对象创建过程的历史记录,当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__
隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构 - 所有函数的__proto__都是指向Function的prototype,构造函数new出来的对象__proto__指向构造函数的prototype,非构造函数实例化出的对象或者对象的prototype的__proto__指向Object的prototype,Object的prototype指向null
- 对于 instanceof的原理,instanceof主要用于判断某个实例是否属于某个类型,也可用于判断某个实例是否是其父类型或者祖先类型的实例。instanceof主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。
对于 new 运算符,如下所示: - 一个新对象被创建,它继承自 foo.prototype
- 构造函数 foo 被执行,执行的时候,相应的参数会被传入,同时上下文 this 会被指定为这个新实例。new foo 等同于 new
foo(),只能用在不传递任何参数的情况 - 如果构造函数返回了一个对象,那么这个对象会去取代整个 new 出来的结果。如果构造函数没有返回对象,那么 new
出来的结果为步骤一创建的对象
对于原型链的代码,如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>原型链</title>
</head>
<body>
<script type="text/javascript">
M.prototype.say = function () {
console.log('say hi');
};
var o5 = new M('o5');
var new2 = function (func) {
var o = Object.create(func.prototype);
var k = func.call(o);
if (typeof k === 'object') {
return k;
} else {
return o;
}
};
</script>
</body>
</html>
二、面向对象
对于面向对象,分为类与实例和类与继承,类与实例包括类的声明、生成实例,类与继承包括实现继承和继承的几种方式。
对于类的声明,如下所示:
- 类的声明,代码如下
var Animal = function () {
this.name = 'Animal';
};
- es6 中 class 的声明
class Animal2 {
constructor () {
this.name = 'Animal2';
}
}
对于类与继承的实现及方式,如下所示:
- 借助构造函数实现继承,但是缺点是 child1 没有继承 Parent1 原型对象的方法,代码如下所示:
function Parent1 () {
this.name = 'parent1';
}
Parent1.prototype.say = function () {
};
function Child1 () {
Parent1.call(this);
this.type = 'child1';
}
console.log(new Child1(), new Child1().say());
- 借助原型链实现继承,但是缺点是 s1 改变的东西,s2 也会看到,代码如下所示:
function Parent2 () {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2 () {
this.type = 'child2';
}
Child2.prototype = new Parent2();
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);
- 组合方式,Parent3 初始化了两次,指向 Parent3 构造函数,代码如下所示:
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3 () {
Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
- 组合继承的优化一,但是缺点是指向 Parent4 的构造函数,无法区分实例是由父类创建的,还是子类创建的,代码如下所示:
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4 () {
Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5, s6);
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor);
- 组合继承的优化二,代码如下所示:
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5 () {
Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);