乐趣区

关于javascript:JavaScript构造函数面向对象编程

前言

大家应该都据说过面向对象编程吧,在 java 和 c 语言中,是有”类 (class)”的概念的,所谓“类”就是对象的模板,对象就是“类”的实例。而在 JavaScript 语言,他的对象体系是基于构造函数(constructor)和原型链(prototype)的。

你可能会问,不对啊,es6 不是有个 class 么?实际上 es6 的 class 只是模拟 java 起了一个面向对象的习惯性的名字,让对象原型的写法更加清晰、更像面向对象编程的语法而已,而且 es6 的 class 本身指向的就是构造函数。所以能够认为 ES6 中的类其实就是构造函数的语法糖或者是构造函数的另外一种写法而已!

通常生成一个对象的传统形式就是通过构造函数,这也是 JS 面向对象惟一终点。

一、构造函数根底学习

1. 构造函数的定义和应用办法

JavaScript 构造函数是用于创建对象的函数。它们与一般函数的区别在于,构造函数被用于创立一个新的对象,并且该对象的属性和办法能够在函数外部定义,为了辨别一般函数,构造函数个别首字母大写。

构造函数的应用办法非常简单。您只须要应用 new 关键字来调用构造函数,并将其赋值给一个变量即可。例如:

function Car(name, age) {
  this.name = name;
  this.age = age;
}

const car1 = new Car('小明', 20);

在这个例子中,咱们创立了一个名为 Car 的构造函数,并应用 new 关键字创立了一个名为 car1 的 Car 对象。该对象具备两个属性:name 和 age。这些属性是在构造函数外部定义的。

2. 构造函数的参数和返回值

构造函数能够承受任意数量的参数,并且能够返回一个新的对象。在构造函数外部,应用 this 关键字来定义对象的属性和办法。

上面是一个应用构造函数返回对象的例子:

function Rectangle(width, height) {
  this.width = width;
  this.height = height;
  this.area = function() {return this.width * this.height;}
}

const rect1 = new Rectangle(5, 10);
console.log(rect1.area()); // 50

在这个例子中,咱们创立了一个名为 Rectangle 的构造函数,并应用它创立了一个名为 rect1 的对象。该对象具备三个属性:width、height 和 area。其中,area 是一个函数,用于计算矩形的面积。

3. 原型和原型链的概念

在 JavaScript 中,每个对象都有一个原型。原型是一个对象,蕴含了该对象的属性和办法。当您尝试拜访一个对象的属性或办法时,JavaScript 会首先查找该对象自身是否具备该属性或办法。如果对象自身没有该属性或办法,JavaScript 会查找该对象的原型,并在原型中查找该属性或办法。

原型链是一系列由对象原型组成的链。当您尝试拜访一个对象的属性或办法时,JavaScript 会沿着该对象的原型链向上查找,直到找到该属性或办法为止。

<aside> 💡 js 构造函数的执行过程是怎么的?

</aside>

  • 创立一个空对象(this)。
  • 将 this 绑定到新创建的对象上。
  • 执行构造函数外部的代码,给 this 增加属性和办法。
  • 默认返回 this(除非显式返回其余值 )

实际上,用 new 调用函数后,JS 引擎就会在内存中创立一个空对象 {},创立进去的新对象的__proto__属性指向构造函数的原型对象(艰深了解就是新对象隐式原型__proto__链接到构造函数显式原型 prototype 上。)构造函数外部的 this 会指向这个新对象, 而后从上到下执行函数体(只有这步是咱们能直观看到代码的)最初,返回发明进去的对象,也就是咱们失去的实例对象

原型对象,构造函数和实例三者的关系如图所示:

也就是说,你每次 new,都会失去一个全新的对象,有本人的内存空间,所以创立多个实例对象,他们之间互不影响,只会共用一个原型对象上的属性和办法,这里就要留神,尽量把雷同的属性或者办法都放在构造函数外部,这样多个实例应用时能够节俭本身空间

4. 如何继承构造函数

JavaScript 容许您通过继承构造函数来创立新的对象类型。这能够通过应用原型来实现。上面是一个应用原型继承构造函数的例子:

function Animal(name) {this.name = name;}

Animal.prototype.getName = function() {return this.name;}

function Dog(name, breed) {Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.getBreed = function() {return this.breed;}

const dog1 = new Dog('小黑', '拉布拉多');
console.log(dog1.getName()); // '小黑'
console.log(dog1.getBreed()); // '拉布拉多'

在这个例子中,咱们创立了一个名为 Animal 的构造函数,并在其原型中定义了一个名为 getName 的办法。而后,咱们创立了一个名为 Dog 的构造函数,并通过调用 Animal.call 办法来继承 Animal 构造函数。最初,咱们在 Dog 原型中定义了一个名为 getBreed 的办法,并将 Dog.prototype 设置为 Animal.prototype 的一个新实例,从而实现了继承。

通过继承构造函数,您能够创立简单的对象类型,并将其组织成易于治理和保护的代码构造。

5. 构造函数应用场景

  • 当你须要创立雷同类型的多个对象时,构造函数能够防止反复编写代码,提高效率和可读性
  • 当你须要给对象的成员变量赋予初始值时,构造函数能够保障对象在创立时就被正确初始化
  • 当你须要给对象的成员变量赋予常量或援用类型的值时,构造函数必须应用初始化列表来实现,因为常量和援用不能被从新赋值
  • 当你须要给子类对象的父类局部赋予初始值时,构造函数必须调用父类的构造函数来实现

如果您曾经相熟 JavaScript 构造函数的基础知识,那么您能够进一步学习深度 JavaScript 构造函数。以下是一些深刻的话题,您能够在这些话题中深刻理解 JavaScript 构造函数。

二、构造函数进阶学习

1. 应用类定义构造函数

类的定义:

在 ES6 中,引入了类的概念。类是一种定义对象的模板。您能够应用类来定义 JavaScript 构造函数。以下是一个应用类定义构造函数的例子:

class Person {constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const person1 = new Person('小明', 20);

在这个例子中,咱们应用 class 关键字定义了一个名为 Person 的类,并在其构造函数中定义了两个属性:name 和 age。而后,咱们应用 new 关键字创立一个名为 person1 的 Person 对象。

constructor 办法:

constructor 办法就是类的构造方法,this 关键字代表实例对象。其对应的也就是 ES5 的构造函数 Person。

constructor 办法是类的默认办法,通过 new 命令生成对象实例时,会主动调用该办法。一个类必须有 constructor 办法,如果没有显式定义,会默认增加一个空的 constructor 办法。

类的继承:

es6 的 class 类继承能够通过 extends 关键字实现,以下是一个应用类继承的例子:

class Animal {constructor(name) {this.name = name;}

  getName() {return this.name;}
}

class Dog extends Animal {constructor(name, breed) {super(name);
    this.breed = breed;
  }

  getBreed() {return this.breed;}
}

const dog1 = new Dog('小黑', '拉布拉多');
console.log(dog1.getName()); // '小黑'
console.log(dog1.getBreed()); // '拉布拉多'

在这个例子中,咱们定义了一个名为 Animal 的类,并在其构造函数中定义了一个属性和一个办法。而后,咱们应用 extends 关键字创立了一个名为 Dog 的类,并应用 super 关键字调用了 Animal 构造函数。最初,咱们在 Dog 中定义了一个新属性和一个新办法。

应用类继承,您能够轻松地创立简单的对象类型,并将其组织成易于治理和保护的代码构造。

super 关键字:

super 这个关键字,既能够当作函数应用,也能够当作对象应用。用法齐全不同。

super() 办法:

super 作为函数调用时,代表父类的构造函数。子类的构造函数必须执行一次 super() 办法。

因为 ES6 的继承机制与 ES5 构造函数不同,ES6 的子类实例对象 this 必须先通过父类的构造函数创立,失去与父类同样的实例属性和办法后再增加子类本人的实例属性和办法。因而如果不调用 super() 办法,子类就得不到 this 对象。

super 尽管代表了父类的构造函数,但返回的是子类的实例,即通过 super 执行父类构造函数时,this 指的都是子类的实例。也就是 super() 相当于 Person.call(this)。

class A {constructor() {console.log(this.constructor.name)
  }
}

class B extends A {constructor() {super();
  }
}

new A()       // A
new B()       // B

作为函数时,super() 只能在子类的构造函数之中,用在其余中央就会报错。

super 对象:

在一般办法中指向父类的 prototype 原型

super 作为对象时,在一般办法中,指向父类的 prototype 原型,因而不在原型 prototype 上的属性和办法不能够通过 super 调用。

class A {constructor() {this.a = 3;}
  p() {return 2;}
}
A.prototype.m = 6;

class B extends A {constructor() {super();
    console.log(super.a);    // undefined
    console.log(super.p());  // 2
    console.log(super.m);    // 6
  }
}

new B();
let a = new A() console.log(a.__proto__) // {constructor: ƒ, p: ƒ}

问题: 为什么 super.a 是 undefined?

因为下面说了,一般办法外面指向的是父类的 prototype 原型,从打印能够看进去,他只能拿到 constructor 这个办法,而 a 属性并不间接挂到 A 原型对象上面,所以拿不到

在子类一般办法中通过 super 调用父类办法时,办法外部的 this 指向以后的子类实例。

class A {constructor() {this.x = 'a';}
  aX() {console.log(this.x);
  }
}

class B extends A {constructor() {super();
    this.x = 'b';
  }
  bX() {super.aX();
  }
}

(new B()).bX()    // 'b'

在静态方法中,指向父类

class A {static m(msg) {console.log('static', msg);
 }
 m(msg) {console.log('instance', msg);
 }
}

class B extends A {static m(msg) {super.m(msg);
  }
  m(msg) {super.m(msg);
  }
}

B.m(1);          // "static" 1
(new B()).m(2)   // "instance" 2

在子类静态方法中通过 super 调用父类办法时,办法外部的 this 指向以后的子类,而不是子类的实例。

属性拦挡:

与 ES5 一样,在 Class 外部能够应用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦挡该属性的存取行为。

class Person {constructor() {this.name = 'dora';}
  get author() {return this.name;}
  set author(value) {
    this.name = this.name + value;
    console.log(this.name);
  }
}

let p = new Person();
p.author          //  dora
p.author = 666;   // dora666

且其中 author 属性定义在 Person.prototype 上,但 get 和 set 函数是设置在 author 属性形容对象 Descriptor 上的。

Class 的 static 静态方法:

类相当于实例的原型,所有在类中定义的办法,都会被实例继承。但如果在一个办法前,加上 static 关键字,就示意该办法不会被实例继承,而是间接通过类来调用,这就称为“静态方法”。

class Person {static sayHi() {console.log('Hi');
  }
}

Person.sayHi()      // "Hi"

let p = new Person();
p.sayHi()           // TypeError: p.sayHi is not a function

2. 应用闭包定义构造函数

闭包是一种定义函数的形式,能够捕捉函数被创立时的环境。您能够应用闭包来定义 JavaScript 构造函数。以下是一个应用闭包定义构造函数的例子:

function createPerson(name, age) {return function() {
    return {
      name: name,
      age: age
    }
  }
}

const person1 = createPerson('小明', 20)();

在这个例子中,咱们定义了一个名为 createPerson 的函数,并返回一个新函数。返回的函数创立一个对象,该对象蕴含两个属性:name 和 age。咱们应用 createPerson 函数来创立一个名为 person1 的 Person 对象。

3. 应用工厂函数定义构造函数

工厂函数是一种定义函数的形式,能够返回一个新的对象。您能够应用工厂函数来定义 JavaScript 构造函数。以下是一个应用工厂函数定义构造函数的例子:

function createPerson(name, age) {
  return {
    name: name,
    age: age
  }
}

const person1 = createPerson('小明', 20);

在这个例子中,咱们定义了一个名为 createPerson 的函数,并返回一个新的对象。返回的对象蕴含两个属性:name 和 age。咱们应用 createPerson 函数来创立一个名为 person1 的 Person 对象。

总结

以上是一些深刻的 JavaScript 构造函数话题。把握这些话题能够帮忙您更好地了解 JavaScript 构造函数的工作原理,并且可能在本人的代码中利用它们。

退出移动版