为了更好了理解构造函数,原型,原型链之间的关系,我们先看什么是对象。
1、 对象是什么?
- 说到对象不得不说到类。世界万物都可分类,具有相同的特征和行为的归为一类。比如动物类,植物类。动物类又可划分为,单细胞动物、多细胞动物、哺乳动物;哺乳动物又可分为人,猪,狗等等。
- 动物对象或植物对象所具有的特征就是编程里类的属性,具有的行为就是类的方法。所以说,对象具有类所描述的属性和方法。反过来也可以说类描述了具有相同特征和行为的对象。
- 在程序中创建一个动物对象或植物对象就是以类作为该对象的基础,因此类是对象的模板、蓝图。
- 人们所研究的事物都可称为对象,比如人这个对象,具有人类共同的特征和行为。在Java中通过
Person person = new Person();
这段代码我们创建出了一个人对象。由此又可以看出**类的抽象的,而对象是具体的。**类的具体是对象,对象的抽象的类。
小结:
类:就是描述了一组具有相同特征和行为的对象。
对象:是人们研究的任何事物,所以万物皆对象。对象具有类所描述的属性和方法。
类是对象的模板蓝图。类是抽象的,对象是具体的。对象的抽象是类,类的具体化是对象。
在JS中对象是什么?ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。” 简单来说,对象就是一系列的键值对(key-value),而键值对分为两种,属性(property)和方法(method)。
面向对象编程,就是站在对象的角度来思考问题,把任务抽象成一个个对象,赋予对象一些属性和方法,通过各个对象之间的相互协作,以此来完成任务。
面向过程编程,就是站在过程的角度来思考问题,把任务分成一个个步骤,然后用函数实现各个步骤的功能,再在主函数中一次调用这些函数,来完成任务。
2、对象的创建
-
使用字面量创建对象
var cat = { name: 'xiaomao', color: 'pink' }; console.log(cat); //output //{name: "xiaomao", color: "pink"}
-
利用 new Object 创建对象
var dog = new Object(); dog.name="xiaogou"; dog.color="black"; console.log(dog); //output //{name: "xiaogou", color: "black"}
-
利用构造函数创建对象
function Fish(name, color){ this.name=name; this.color=color; } var fish = new Fish("xiaoyu", "red"); console.log(fish); //output //Fish {name: "xiaoyu", color: "red"}
构造函数的特点:
- 构造函数约定首字母大写。是为了区别于普通函数
- 内部使用this对象,来指向新生成的实例对象
- 函数内的属性和方法前面需要添加 this ,表示新对象的属性和方法。
- 当我们创建对象的时候,必须用 new 来调用构造函数。
3、构造函数
在Java中,类是对象的模板,对象就是类的实例。而在JavaScript中,是利用构造函数(constructor
)作为对象的模板,来生成对象的实例。因此可以把构造函数等同于类,都是为对象提供模板来生成实例的。
通过使用字面量创建对象和利用 new Object 创建对象 都是以Object构造函数为模板的。而我们利用构造函数创建对象,比如下面的代码就是**以Fish这个构造函数为模板创建的对象的。
function Fish(name, color){
this.name=name;
this.color=color;
}
var fish = new Fish("xiaoyu", "red");
console.log(fish);
//output
//Fish {name: "xiaoyu", color: "red"}
当使用new
操作符来调用Fish构造函数
创建对象时,经历以下步骤:
- 调用构造函数,创建一个新对象
- 将
Fish构造函数
的prototype
属性指向空对象的原型对象,这个prototype
是属性,是隐式引用,是原型对象。(下面详细介绍) - 将新对象设置为构造函数内部的
this
关键字,在构造函数中就可以使用this
来引用新对象了。 - 执行构造函数内部的代码。
- 将新对象作为返回值返回。
构造函数的缺点:
- 在一个已存在的对象构造器中是不能添加新的的属性和方法。
- 同一个构造函数来创建的实例对象之间不能共享属性和方法。
解决:
创建一个公共对象或者称之为公共容器,来提供共享的属性和方法。这个公共对象就叫做原型。
function Fish(name, color){
this.name=name;
this.color=color;
}
Fish.prototype.age=12;
Fish.prototype.breathe=function(){
console.log(this.name+" 呼吸")
}
var fish1 = new Fish("xiaoyu1", "red");
var fish2 = new Fish("xiaoyu2", "blue");
console.log(fish1.age===fish2.age); //true
console.log(fish1.breathe===fish2.breathe);//true
4、原型是什么
4.1 原型是什么呢?
prototype
object that provides shared properties for other objects
给其他对象提供共享属性的对象
也就是说,原型也是一个对象,只不过承担了给其他对象提供属性的功能而已。原型描述的是两个对象之间的关联(其中一个为另一个提供属性和方法的访问权限)。我们平常说原型对象时,只是简短的描述,实际上说的是 “xxx对象的原型对象”。
所有的对象都有一个隐式引用
Every object has an implicit reference (called the object’s prototype)
所有的对象都有一个隐式引用,这个隐式引用被称之为该对象的 prototype。
什么叫隐式引用?
function Fish(name, color){
this.name=name;
this.color=color;
}
var fish = new Fish("xiaoyu", "red");
console.log(fish);
从上面可以看出,我们只定义了name和color属性,却发现还有一个__proto__
属性。这意味着,Fish对象被隐式地挂载了另一个对象的引用,置于__proto__
属性中。
4.2 历史问题:__proto__
ECMAScript 规范描述 prototype
是一个隐式引用,但之前的一些浏览器,已经私自实现了 __proto__
这个属性,使得可以通过 fish.__proto__
这个显式的属性,等同于被定义为隐式引用的 prototype
。即fish.__proto__==Fish.prototype
因此,情况是这样的,ECMAScript 规范说 prototype
应当是一个隐式引用:
- 通过
Object.getPrototypeOf(obj)
间接访问指定对象的原型对象。 - 通过
Object.setPrototypeOf(obj, anotherObj)
间接设置指定对象的原型对象。 - 部分浏览器提前开了
__proto__
的口子,使得可以通过obj.__proto__
直接访问原型对象,通过obj.__proto__ = anotherObj
直接设置原型。 - ECMAScript 2015 规范只好向事实低头,将
__proto
__ 属性纳入了规范的一部分。
4.3 小结
原型就是给其他对象提供共享属性和方法的公共对象。
所有对象都有一个非标准属性
__proto__
,通过该属性可以访问到原型对象。
__proto__
是每个对象都有的属性,prototype
是函数才有的属性。
fish.__proto__
等于Fish.prototype
,两者都可以访问到原型对象。
4.4 实践
知道了 原型对象、__proto__
属性、隐式引用pototype
,分析下面代码
function Fish(name, color){
this.name=name;
this.color=color;
}
Fish.prototype.age=12;
Fish.prototype.breathe=function(){
console.log(this.name+" 呼吸")
}
var fish1 = new Fish("xiaoyu1", "red");
var fish2 = new Fish("xiaoyu2", "blue");
console.log(fish1);
console.log(fish2);
console.log(fish1.__proto__===Fish.prototype);//true
console.log(fish1.__proto__===fish2.__proto__);//true
console.log(Fish.prototype.constructor===Fish);//true
constructor
属性是原型对象自带的,意味着可以被实例对象继承
console.log(fish1.constructor)//Fish()
console.log(fish1.constructor===Fish.prototype.constructor)//
constructor
属性的作用:
-
有了
constructor
属性,可以得知某个实例对象,到底是哪一个构造函数产生的console.log(fish1.constructor.name)//Fish
-
有了
constructor
属性,就可以从一个实例对象新建另一个实例//fish1 是构造函数Fish的实例 //从fish1,constructor间接调用构造函数 var fish3 = new fish1.constructor(); console.log(fish3 instanceof Fish);//true
constructor
属性表示原型对象和构造函数的关系,所以修改了原型对象,一般会同时修改construtor
的属性,避免引用出错,instanceof
失真。
4.5 普通对象和函数对象
JS中万物都是对象,但是对象也分为
-
普通对象
-
- 最普通的对象(Object的实例对象)
- 原型对象(Fish.prototype 原型对象还有constructor属性(指向构造函数对象))
-
函数对象:
- 凡是通过new Function()创建的都是函数对象。
需要注意的是:
- 所有对象都有
__proto__
属性,函数对象才有prototype
属性(prototype是属性,是指针,是隐式引用,是原型对象)
//函数对象
function F1(){};
var F2 = function(){};
console.log(typeof F1); //function
console.log(typeof F2); //function
console.log(typeof Object); //function
console.log(typeof Array); //function
console.log(typeof String); //function
console.log(typeof Date); //function
console.log(typeof Function); //function
Array是函数对象,是Function的实例对象,Array是通过newFunction创建出来的。因为Array是Function的实例,所以Array.__proto__ === Function.prototype
5、 原型链
最后结合这张图看,就更加清晰明了了!
我对js原型和原型链的理解 图清晰明了