142 lines
5.1 KiB
Markdown
142 lines
5.1 KiB
Markdown
## 概念解析
|
||
|
||
1: 构造函数 constructor
|
||
|
||
用new调用一个函数,这个函数就被称为构造函数,任何函数都可以是构造函数,只需要用new调用它
|
||
构造函数用来构造初始化新对象,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化赋值
|
||
|
||
例如:
|
||
|
||
```js
|
||
// 此时方法 Test 就是构造函数
|
||
function Test(name = "测试") {
|
||
|
||
// 添加属性
|
||
this.name = name
|
||
|
||
// 添加方法
|
||
this.getName = function () {
|
||
console.log(`我是 ${this.name}`)
|
||
}
|
||
}
|
||
|
||
// c 为实例对象
|
||
var c = new Test("传入参数")
|
||
```
|
||
|
||
** 为什么需要构造函数?**
|
||
|
||
因为我们一次创建一个对象,里面很多的属性和方法是大量相同的,代码就显得有些冗余,所以我们可以利用函数的方式,重复这些相同的代码,我们把这个函数称为“构造函数”,又因为这个函数不一样,里面封装的不是普通代码,而是“对象”,构造函数就是把我们对象里面一些相同的属性和方法抽象出来封装到函数里面
|
||
|
||
构造函数与普通函数的区别:
|
||
|
||
1. 普通函数不需要用new关键字,调用而构造函数需要
|
||
2. 普通函数可以用return,构造函数会返回一个新对象所以构造函数不需要return
|
||
3. this的指向问题,普通函数this指向的是window,构造函数this指向的是 **构造函数new出来的实例对象(var c)**
|
||
4. 函数命名建议首字母大写,与普通函数区分开
|
||
5. 构造函数的属性和方法前面必须加this
|
||
|
||
2 实例(对象)
|
||
|
||
用new调用构造函数创建出来的对象叫做实例, 或是实例对象。
|
||
上面例子中的变量 **c** 就是实例
|
||
|
||
3 原型(prototype)显式原型
|
||
它是函数独有的,每个 函数function 都有一个 prototype,这个就是 **显式原型**
|
||
prototype 是一个指针 指向一个对象, 这个对象包含了所有实例共享的属性和方法。
|
||
|
||
原型 简单理解就是:**构造函数.prototype**,又称 **原型对象**
|
||
|
||
在我们的例子中为:
|
||
|
||
```js
|
||
// 此时方法 Test 就是构造函数
|
||
function Test(name = "测试") {
|
||
this.name = name
|
||
}
|
||
|
||
// 构造函数.prototype
|
||
// 注意 Test.prototype 返回的数值 就是 原型对象
|
||
console.log(Test.prototype)
|
||
```
|
||
|
||
4 __proto__ 隐式原型
|
||
|
||
每个 实例对象(本例中为c) 都有一个__proto__,是专属于对象数据的一个属性,其类型为对象
|
||
它指向了 **该实例对象** 对应 的 **原型对象**,继承自 Object.prototype.__proto__
|
||
|
||
在我们的例子中为:
|
||
|
||
```js
|
||
// 此时方法 Test 就是构造函数
|
||
function Test(name = "测试") {
|
||
this.name = name
|
||
}
|
||
|
||
// 实例对象为 c
|
||
var c = new Test("你好")
|
||
|
||
// 构造函数.prototype
|
||
// 注意 Test.prototype 返回的数值 就是 原型对象
|
||
// 对象的隐式原型的值为其对应构造函数的显示原型的值
|
||
console.log(c.__proto__ == Test.prototype)
|
||
```
|
||
|
||
注意:
|
||
|
||
prototype与__proto__都指向 原型对象。
|
||
|
||
任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象
|
||
任意一个构造函数实例化的对象,都有一个__proto__对象,它指向构造函数的原型对象.
|
||
|
||
这就是基于原型的类的实现方式,也就是原型链的实现方式。实例化后当调用对象的属性或方法时,会有如下过程:
|
||
|
||
查找对象是否有该属性或方法,如果则在则返回或调用
|
||
如果不存在,则通过__proto__属性,在原型链上查找有没有属性或方法
|
||
|
||
5 原型链
|
||
|
||
简单的回顾构造函数、原型和实例的关系:
|
||
|
||
**每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。**
|
||
|
||
那么假如我们让 **原型对象等于另一个类型的实例**,结果会怎样?
|
||
|
||
显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。
|
||
|
||
假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了 实例与原型的链条。这就是所谓的原型链的基本概念。
|
||
|
||
代码演示:
|
||
```js
|
||
function Test(name) {
|
||
this.name = name
|
||
}
|
||
|
||
function Test2(name) {
|
||
this.twoName = name
|
||
}
|
||
|
||
// Test 的 prototype 对象 = Test2的实例
|
||
// Test 是子类,Test2是父类
|
||
Test.prototype = new Test2("测试2")
|
||
let cccc = new Test("测试1")
|
||
console.log(cccc);
|
||
```
|
||
|
||
其实上面代码稍微改动一下就可以实现原型链实现继承:
|
||
|
||
```js
|
||
function Test(name) {
|
||
this.name = name
|
||
}
|
||
|
||
function Test2(name) {
|
||
this.twoName = name
|
||
}
|
||
|
||
Test.prototype = new Test2("测试2")
|
||
// 让子类型的原型的 constructor 指向子类型(为了修正 constructor 属性)
|
||
// Test.prototype.constructor = Test
|
||
var test = new Test("测试1")
|
||
console.log("test: ", test);
|
||
``` |