javascript原型和继承
“一切都是对象”这句话的重点在于如何去理解“对象”这个概念。当然,也不是所有的都是对象,值类型就不是对象。
javascript
中判断一个变量是不是对象?
function show(x) {
console.log(typeof x); // undefined
console.log(typeof ); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
console.log(typeof function () { }); //function
console.log(typeof [, 'a', true]); //object
console.log(typeof { a: , b: }); //object
console.log(typeof null); //object
console.log(typeof new Number()); //object
}
show();
以上代码列出了typeof输出的集中类型标识,其中上面的四种(
undefined
,
number
,
string
,
boolean
)属于简单的值类型,不是对象。剩下的几种情况——函数、数组、对象、
null
、
new Number(10)
都是对象。 他们都是引用类型。
var fn = function () { };
console.log(fn instanceof Object); // true
判断一个变量是不是对象非常简单。值类型的类型判断用
typeof
,引用类型的类型判断用
instanceof
。
有个疑问。在
typeof
的输出类型中,
function
和
object
都是对象,为何却要输出两种答案呢?都叫做
object
不行吗?
函数和对象的关系
函数是一种对象,但是函数却不像数组一样——你可以说数组是对象的一种,因为数组就像是对象的一个子集一样。但是函数与对象之间,却不仅仅是一种包含和被包含的关系,函数和对象之间的关系比较复杂,甚至有一点鸡生蛋蛋生鸡的逻辑,
用案例来解答:
function Fn() {
this.name = '小明';
this.sex = 'male';
}
var fn1 = new Fn();
上面的这个例子很简单,它能说明:对象可以通过函数来创建。
var obj = new Object();
obj.a = ;
obj.b = ;
var arr = new Array();
arr[] = ;
arr[] = 'x';
arr[] = true;
Object 和 Array 都是函数:
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
所以,可以很负责任的说——对象都是通过函数来创建的。
有个疑问:对象是函数创建的,而函数却又是一种对象,函数和对象到底是什么关系啊???
prototype原型
函数也是一种对象。他也是属性的集合,你也可以对函数进行自定义属性,
javascript
自己就先做了表率,人家就默认的给函数一个属性——
prototype
。每个函数都有一个属性叫做
prototype
。
这个
prototype
的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做
constructor
的属性,指向这个函数本身。
SuperType是是一个函数,右侧的方框就是它的原型。
原型既然作为对象,属性的集合,不可能就只有
constructor
,肯定可以自定义的增加许多属性。例如
Object
,他的
prototype
里面,就有好几个其他属性。
你也可以在自己自定义的方法的
prototype
中新增自己的属性
function Fn() { }
Fn.prototype.name = '小明';
Fn.prototype.getSex = function () {
return 'male';
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getSex());
Fn
是一个函数,
fn
对象是从
Fn
函数
new
出来的,这样
fn
对象就可以调用
Fn.prototype
中的属性。因为每个对象都有一个隐藏的属性——“__proto__ ”,这个属性引用了创建这个对象的函数的
prototype
。 即:
fn.__proto__ === Fn.prototype
这里的
"__proto__ "
成为“隐式原型”
隐式原型
每个函数
function
都有一个
prototype
,即原型。这里再加一句话——每个对象都有一个
__proto__
,可成为隐式原型。
这个
__proto__
是一个隐藏的属性,
javascript
不希望开发者用到这个属性值,有的低版本浏览器甚至不支持这个属性值。
上面截图看来,
obj.__proto__
和
Object.prototype
的属性一样!这么巧!
答案就是一样。
obj这个对象本质上是被
Object
函数创建的,因此
obj.__proto__ === Object.prototype
。我们可以用一个图来表示。
即,每个对象都有一个
__proto__
属性,指向创建该对象的函数的
prototype
。
那么上图中的
“Object prototype”
也是一个对象,它的
__proto__
指向哪里?
在说明
“Object prototype”
之前,先说一下自定义函数的
prototype
。自定义函数的
prototype
本质上就是和
var obj = {}
是一样的,都是被
Object
创建,所以它的
__proto__
指向的就是
Object.prototype
。
但是
Object.prototype
确实一个特例——它的
__proto__
指向的是
null
函数也是一种对象,函数也有
__proto__
吗?
函数也是被创建出来的。谁创建了函数呢?
Function
注意这个大写的
“F”
。
function fn(x,y) {
return x + y;
};
console.log(fn(, ));
var fn1 = new Function('x' , 'y', 'return x + y;');
console.log(fn1(,));
以上代码中,第一种方式是比较传统的函数创建方式,第二种是用
new Functoin
创建。
根据上面说的一句话——对象的
__proto__
指向的是创建它的函数的
prototype
,就会出现:
Object.__proto__ === Function.prototype
。用一个图来表示。图中标出了:自定义函数
Foo.__proto__
指向
Function.prototype
,
Object.__proto__
指向
Function.prototype
Function.prototype
指向的对象,它的
__proto__
是不是也指向
Object.prototype
答案是肯定的。因为
Function.prototype
指向的对象也是一个普通的被
Object
创建的对象,所以也遵循基本的规则。
介绍一下instanceof
先看例子
function Foo() {};
var f1 = new Foo();
console.log(f1 instanceof Foo); //true
console.log(f1 instanceof Object) //true
Instanceof
运算符的第一个变量是一个对象,暂时称为
A
;第二个变量一般是一个函数,暂时称为
B
。
Instanceof
的判断规则是:沿着
A
的
__proto__
这条线来找,同时沿着
B
的
prototype
这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回
true
。如果找到终点还未重合,则返回
false
。所以
f1 instanceof Object
这句代码就是
true
介绍一下继承
javascript
中的继承是通过原型链来体现的
function Foo() { };
var f1= new Foo();
f1.a = ;
Foo.prototype.a = ;
Foo.prototype.b = ;
console.log(f1.a);
console.log(f1.b);
f1
是
Foo
函数
new
出来的对象,
f1.a
是
f1
对象的基本属性,
f1.b
是怎么来的呢?——从
Foo.prototype
得来, 因为
f1.__proto__
指向的是
Foo.prototype
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着
__proto__
这条链向上找,这就是原型链。
访问
f1.b
时,
f1
的基本属性中没有
b
,于是沿着
__proto__
找到了
Foo.prototype.b
。有疑问可以看看上一节将
instanceof
时候那个大图
继承的演变
ES3 => ES5 => ES6 的变化过程
因为我们现在用的
react
做的项目对
ES6
语法用的比较多,所以再详细说一下:
extends
super
static
下面是
ES3
的写法
function Student(props) {
this.name = props.name || 'Unnamed';
} //构造函数
Student.prototype.hello = function() {
console.log('Hello, ' + this.name + '!');
} //原型
function PrimaryStudent(props) {
Student.call(this, props); //拿到Student构造函数的this.name = props.name || 'Unnamed';
this.grade = props.grade || ;
}
// es3实现继承
function F() {}
F.prototype = Student.prototype;
PrimaryStudent.prototype = new F();
PrimaryStudent.prototype.constructor = PrimaryStudent;
PrimaryStudent.prototype.getGrade = function() {
return this.grade;
}
var xiaoming = new PrimaryStudent({
name: '小明',
grade: '2',
});
console.log(xiaoming.name); // 小明
console.log(xiaoming.grade) // 2
// 验证原型:
console.log(xiaoming.__proto__ === PrimaryStudent.prototype) // true
console.log(xiaoming.__proto__ .__proto__ === Student.prototype) // true
// 验证继承关系
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
ES5
的继承写法:
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function() {
console.log('Hello, ' + this.name + '!');
}
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || ;
}
PrimaryStudent.prototype.getGrade = function() {
return this.grade;
}
// es5实现继承
PrimaryStudent.prototype = Object.create(Student.prototype);
PrimaryStudent.prototype.constructor = PrimaryStudent;
var xiaoming = new PrimaryStudent({
name: '小明',
grade: '2',
});
console.log(xiaoming.name); // 小明
console.log(xiaoming.grade) // 2
// 验证原型:
console.log(xiaoming.__proto__ === PrimaryStudent.prototype) // true
console.log(xiaoming.__proto__ .__proto__ === Student.prototype) // true
// 验证继承关系
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
ES6
的继承写法:
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function() {
console.log('Hello, ' + this.name + '!');
}
class PrimaryStudent extends Student {
constructor(props) {
super(props);
this.grade = props.grade || ;
}
getGrade() {
return this.grade;
}
}
var xiaoming = new PrimaryStudent({
name: '小明',
grade: '2',
});
console.log(xiaoming.name); // 小明
console.log(xiaoming.grade) // 2
// 验证原型:
console.log(xiaoming.__proto__ === PrimaryStudent.prototype) // true
console.log(xiaoming.__proto__ .__proto__ === Student.prototype) // true
// 验证继承关系
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
extends 继承内建类的能力
因为语言内部设计原因,我们没有办法自定义一个类来继承
JavaScript
的内建类的。继承类往往会有各种问题。
ES6
的
extends
的最大的卖点,就是不仅可以继承自定义类,还可以继承 JavaScript 的内建类,比如这样:
class MyArray extends Array {
}
这种方式可以让开发者继承内建类的功能创造出符合自己想要的类。所有 Array 已有的属性和方法都会对继承类生效。这确实是个不错的诱惑,也是继承最大的吸引力。举个例子:
var a = new Array(, , )
a.length // 3
var b = new MyArray(, , )
b.length // 3
super 在 constructor 和普通方法里的不同
在
constructor
里面,
super
的用法是
super(..)
。它相当于一个函数,调用它等于调用父类的
constructor
。但在普通方法里面,
super
的用法是
super.prop
或者
super.method()
。它相当于一个指向对象的
__proto__
的属性。这是
ES6
标准的规定,
如果写子类的
constructor
需要操作
this
,那么
super
必须先调用!这是
ES6
的规则。所以写子类的
constructor
时尽量把
super
写在第一行。
super
是编译时确定(可以理解为定义时确定),不是运行时确定
总结:
如何记住原型链,这里和
html
事件冒泡差不多
<null>
<Object>//原型链顶端
<parent>
<child></child>
</parent>
</Object>
</null>
ES6
中继承:
class Parent {
constructor(firstName, lastName) {
console.log(firstName,lastName)
}
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
class Child extends Parent { //extends:不仅可以继承自定义类,还可以继承 JavaScript 的内建类;
static findAll() { //static: 该方法不会被实例继承, 而是直接通过类来调用
return super.findAll()
}
constructor(firstName, lastName, age) {
super(firstName, lastName); // super: Parent.call(this, firstName, lastName)相当于函数执行Parent();
this.age = age
}
}
ES5
最经典的继承方法是用组合继承的方式,原型链继承方法,借用函数继承属性,
ES6
也是基于这样的方式,但是封装了更优雅简洁的
api
,让
Javascript
越来越强大,修改了一些属性指向,规范了继承的操作,区分开了继承实现和实例构造,此外
ES6
继承还能实现更多的继承需求和场景。