JavaScript构造函数
构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数; 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法; 但是JavaScript中的构造函数有点不太一样;
在JavaScript中,构造函数就是一个普普通通的函数,从表现形式来看,和其他函数没有区别。当被new关键字所调用时,就变成了构造函数。
new操作调用函数时的流程
- 在内存中开辟一块新的空间(空对象)。
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。
- 构造函数内部的this会指向新创建出来的对象。
- 指向内部函数代码
- 如果没有返回非空对象,则会把创建出来的对象给返回。
对象与函数的原型
对象的原型——[[prototype]]
JavaScript每一个对象都有一个特殊的内置属性[[prototype]](proto),这个特殊的对象指向另外一个对象。 :::warning__proto__
是早期浏览器自己添加的,可能存在浏览器等兼容性问题,也是历史遗留下的问题。在现代的js中,推荐使用[Object.getPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf)
或者[Reflect.getPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf)
:::函数的属性——prototype
JavaScript中所有函数都有一个prototype
属性。
该prototype
属性可以被重写。[[prototype]]和prototype两者的区别
:::info[[prototype]]
是对象的原型prototype
是函数的属性 :::
new一个对象时的内存表现
修改prototype(重写prototype)
function Foo() {}
Foo.prototype = {
msg: "hello",
x: 100,
};
const foo = new Foo();
console.log(foo);
console.log(foo.__proto__);
console.log(foo.msg);
console.log(foo.x);
内存表现
给prototype添加属性
function Foo() {}
Foo.prototype.msg = "hello";
Foo.prototype.x = 100;
const foo = new Foo();
console.log(foo);
console.log(foo.__proto__);
console.log(foo.msg);
console.log(foo.x);
函数与prototype结合使用
直接在构造方法上添加方法的方式。
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log("eat");
};
}
:::warning
通过这种方式创建一个对象有一个弊端,就是每次创建一个新的Person
对象的时候,对象上的eat()
函数会被重复创建。非常影响性能。
:::
在函数原型上添加方法,这样通过该方式,所有创建出来的Person对象,可以共享一个eat函数。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.eat = function() {
console.log("eat");
}
prototype.constructor
每一个函数都有一个prototype,而prototype上都会有一个constructor属性,这个constructor指向该函数。
Object是所有类的父类
通过var foo = new Foo()
,会将Foo
的原型对象Foo.prototype
赋值给foo
的__proto__
,而Foo.prototype
中的__proto__
默认指向Object.prototype
,也就是[Object: null prototype]
。
因为无论是通过显式原型prototype
还是隐式原型[[prototype]]
,它的值只能是对象
或者null
。当其值为对象时,该对象肯定又会有一个隐式原型,如此无论怎样都会最终指到Object
。
ES5中对象的继承方案
寄生组合式继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.eat = function () {
console.log(this.name + " is eating...");
};
function Student(name, age, stuNumber) {
// 借用构造函数
Person.call(this, name, age);
this.stuNumber = stuNumber;
}
// 继承prototype
Student.prototype = Object.create(Person.prototype);
// 重定义constructor
Object.defineProperty(Student.prototype, "constructor", {
value: Student,
writable: true,
enumerable: false,
configurable: true,
});
Student.prototype.study = function () {
console.log(this.name + " is studying...");
};
Object、Function及自定义函数之间的关系
在JavaScript中,任何一个函数本质上都是通过new Function()
出来的,所以它们的[[prototype]]
都指向Function.prototype
,每个函数中的prototype
中的[[prototype]]
默认指向Object.prototype
,也就是说默认继承自Object。
ES6中的class类
通过前面的方式创建一个类,因为形式上与普通的function过于类似,而且代码不容易理解。在ES6中,添加了一种新的方式定义一个类,但本质上依然是构造函数,只是一种语法糖。 :::tips 如果想查看转化成ES5的代码,可以通过babel工具。 :::
类和构造函数的区别
本质上依然是构造函数,只是一种语法糖。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person = new Person("abc", 18);
console.log(person);
console.log(person.__proto__);
console.log(person.__proto__ === Person.prototype);
console.log(Person.prototype);
console.log(Person.prototype.__proto__);
console.log(typeof Person);
类的构造函数
每个类都有自己的构造方法,名称为constructor
,且每个类只能有一个,如果有多个就会报错。
该构造方法与构造函数的运行原理一致。
类的实例方法
eat
、run
即为实例方法,与function.prototype.eat
方式基本一致。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
console.log(`${this.name} is eating~`);
}
run() {
console.log(`${this.name} is running~`);
}
}
类的访问器方法——getter、setter
通过get
和set
修饰符即可添加getter
和setter
函数
class Person {
constructor(name, age) {
this._name = name;
this.age = age;
}
set name(name) {
this._name = name;
}
get name() {
return this._name;
}
}
类的静态方法——static
通过static
修饰,可以直接通过类.静态方法
调用。
class Person {
constructor(name) {
this.name = name;
}
static count() {
return 10;
}
}
console.log(Person.count());
类的继承——extends
通过extends
关键字可以帮助快速实现继承。
class Person {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating~`);
}
}
class Student extends Person {
constructor(name, score) {
super(name); // super()调用父类的构造方法
this.score = score;
}
study() {
console.log(`${this.name} is ${this.score}`);
}
}
:::warning
在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
super的使用位置有三个:
- 子类的构造函数
- 实例方法
- 静态方法
:::