典型的面向对象编程语言(比如C++和Java),存在“类”(class)这个概念。所谓“类”就是对象的模板,对象就是“类”的实例。但是,在JavaScript语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)
‘面向对象编程’的第一步,就是要生成对象。而js中面向对象编程是基于构造函数(constructor)和原型链(prototype)的。
前面说过,“对象”是单个实物的抽象。通常需要一个模板,表示某一类实物的共同特征,然后“对象”根据这个模板生成。
js语言中使用构造函数(constructor)作为对象的模板,所谓构造函数,就是提供一个生成对象的模板,并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。
对于JS中的任何一个普通函数,当用new关键字来调用时,它就是构造函数。可见与函数定义无关,与调用方法有关。在社区中,通常默契地将函数名首字母大写来表示该函数以后希望被作为构造函数来使用
作用:构造新对象,设置对象的属性和方法
ECMAScript提供了多个内置构造函数,如 Object、Array、String、Boolean、Number、Date…等等。
var obj = new Object();
var arr = new Array();
ECMAScript也允许自定义构造函数
构造函数一般首字母会大写,为了和普通函数区分
一个构造函数可以通过new创建多个实例对象
创建构造函数时,里面的属性和方法前必须加this,this就表示当前运行时的对象
function Person(name, height) {
this.name = name;
this.height = height;
this.bark = function(fs){
return fs
}
}
var boy = new Person('Keith', 180);
console.log(boy); //Person {name: 'Keith', height: 180, bark: ƒ}
console.log(boy.constructor); //f Person(){} //整个构造函数原型
console.log(boy.bark(8)); //8
console.log(boy.name); //'Keith'
console.log(boy.height); //180
function Cat1(name){
this.name = name;
console.log(this) //先打印 new的时候打印 Cat1 {name: 'kk'}
}
var cat3 = new Cat1("kk");
console.log(cat3); //后打印 Cat1 {name: 'kk'} 指向原型链,再赋值
构造函数的return
function Dog(){
this.name = "贝贝";
this.bark = function(){
console.log("汪汪汪");
}
// return 0;
// return [];
}
var d1 = new Dog();
console.log(d1);//Dog {name: '贝贝', bark: ƒ}
//构造函数不需要return 就可以返回结果
return一个基本数据类型,结果不变,依旧返回一个对象
例:
return 0;
console.log(d1);//Dog {name: '贝贝', bark: ƒ}
return一个复杂数据类型,返回一个复杂数据类型
例:
return [];
console.log(d1);//[]
构造函数构造出的对象带有类型标识
console.log(p1)
Person {
name: 'zs',
age: 12,
eating: [Function: eating],
} //打印结果有类型标识 //Person 就是类型标识
console.log(p1)
{name: 'zs', age: 12, eating:[Function: eating]} //无类型标识的
构造函数的原理(new之后发生了什么)
构造函数之所以能构造出对象,其实JS帮助我们做了很多骚操作。你以为new之后直接执行函数体代码,其实并不是,事实比我们看到了多了四步
1 自从用new调用函数后,JS引擎就会在内存中创建一个空对象{}
const newObj = {};
2 新对象的__proto__属性指向构造函数的原型对象
(通俗理解就是新对象隐式原型__proto__链接到构造函数显式原型prototype上。)
newObj.__proto__ = functionName.prototype
3 构造函数内部的this会指向这个新对象(即将构造函数的作用域指向新对象)
this = newObj
4 从上到下执行函数体(只有这步是我们能直观看到代码的)
5 返回创造出来的对象(如果构造函数没有返回对象,则默认返回this。在函数体内部的this指向新创建的内存空间,默认返回 this 就相当于默认返回了该内存空间)
例子:
function Person(name, age) {
this.name = name;
this.age = age;
this.eating = function() {
console.log(this.name + ' is eating');
}
}
const p1 = new Person('zs', 12);
//----------------------------------------------------------------------------
/*实际JS引擎帮助我们实现的操作*/
const newObj = {};
newObj.__proto__ = Person.prototype;
this = newObj;
this.name = name;
this.age = age;
this.eating = function() {
console.log(this.name + ' is eating');
}
return newObj;
在构造函数原型上绑定方法节省空间
采用构造函数的确可以批量创建对象,且对象还都有该构造函数的logo,那么构造函数有什么缺点吗?有的。由于每次函数调用都会创建新的对象,对象中的函数(比如eating)也会创建多份,对于函数而言创建多份没有必要,能不能共用一个函数呢?
function Person(name, age) {
this.name = name;
this.age = age;
}
const p1 = new Person('zs', 12);
// 在函数原型上添加方法
Person.prototype.eating = function() {
console.log(this.name + ' is eating'); // zs is eating
return 5
}
console.log(p1.eating()); //5
将方法转移到构造函数原型上来定义后,对于实例对象p1,依然可以调用eating方法。调用p1的eating方法时,如果p1对象没有该方法,会去p1对象的原型对象p1.__proto_
找,因为在构造p1时,绑定的原型:p1.__proto__ = Person.prototype
,所以可以找到p1.__proto__.eating