javascript中构造函数、原型、原型链(prototype)
一、构造函数
1.构造函数是一种特殊的方法。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
2.实际上构造函数并没有特殊创建语法,它与普通函数的区别在于调用方法。对于任意函数,使用new 调用,它就是构造函数;不使用new调用,它就是普通函数。
3.约定构造函数名以大写字母开头,普通函数以小写字母开头,这样有利于显性区分二者
4.使用new操作符调用构造函数时,会经历 1)创建一个新对象; 2)将构造函数作用域赋给新对象(使this指向该新对象); 3)执行构造函数代码; 4)返回新对象;
var obj ={};
obj.__proto__ =Fn.prototype;
Fn.call(obj);
return obj;
创建构造函数实例
var Fn1=function(){
this.name="xiaoming";
this.age=18;
this.func=function(a,b){
return a+b;
}
}
var Fn2=function(){
this.name='xiaozhang';
this.age=20;
this.func=function(a,b){
return a-b;
}
}
var student1=new Fn1();
var student2=new Fn2();
让我们来看一下继承关系
console.log(student1 instanceof Fn1); //true
console.log(student2 instanceof Fn1); //false
console.log(student1 instanceof Fn2); //false
console.log(student2 instanceof Fn2); //true
console.log(student1 instanceof Object); //true 任何对象均继承自Object
console.log(student2 instanceof Object); //true 任何对象均继承自Object
如果我们多次实例化同一个函数
var stu1=new Fn1();
var stu2=new Fn1();
var stu3=new Fn1();
var stu4=new Fn1();
console.log(stu1.func === stu2.func); // false 这里会发现调用的并非同一个方法
这样我们会发现每次实例化的方法不是指向同一个,而是每次都被实例化了一次,这样无疑是内存的浪费,我们可以把func方法移植到Fn1函数外部,让他成为一个全局变量,让他每次实例化的是一个方法名,调用是指向一个函数,从而解决内存浪费
function Fn3(name, age) {
this.name = name;
this.age = age;
this.func = returnFn;
}
function returnFn(a,b) {
return a+b;
}
var stu5 = new Fn3();
var stu6 = new Fn3();
console.log(stu5.func === stu6.func); //true 这里最终都是指向returnFn函数
这样虽然解决了内存浪费,但是如果函数多了,这样会造成大量的全局变量函数,由此js给提供了原型对象。
二、原型
原型总共涉及到3个概念:原型指针__proto__、原型对象、原型链
1)原型指针__proto__
所有对象都有__proto__属性, 当用构造函数实例化(new)一个对象时,会将新对象的__proto__属性指向 构造函数的prototype。
function Fn4(name, age) {
this.name = name;
this.age = age;
}
Fn4.prototype.func=function(a,b){
return a+b;
}
var stu7=new Fn4();
console.log(stu7.__proto__ === Fn4.prototype) //true 当new一个对象时,会将新对象的__proto__属性指向 构造函数的prototype
当我们创建一个函数,系统就会为这个函数自动分配一个prototype指针,指向它的原型对象。并且可以发现,这个原型对象包含两个部分(constructor 和 __proto__)其中constructor指向函数自身。(这里形成了一个小闭环)
console.log(Fn4.prototype); //打印一下 Fn4 的prototype指针
当我们将该函数作为模版创建实例(new方法)的时候,我们发现创建出的实例是一个与构造函数同名的object,这个object是独立的,他只包含了一个__proto__指针(实例没有prototype,强行访问则会输出undefined),这个指针指向上面提到的构造函数的prototype原型对象
console.log(stu7);// 打印实例化的Fn4 ==>stu7
这时候我们发现三者形成了一个大"闭环"。之所以加上引号,因为构造函数和实例之间无法直接访问,需要通过__proto__指针间接读取。
2)prototype
创建的每一个函数都有一个prototype属性,该属性是一个指针,该指针指向了一个对象。对于我们创建的构造函数,该对象中包含可以由所有实例共享的属性和方法。
在默认情况下,所有原型对象会自动包含一个constructor属性,该属性也是一个指针,指向prototype所在的函数
在调用构造函数创建新的实例时,该实例的内部会自动包含一个[[Prototype]]指针属性,该指针指便指向构造函数的原型对象。注意,这个指针关联的是实例与构造函数的原型对象而不是实例与构造函数:
当在stu7中添加私有属性时
stu7.sex='男';
var stu8=new Fn4();
console.log(Fn4.prototype);
console.log(stu7);
console.log(stu8);
当在Fn4中添加属性时,被实例化的不会自动再添加构造函数之后添加的属性
stu7.sex='男';
Fn4.prototype.height="189px";
var stu8=new Fn4();
console.log(Fn4.prototype);
console.log(stu7);
console.log(stu8);
3)原型链
由此原型链的含义就出来了,由于原型对象也有一个_proto_指针,又指向了另一个原型,一个接一个,就形成了原型链。Object.prototype是最顶层的原型,所以如果修改了Object.prototype的属性,那么就影响了所有的对象。