构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员赋初始值,总是与new一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
构造函数采用大驼峰命名规则,即首字母也要大写。
构造函数中成员(属性和方法)分为两种:
- 静态成员:构造函数本身添加的成员,只能通过构造函数访问。(sex)
- 实例成员:构造函数内部通过this添加的成员,只能通过实例对象访问。(name、age、say)
function Person(uname,age){ //定义构造函数
this.uname = uname;
this.age = age;
this.say = function(){
console.log('hey~');
}
}
var tom = new Person('tom',18);
var andy = new Person('andy',19);
console.log(tom.age); //18
andy.say(); //hey~
Person.sex = 'male';
console.log(Person.sex); //male
console.log(tom.sex); //undefined 不能通过实例访问静态成员
console.log(Person.uname); //undefined 不能通过构造函数访问实例成员
new在执行使发生了什么:
- 在内存中创建一个新的空对象;
- 让this指向这个对象;
- 执行构造函数内的代码,为这个新对象添加属性和方法;
- 返回这个对象(所有构造函数里面不需要return);
若在构造函数内部添加
return;或者return 简单数据;会自动忽略,但若return 对象;这个对象内的成员就会替换构造函数内的成员。比如,上述代码中构造函数内添加return {age:6};那么它所有的实例成员都只有一个属性age=6;
空间浪费
在通过上述构造函数创建两个实例的过程中,因为每个实例都有一个方法,所有会为各自单独开辟一块空间来存放这个方法,问题就是造成了空间的浪费,因为方法是相同的,却有两个地址,占用了两块空间。
console.log(tom.say === andy.say); //false
这个问题的解决,就用到构造函数原型对象 prototype
构造函数原型对象 prototype
prototype属性指向另一个对象,这个prototype本身也是一个对象,它的所有属性和方法都被构造函数所拥有,也就是可以被构造函数的实例访问。prototype的主要作用是共享方法。
为prototype添加方法:
构造函数.prototype.方法名 = function() {...}
上面的代码就可以变成:
function Person(uname,age){
this.uname = uname;
this.age = age;
}
Person.prototype.say = function(){ //将方法添加到prototype中实现共享
console.log('hey~');
}
var tom = new Person('tom',18);
var andy = new Person('andy',19);
console.log(tom.age); //18
console.log(tom.say === andy.say); //false
andy.say(); //hey~
这时再来验证:
console.log(tom.say === andy.say); //true
可见方法已经实现共享,每个实例都可以访问这个方法。
那实例对象是如何访问到prototype上方法的呢?
因为在每个对象身上都有一个 _proto_ 属性,指向构造函数的原型对象prototype,所有可以使用原型对象的方法。
console.log(tom.__proto__ === Person.prototype); //true
方法查找规则:
现在自身查找是否有这个方法,有则调用自身方法,没有则沿着 去_proto_指向的prototype身上去查找。
constructor属性
_proto_ 和 prototype 中都有这个属性,它指向构造函数本身,记录实例对象是通过哪个构造函数创建的。
经常需要手动利用constructor属性指回原来的构造函数。
为prototype添加方法还有第二种形式,适用于方法较多时:
构造函数.prototype = {方法1:...,方法2:... }
但这种形式会有一个问题:
- 第一中方法输出
tom.\__proto__.constructor/Person.prototype.constructor:
{say: ƒ, constructor: ƒ}
say: ƒ ()
constructor: ƒ Person(uname,age)
_proto_: Object - 第二种方法输出
tom.\__proto__.constructor/Person.prototype.constructor:
{say: ƒ}
say: ƒ ()
_proto_: Object
因为对象赋值的形式会覆盖掉prototype中原有的属性方法,包括指向构造函数的constructor属性也没有了,所有手动添加 constructor属性使其指向原来的构造函数。
原型链

数组也是对象,Array.prototype.proto === Object.prototype。
绝大多数对象的最终都会继承自Object.prototype,一个例外是:
通过
Object.create(null);创建的对象。
总结:
- 构造函数都有一个原型对象,如Array.prototype,原型对象上的方法可以被构造函数的实例对象共享
- 构造函数的实例对象上都有一个指针proto,指向构造函数的原型对象,如:[].proto === Array.prototype
- 原型对象上都有一个指针可以指回构造函数,如:Array.prototype.constructor === Array,[].constructor === Array
- 构造函数本身是一个函数,是Function构造函数的实例对象,如:Array.proto === Function.prototype,然后你会发现 Function.proto === Function.prototype 也是true;
- 数组或是函数都是对象,所以:Array.prototype.proto === Object.prototype, [].proto.proto === Object.prototype
这就是原型链,好像还挺神奇的