面向对象语言存在类(class)的概念,但在javascript 语言中不存在类的概念,javascript中不是基于‘类的’,而是通过构造函数(cnstructor)和原形链(prototype chains)实现的。但在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,通过class关键字,可以定义类,实际上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让原型对象的写法更加清晰、更像面向对象编程的语法而已。
什么是构造函数
1:构造函数的函数名首字母必须大写
2:内部使用this对象,来指向将要生成的对象实例。
3:使用new操作符来调用构造函数,并返回对象实例
demo:
function Student(){
this.name="zhongshan"
}
var boy = new Student();
扩展:
new操作符具体干了什么呢?其实很简单,就干了三件事情。
|
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
|
第一行,我们创建了一个空对象obj
第二行,我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
第三行,我们将Base函数对象的this指针替换成obj,然后再调用Base函数,于是我们就给obj对象赋值了一个id成员变量,这个成员变量的值是”base”,关于call函数的用法。
构造函数的缺点
所有的实例对象都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。
|
function
Person(name,height){
this
.name=name;
this
.height=height;
this
.hobby=
function
(){
return
'watching movies'
;
}
}
var
boy=
new
Person(
'keith'
,180);
var
girl=
new
Person(
'rascal'
,153);
console.log(boy.name);
//'keith'
console.log(girl.name);
//'rascal'
console.log(boy.hobby===girl.hobby);
//false
|
上面代码中,一个构造函数Person生成了两个对象实例boy和girl,并且有两个属性和一个方法。但是,它们的hobby方法是不一样的。也就是说,每当你使用new来调用构造函数放回一个对象实例的时候,都会创建一个hobby方法。这既没有必要,又浪费资源,因为所有hobby方法都是童颜的行为,完全可以被两个对象实例共享。
所以,构造函数的缺点就是:同一个构造函数的对象实例之间无法共享属性或方法。
prototype属性
为了解决构造函数对象实例之间无法共享属性的缺点,js提供了prototype属性。
js中每个数据类型都是对象(null,undefined除外),而每个对象都继承另外一个对象,后者称为“原型”对象,只有null除外,它没有自己的原型对象
原型对象上的所有属性和方法,都会被对象实例所共享
通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。
|
function
Person(name,height){
this
.name=name;
this
.height=height;
}
Person.prototype.hobby=
function
(){
return
'watching movies'
;
}
var
boy=
new
Person(
'keith'
,180);
var
girl=
new
Person(
'rascal'
,153);
console.log(boy.name);
//'keith'
console.log(girl.name);
//'rascal'
console.log(boy.hobby===girl.hobby);
//true
|
上面代码中,如果将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象
原型对象的属性不是对象实例的属性。对象实例的属性是继承自构造函数定义的属性,因为构造函数内部有一个this关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。
|
Person.prototype.hobby=
function
(){
return
'swimming'
;
}
console.log(boy.hobby===girl.hobby);
//true
console.log(boy.hobby());
//'swimming'
console.log(girl.hobby());
//'swimming'
|
上面代码中,当修改了原型对象的hobby方法之后,两个对象实例都发生了变化。这是因为对象实例其实是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。
boy.hobby=
function
(){
return
'play basketball'
;
}
console.log(boy.hobby());
//'play basketball'
console.log(girl.hobby());
//'swimming'
|
上面代码中,boy对象实例的hobby方法修改时,就不会在继承原型对象上的hobby方法了。不过girl仍然会继承原型对象的方法。
总结:
a:原型对象的作用,就是定义所有对象实例所共享的属性和方法。
b:prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。
原型链
对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型对象本身对于对象实例来说也是对象,它也有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。所有一切的对象的原型顶端,都是Object.prototype,即Object构造函数的prototype属性指向的那个对象。
当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的null对象,而null对象没有自己的原型。
1 console.log(Object.getPrototypeOf(Object.prototype)); //null
2 console.log(Person.prototype.isPrototypeOf(boy)) //true
原型链(prototype chain)的特点有:
a:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
b:如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
看概念可能比较晦涩,我们来看一个例子。但是理解了概念真的很重要。
|
var
arr=[1,2,3];
console.log(arr.length);
//3
console.log(arr.valueOf())
//[1,2,3]
console.log(arr.join(
'|'
))
//1|2|3
|
上面代码中,定了一个数组arr,数组里面有三个元素。我们并没有给数组添加任何属性和方法,可是却在调用length,join(),valueOf()时,却不会报错。
length属性是继承自Array.prototype的,属于原型对象上的一个属性。join方法也是继承自Array.prototype的,属于原型对象上的一个方法。这两个方法是所有数组所共享的。当实例对象上没有这个length属性时,就会去原型对象查找。
valueOf方法是继承自Object.prototype的。首先,arr数组是没有valueOf方法的,所以就到原型对象Array.prototype查找。然后,发现Array.prototype对象上没有valueOf方法。最后,再到它的原型对象Object.prototype查找。