构造函数
构造函数
1.什么是构造函数
定义:在js中,使用new
关键字来调用的函数,被称为构造函数。
构造函数的作用:创建对象。
2.为什么要使用构造函数
假如需要创建多个类似的对象,我们会书写很多重复的无意义代码。此时我们使用构造函数,方便快捷的创建一个对象。
如何封装一个构造函数
将一个对象的特征作为属性,将它的行为作为方法。
function Dog(name,age,breed,color){
this.name = name;
this.age = age;
this.breed = breed;
this.color = color;
this.show = function () {
alert("我只是一只小" + this.breed + "啊!")
}
this.bark = function () {
alert("汪汪汪!")
}
}
【注意】构造函数的名字首字母大写。
3.构造函数的执行过程
function Animal(color){
this.color = color;
}
当一个函数创建完成后,我们并不知道它是不是一个构造函数。像上面案例中,即使函数名首字母大写,我们也不确定它是构造函数。只有当一个函数前用new
关键字来调用时,我们才能说它是一个构造函数。
var dog = new Animal("black")
构造函数的执行过程有以下4个步骤。
- 当用
new
关键字调用构造函数时,会开辟一个新的内存空间,这个内容空间就是新的对象实例。 - 将构造函数内部的
this
指向当前的内存空间(新的对象)。 - 执行函数内的代码。(对象的属性和方法的赋值。)
- 将内存空间的地址作为返回值返回。
4.构造函数的返回值
构造函数不需要手动添加返回值,默认返回新对象的内存地址。
(1)手动添加一个基础数据类型的返回值,最终还是返回新对象的内存地址。
function Person(name){
this.name = name;
return "蔡徐坤";
}
var p = new Person("范丞丞");
console.log(p.name);//范丞丞
(2)手动添加一个复合数据类型的返回值,返回手动添加的对象的内存地址。
function Person(name){
this.name = name;
return {name:"蔡徐坤"};
}
var p = new Person("范丞丞");
console.log(p.name);//蔡徐坤
5.与普通函数的区别
5.1调用方式的不同
普通函数使用函数名
调用
构造函数通过new
关键字来调用
5.2 返回值不同
普通函数的返回值是函数内return的结果
构造函数的返回值是函数内创建对象的地址。
5.3 作用的不同
构造函数时专门用来创建对象。
普通函数的作用可以自由定义。
原型对象
我们创建的每一个函数都有一个prototype属性,这个属性指向一个对象。而这个对象所有的属性和方法都会被构造函数拥有。
function Dog(name,age){
this.name = name;
this.age = age;
}
var dog1 = new Dog("来福",3)
var dog2 = new Dog("常威",2)
Dog.prototype.bark = function () {
alert("汪汪汪!")
}
Dog.prototype.breed = "哈士奇";
alert(dog1.bark===dog2.bark);//true
alert(dog1.breed)//哈士奇
alert(dog2.breed)//哈士奇
对象的封装
一般情况下,公共属性定义到构造函数里面,公共的方法定义到原型对象身上。
混合模式(构造函数+原型 模式)
function Dog(name,age,breed,color){
this.name = name;
this.age = age;
this.breed = breed;
this.color = color;
}
Dog.prototype.show = function () {
alert("我只是一只小" + this.breed + "啊!")
}
Dog.prototype.bark = function () {
alert("汪汪汪!")
}
__proto__
每一个对象都有一个属性__proto__
,这个属性指向构造函数的prototype
,也就是构造函数的原型对象。我们之所以可以在对象中使用原型对象中的方法和属性就是因为对象中有__proto__
属性的存在。
原型链
如果实例对象中调用了该对象没有的方法或属性,去__proto__
去找方法或属性,如果还没有则继续往上寻找,直到null。
继承
面上对象的三大特性:封装(封装构造函数),继承,多态
ES6之前没有给我们提供 extends 继承。我们可以通过构造函数+原型对象的模式去模拟实现继承。这种方法也被称为组合继承。
function Dog(age){
this.age = age;
}
Dog.prototype.bark = function(){
alert("汪汪汪!")
}
function Huskie(name,age){
this.name = name;
this.age = age;
}
Huskie.prototype.bark = function(){
alert("汪汪汪!")
}
像上面的案例中,哈士奇是属于狗的一种,如果我们重复定义了一样的属性和方法,写了一些重复的代码也造成了资源的浪费,所以我们让哈士奇继承狗的所有属性和方法。
使用call()方法实现继承
function Dog(age){
this.age = age;
}
Dog.prototype.swimming = function(){
alert("会游泳!")
}
function Huskie(name,age){
Dog.call(this,age);
this.name = name;
}
var hsq = new Huskie("二哈",2);
hsq.swimming();//报错
特点:
- 该方式是靠调用需要继承的构造函数来实现的,调用过程中使用call方法来改变this的指向。
- call是不可以继承父对象原型中的属性和方法。
- call是只能继承构造函数中的属性和方法。
优点:继承了父类构造函数中所有的属性和方法
缺点:不能继承父类原型对象中的属性和方法
使用prototype实现继承
在原型对象中有一个constructor属性,该属性指向该原型对象的构造函数。
function Dog(age){
this.age = age;
}
Dog.prototype.bark = function(){
alert("汪汪汪!")
}
function Huskie(name,age){
this.name = name;
}
// 这样写相当于让子类与父类指向了同一个原型对象。如果修改了子类的原型对象,则父类的原型对象也会随之修改
// Huskie.prototype = Dog.prototype;
Huskie.prototype = new Dog(3);
Huskie.prototype.constructor = Huskie;
// var h = new Huskie("二哈");
// console.log(h.age);
// h.bark();
// var dog = new Dog();
// console.dir(dog)
// console.dir(h.constructor === h.__proto__.constructor); true
优点:继承了父级原型上的属性和方法
缺点:实现化多个子类时,必须使用共同的属性值。
组合式继承
function Dog(age){
this.age = age;
}
Dog.prototype.bark = function(){
alert("汪汪汪!")
}
function Huskie(name,age){
Dog.call(this,age);
this.name = name;
}
//此时不需要给父类添加参数
Huskie.prototype = new Dog();
Huskie.prototype.constructor = Huskie;
var h = new Huskie("二哈",5);
console.log(h.age);
h.bark();