Web/04-JavaScript基础/原型链.md

251 lines
6.7 KiB
Markdown
Raw Normal View History

---
publish: false
---
2018-03-06 22:22:25 +08:00
## 常见概念
2021-12-07 17:26:45 +08:00
- 构造函数
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 构造函数-扩展
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 原型规则和示例
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 原型链
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- instanceof
2018-03-06 22:22:25 +08:00
## 构造函数
2021-12-07 17:26:45 +08:00
任何一个函数都可以被 newnew 了之后,就成了构造方法。
2018-03-06 22:22:25 +08:00
如下:
```javascript
2021-12-07 17:26:45 +08:00
function Foo(name, age) {
this.name = name;
this.age = age;
//retrun this; //默认有这一行。new一个构造函数返回一个对象
}
var fn1 = new Foo('smyhvae', 26);
var fn2 = new Foo('vae', 30); //new 多个实例对象
2018-03-06 22:22:25 +08:00
```
与普通函数相比,构造函数有以下明显特点:
2021-12-07 17:26:45 +08:00
- 用 new 关键字调用。
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 不需要用 return 显式返回值的,默认会返回 this也就是新的实例对象。
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 建议函数名的首字母大写,与普通函数区分开。
2018-03-06 22:22:25 +08:00
参考链接:
2021-12-07 17:26:45 +08:00
- [JavaScript 中的普通函数与构造函数](http://www.cnblogs.com/SheilaSun/p/4398881.html)
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
当 new 之后this 会先变成一个空对象,然后通过`this.name = name`来赋值。
2018-03-06 22:22:25 +08:00
### 构造函数的扩展
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1633.png)
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
上图中发现,数组、对象、函数也有构造函数,它们的构造函数是 Array、Object、function。实际开发中都推荐前面的书写方式。
2018-03-06 22:22:25 +08:00
## 原型规则
原型规则是学习原型链的基础。原型规则有五条,下面来讲解。
2021-12-07 17:26:45 +08:00
### 规则 1
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
所有的引用类型(数组、对象、函数),都具有对象特性,都可以**自由扩展属性**。null 除外。
2018-03-06 22:22:25 +08:00
举例:
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1651.png)
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
### 规则 2
2018-03-06 22:22:25 +08:00
所有的**引用类型**(数组、对象、函数),都有一个`_proto_`属性,属性值是一个**普通的对象**。`_proto_`的含义是隐式原型。
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1656.png)
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
其实,规则 2 是规则 1 的特例只不过js 语法帮我们自动加了 规则 2。
2018-03-06 22:22:25 +08:00
### 规则三
2018-12-09 17:23:30 +08:00
所有的**函数**(不包括数组、对象),都有一个`prototype`属性,属性值是一个**普通的对象**。`prototype`的含义是**显式原型**。(实例没有这个属性)
2018-03-06 22:22:25 +08:00
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1659.png)
2018-03-06 22:22:25 +08:00
### 规则四
2018-12-09 17:23:30 +08:00
所有的**引用类型**(数组、对象、函数),`_proto_`属性指向它的**构造函数**的`prototype`值。
2018-03-06 22:22:25 +08:00
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1701.png)
2018-03-06 22:22:25 +08:00
总结:以上四条,要先理解清楚,然后再来看下面的第五条。
### 规则五
2018-12-09 17:23:30 +08:00
当试图获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的`_proto_`中寻找(即它的构造函数的`prototype`)。
2018-03-06 22:22:25 +08:00
`举例代码1`
```javascript
2021-12-07 17:26:45 +08:00
//创建方法
function Foo(name) {
this.name = name;
}
Foo.prototype.alertName = function () {
// 既然 Foo.prototype 是普通的对象,那也允许给它添加额外的属性 alertName
console.log(this.name);
};
var fn = new Foo('smyhvae');
fn.printName = function () {
console.log(this.name);
};
// 测试
fn.printName(); //输出结果smyhvae
fn.alertName(); //输出结果smyhvae
2018-03-06 22:22:25 +08:00
```
2018-12-09 17:23:30 +08:00
上方代码中,虽然 alertName 不是 fn 自身的属性,但是会从它的构造函数的`prototype`里面找。
2018-03-06 22:22:25 +08:00
**扩展:**遍历循环对象自身的属性
2021-12-07 17:26:45 +08:00
我们知道,`for in`循环可以遍历对象。针对上面的那个 fn 对象,它自身有两个属性:`name`、`printName`,另外从原型中找到了第三个属性`alertName`。现在,如果我们对 fn 进行遍历,能遍历到两个属性还是三个属性呢?
2018-03-06 22:22:25 +08:00
2020-06-06 23:50:36 +08:00
答案:两个。因为,**高级浏览器中已经在 `for in`循环中屏蔽了来自原型的属性。但是,为了保证代码的健壮性,我们最好自己加上判断**,手动将第三个属性屏蔽掉:
2018-03-06 22:22:25 +08:00
```javascript
2021-12-07 17:26:45 +08:00
for (var item in fn) {
if (fn.hasOwnProperty(item)) {
console.log(item);
2018-03-06 22:22:25 +08:00
}
2021-12-07 17:26:45 +08:00
}
2018-03-06 22:22:25 +08:00
```
## 原型链
2021-12-07 17:26:45 +08:00
还是拿上面的`举例代码1`举例,如果此时在最后面加一行代码:
2018-03-06 22:22:25 +08:00
```
2018-12-09 17:23:30 +08:00
fn.toString(); //去 fn._proto_._proto_ 中查找 toString()方法
2018-03-06 22:22:25 +08:00
```
2021-12-07 17:26:45 +08:00
上面的代码中fn 直接调用了 toString()方法,这是因为它通过**原型链**,去`_proto_`的`_proto_`里找到了`Object`,而`Object`是由`toString()`方法的。
2018-03-06 22:22:25 +08:00
### instanceof
格式:
```javascript
2021-12-07 17:26:45 +08:00
对象 instanceof 构造函数;
2018-03-06 22:22:25 +08:00
```
`instanceof`的作用:用于判断**引用类型**属于哪个**构造函数**。
2021-12-07 17:26:45 +08:00
例 1判断一个变量是否为数组 `变量 instanceof Array`
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
例 2
2018-03-06 22:22:25 +08:00
```javascript
2021-12-07 17:26:45 +08:00
function Person() {}
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
//p--->Person.prototype--->Object.prototype--->null
var p = new Person();
//构造函数的**原型**是否在 p 对象的原型链上!
console.log(p instanceof Person);
2018-03-06 22:22:25 +08:00
```
2021-12-07 17:26:45 +08:00
例 3
2018-03-06 22:22:25 +08:00
```javascript
2021-12-07 17:26:45 +08:00
fn instanceof Foo;
2018-03-06 22:22:25 +08:00
```
上面这句话,判断逻辑是:**fn 的`_proto_`一层一层往上找,看能否对应到 Foo.prototype**。
原型链如下:(重要)
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1853.png)
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
注意Object 这个构造方法的显式原型是 null这是一个特例。
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
issues 101 补充:通过原型链查找时,如果你找的是一个属性的话,则返回 undefined如果你找的是一个方法则报错。
2020-08-24 10:38:12 +08:00
2018-03-06 22:22:25 +08:00
## 常见题目
2021-12-07 17:26:45 +08:00
- 如何准确判断一个变量是数组类型
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 写一个原型链继承的例子
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- 描述 new 一个对象的过程
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
- zepto(或其他框架)源码中如何使用原型链
2018-03-06 22:22:25 +08:00
下面分别讲解。
2020-05-23 17:54:54 +08:00
### 题目一:如何准确判断一个变量是数组类型
2018-03-06 22:22:25 +08:00
答案:
```javascript
2021-12-07 17:26:45 +08:00
var arr1 = [];
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
console.log(arr1 instanceof Array); //打印结果true。
console.log(typeof arr1); //打印结果object。提示typeof 方法无法判断是否为数组
2018-03-06 22:22:25 +08:00
```
上方代码表明,只能通过 instanceof 来判断是否为数组。而 typeof 的打印结果是 object。
### 题目二:写一个原型链继承的例子
来看个基础的代码:
2018-04-26 22:36:33 +08:00
![](http://img.smyhvae.com/20180306_1931.png)
2018-03-06 22:22:25 +08:00
上面这个例子是基础,但是,在回答面试官的问题时,不要写上面的例子。要写成下面这个例子:(更贴近实战)
2021-12-07 17:26:45 +08:00
```js
function DomElement(id) {
this.dom = document.getElementById(id);
}
DomElement.prototype.html = function (val) {
var ele = this.dom;
if (val) {
ele.innerHTML = val;
return this;
} else {
return ele.innerHTML;
}
};
DomElement.prototype.on = function (type, fn) {
var ele = this.dom;
ele.addEventListener(type, fn);
return this;
};
var div1 = new DomElement('div1');
div1.html('<p>这是一段文字</p >');
div1.on('click', function () {
console.log('clicked');
});
```
2018-03-06 22:22:25 +08:00
### 题目三:描述 new 一个对象的过程
1创建一个新对象
2this 指向这个新对象
2021-12-07 17:26:45 +08:00
3执行代码对 this 赋值)
2018-03-06 22:22:25 +08:00
2021-12-07 17:26:45 +08:00
4返回 this
2018-03-06 22:22:25 +08:00
参考链接:
2021-12-07 17:26:45 +08:00
- [原型、原型链、继承模式](https://my.oschina.net/u/2600761/blog/1524617)