目录
构造函数
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: "蔡徐坤" };
//这样写就是错的 return ["name", "蔡徐坤" ];
}
var p = new Person("范丞丞");
console.log(p.name);//蔡徐坤
5.与普通函数的区别
5.1调用方式的不同
普通函数使用函数名
调用
构造函数通过new
关键字来调用
5.2 返回值不同
普通函数的返回值是函数内return的结果
构造函数的返回值是函数内创建对象的地址。
5.3 作用的不同
构造函数时专门用来创建对象。
普通函数的作用可以自由定义。
原型对象
传统写法:
<script>
function Dog(name,age){
this.name = name;
this.age = age;
this.bark = function () {
alert("汪汪汪!")
}
}
var dog1 = new Dog("来福",3)
var dog2 = new Dog("常威",2)
dog1.bark();
dog2.bark();
</script>
因为上述方法造成了资源的浪费我们学习一个新的概念prototype属性
我们创建的每一个函数都有一个prototype属性,这个属性指向一个对象。而这个对象所有的属性和方法都会被构造函数拥有。
<script>
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)//哈士奇
</script>
对象的封装
一般情况下,公共属性定义到构造函数里面,公共的方法定义到原型对象身上。
混合模式(构造函数+原型 模式)
<script>
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("汪汪汪!")
}
var d3 = new Dog("昊",4,"泰迪","浅棕色");
d3.bark();//汪汪汪
console.log(d3.name);//昊
</script>
对象属性 proto
每一个对象都有一个属性__proto__
,这个属性指向构造函数的prototype
,也就是构造函数的原型对象。我们之所以可以在对象中使用原型对象中的方法和属性就是因为对象中有__proto__
属性的存在。
原型链(这副图从构造函数位置开始看)
<script>
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("汪汪汪!")
}
var lf = new Dog("来福",3,"秋田","明黄色");//传参(因为这一步,Dog()才是一个构造函数)
console.dir(lf.__proto__ === Dog.prototype);//true 构造函数的原型对象(Dog.prototype) 的指向和和实例对象的__proto__方法指向的都是一个,所以为true
console.log(lf.__proto__);// {
show: ƒ, bark: ƒ} 这是两个原型对象
//-------------------------------------------------------
console.log(lf.__proto__.__proto__);// Object原型对象
console.log(lf.__proto__.__proto__.__proto__);//null
</script>
面向对象的三大特性:封装(封装构造函数),继承,多态
面向对象 是一种编程思想。
区别: 比如吃饭:
面向过程:
红烧肉
1.买菜
2.洗菜
3.切菜
4.炒菜
5.出锅
6.开吃
面向对象:
1.找一个对象
2.让她给你做饭吃
继承:从父类那里继承父类的属性和方法。
多态:子类自己重新定义从父类继承过来的方法。或者新增一个父类没有的方法。
继承
ES6之前没有给我们提供 extends 继承。我们可以通过构造函数+原型对象的模式去模拟实现继承。这种方法也被称为组合继承。
<script>
//目地: 哈士奇继承狗的属性和方法
function Dog(age) {
this.age = age;
}
Dog.prototype.bark = function () {
//Dog的原型对象
alert("汪汪汪!")
}
function Huskie(name, age) {
this.name = name;
this.age = age;
}
Huskie.prototype.bark = function () {
//Huskie的原型对象
alert("汪汪汪!")
}
var hsq = new Huskie("二哈",2);//因为 new Huskie变成了一个构造函数
console.log(hsq.age); //实例对象.属性 输出 2
hsq.bark();//调用了Huskie的原型对象方法
</script>
使用call()方法实现继承
<script>
//目的:哈士奇用Dog的方法(这是个错误示范,因为this指向window)
function Dog(age){
console.log(this)//Window
// this.age = age;
}
Dog.prototype.bark = function(){
alert("汪汪汪!")
}
Dog.prototype.color = "黑白";
function Huskie(name,age){
Dog();//这里调用了Dog的方法(我们需要考虑到调用后this的指向,
看到上面this指向window,不是Dog这不是我们想要看到的,就无法正确调用Dog的方法)
this.name = name;
}
Huskie.prototype.bark = function(){
alert("汪汪汪!")
}
var hsq = new Huskie(2);
</script>
<script>
function Dog(age) {
console.log(this);//Huskie
this.age = age;
}
Dog.prototype.bark = function () {
alert("汪汪汪!")
}
Dog.prototype.color = "黑白";
function Huskie(name, age) {
Dog.call(this, age);
this.name = name;
}
Huskie.prototype.bark = function () {
alert("汪汪汪!")
}
var hsq = new Huskie( "二哈", 2);
console.log(hsq.age);//2
</script>
特点:
- 该方式是靠调用需要继承的构造函数来实现的,调用过程中使用call方法来改变this的指向。
- call是不可以继承父对象原型中的属性和方法。
- call是只能继承构造函数中的属性和方法。
优点:继承了父类构造函数中所有的属性和方法
缺点:不能继承父类原型对象中的属性和方法
看下面这张图你会好理解
使用prototype实现继承
在原型对象中有一个constructor属性,该属性指向该原型对象的构造函数。
<script>
//目的 哈士奇用dog的方法
function Dog(age){
this.age = age;
}
Dog.prototype.bark = function(){
alert("汪汪汪!")
}
function Huskie(name,age){
//Huskie的构造函数
this.name = name;
}
// 这样写相当于让子类与父类指向了同一个原型对象。如果修改了子类的原型对象,则父类的原型对象也会随之修改
// Huskie.prototype = Dog.prototype;//(把Dog的原型对象方法给 哈士奇原型对象方法不就可以了吗,但是是不对的) 和下一比较
Huskie.prototype = new Dog(3);// 哈士奇的原型对象 = dog实例对象
Huskie.prototype.constructor = Huskie;//(哈士奇的构造函数)的原型对象的构造函数 = 哈士奇构造函数
var h = new Huskie();
console.log(h.age);//3
// 优点:继承了父级原型上的属性和方法**
// 缺点:实现化多个子类时,必须使用共同的属性值
// console.dir(h.constructor === h.__proto__.constructor);// 都指向了同一个构造函数 所以为 true
</script>
优点:继承了父级原型上的属性和方法
缺点:实现化多个子类时,必须使用共同的属性值。
组合式继承
<script>
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();//哈士奇构造函数 . 原型对象 = Dog构造函数
Huskie.prototype.constructor = Huskie;//哈士奇构造函数 . 原型对象 . 构造函数 = 哈士奇构造函数
var h = new Huskie("二哈",5);
console.log(h.age);
h.bark();
</script>
<!-- call()方法优点:继承了父类构造函数中所有的属性和方法
prototype继承优点:继承了父级原型上的属性和方法 -->
多态
多态:子类自己重新定义从父类继承过来的方法。或者新增一个父类没有的方法。
<script>
//哈士奇继承了Dog的属性和方法后还有自己的属性和方法
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;
// Huskie.prototype.bark = function() {
// alert("嗷呜!")//这是哈士奇的方法
// }
var h = new Huskie("二哈", 5);
h.bark();
//当哈士奇没有自己方法时,就会调用Dog的方法汪汪汪
</script>
ES6面向对象
class 类
// function Dog(breed,color,age){
// this.breed = breed;
// this.color = color;
// this.age = age;
// }
// Dog.prototype.bark = function(){
// alert("汪汪汪")
// }
// var dog = new Dog("泰迪","棕色",5);
// ------------- ES6写法 -------------------------
class Dog {
constructor(breed, color, age) {
this.breed = breed;
this.color = color;
this.age = age;
}
bark(){
alert("汪汪汪")
}
}
var dog = new Dog("泰迪","棕色",5);
console.log(dog.age, dog.breed, dog.color);// 实例对象.属性 5 "泰迪" "棕色"
dog.bark();//调用Dog方法 输出汪汪汪
console.log(typeof Dog); //function
console.log(Dog === Dog.prototype.constructor); //true 都指向Dog构造函数
通过以上的代码,我们发现,class的本质是函数,类本身指向的就是构造函数。
ES6的class
可以看作是构造函数的语法糖。它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型写法更加清晰,更加符合面向对象编程的语法而已。
类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
//什么是语法糖
var arr = new Array();
var arr2 = [];//就是上面new Array的语法糖。
ES6继承
class Dog {
constructor(breed, color, age) {
this.breed = breed;
this.color = color;
this.age = age;
}
bark(){
alert("汪汪汪")
}
}
class Haskie extends Dog{
constructor(breed, color, age, name){
//super在这里就相当于调用了父类的构造函数。
//super不可以省略,即使是个空括号也要写
super(breed, color, age);
this.name = name;
}
}
const h2 = new Haskie("哈士奇","黑白",5,"二哈");
console.log(h2.name,h2.breed,h2.color,h2.age);
h2.bark();
extends 这是继承 例子下面b继承a
上面用的super是什么呢,下面来解释
super
关键字,既可以当函数来使用,也可以当对象来使用。是一个方法
第一种情况:super作为函数调用时,表示父类的构造函数。
作为函数时,super()只能用在子类的构造函数中
class A {
}
class B extends A {
//B使用A的方法
constructor() {
super(); //不会报错
}
fn() {
}
}
class A {
}
class B extends A {
//B使用A的方法
constructor() {
//构造函数B的属性
}
fn() {
super(); //报错(作为函数时,super()只能用在子类的构造函数中)
}
}
第二种情况
super作为对象时,在普通方法中,指向父类的原型对象。
class A {
p() {
return 1;
}
}
class B extends A {
constructor() {
super();
console.log(super.p());//1
}
}
let b = new B();
console.log(b.p());//1