前言
本文主要讲的就是函数,方法,构造函数,new
操作符,实例对象,原型,原型链,ES6
类。因为这几个知识点都是有互通的关系的,所以一起讲,方便大家疏通整个关于这方面的知识体系。希望对大家有帮助,看完能有一种醍醐灌顶的感觉。当然,文中如有错误的,也请评论指出。
你的收获:
- 函数和方法的区分
- 函数和构造函数的区分
new
操作符到底做了哪些事情- 如何自己实现一个
new
- 什么是实例对象
new
的缺点以及为什么需要继承Javascript
是如何实现继承的- 什么是原型
prototype
以及__proto__
和constructor
- 什么是原型链
ES6 class
只是一种语法糖,以及它的实现方式
构造函数
在讲构造函数之前,先来讲下函数和方法
-
函数
- 函数是可以执行的
javascript
代码块,由javascript
程序定义或javascript
实现预定义
function fn(){ //to do something }
- 函数是可以执行的
-
方法
- 通过对象调用的
javascript
函数
obj = { fn(){ //to do something } }
- 通过对象调用的
总结而言,独立执行的 JS代码块
就是所谓的函数,而在对象中,需要通过对象调用的函数,就是所谓的方法。
再来讲一下这部分的主题,那就是构造函数, show you code
。
- 构造函数
function Fn(){
//to do something
}
构造函数与函数的异同
-
命名方式不同
构造函数使用大驼峰方式,而普通函数使用小驼峰方式,虽然没有强制要求,但是一般书写方式都这样子,便于区分
-
是否通过
new
操作符来调用的通过
new
操作符来调用就是构造函数,反之,不通过new
操作符就是普通函数 -
this
指向不同普通函数中
this
指向为window
,而构造函数中this
指向的是实例,当然,这也与new
操作符所做的事情有关,在下一个板块中我们来说一说new
操作符
new
操作符
从上面我们也大概知道了,就是构造函数需要使用 new
操作符进行调用,也就是相当于, new
操作符就是区分函数和构造函数的钥匙。
但是,不知道大家有没有想过一个问题,那就是 Fn
明明是一个构造函数,为什么经过 new
以后,就能返回一个实例对象
那么,我们就来说一说 new
操作符,到底做了哪些事情
那么,接下来我们可以自己实现一个 new
方法
// const xxx = _new(Person,'cooldream',24) ==> new Person('cooldream',24)
function _new(fn,...args){
// 新建一个对象 用于函数变对象
const newObj = {};
// 将空对象的原型地址 `_proto_` 指向构造函数的原型对象
newObj.__proto__ = fn.prototype;
// this 指向新对象
fn.apply(newObj, args);
// 返回这个新对象
return newObj;
}
实例对象
前面介绍完了 new
操作符以及构造函数,接下来就是他们的生产物,实例对象
比方说 let person = new Person();
,那么, person
就是所谓的实例对象,实例对象就是通过构造函数配合 new
生成的,而这个过程,我们也称之为实例化
new
操作符的缺点
通过上面对于 new
实例化过程的学习,我们大概也知道,每一个实例对象的内存都是独立的,也就是所谓的深拷贝,关于深浅拷贝,不懂的可以移步到我的这一篇博客 一文搞懂JS系列(二)之JS内存生命周期,栈内存与堆内存,深浅拷贝
因为每一次 new
操作,都会开辟新的内存,所以每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
毕竟大家都知道,一般设计模式讲究区分变与不变,具体的大意就是将变与不变分离,达到使变化的部分灵活、不变的地方稳定的目的。将所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
上面这段话可能有点绕,我们还是来讲个例子吧。就像数组都有一个自带的属性,叫 length
,用来描述数组的长度,毕竟,只要是一个数组,它就会有长度,大不了就是空数组,长度为0。而这个 length
,就是上面实例对象需要共享的属性。就是所谓不变的地方,大家都互通。而这个共享的属性和方法,就叫做原型。例如,可以看下图的 Array
的 prototype
。而这个,就是 Javascript
的继承机制。下面我们就来看看为什么,Javascript
要采用这种继承机制,而不是 Java
的"类"。
Javascript
独特的继承
先让我们来了解一下 JS
独特的继承方式。以下的内容参考于阮一峰的 Javascript继承机制的设计思想,当然,你也不需要去看这篇文章,我会在下面来描述这方面的知识。
我们都知道,JS
的设计初衷知识用于网页脚本语言,他觉得,没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。
正是因为设计初衷就比较简易,其实不需要有"继承"机制。但是,Javascript
里面都是对象,必须有一种机制,将所有对象联系起来。所以,JS作者
最后还是设计了"继承"。
至于为什么打上引号,我想学过 Java
的都应该知道类,但是, Javascript
并没有引入"类", 因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
他考虑到,C++和Java语言都使用new命令,生成实例,他也将 new
引入了 JS
的设计之中。但是,Javascript没有"类",怎么来表示原型对象呢?
这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。
总结而言,Java
中通过 new
类,生成实例对象,那么, Javascript
是通过 new
构造函数(constructor
)来生成实例对象。这些概念在上面都已经有所提及。
原型
所有 JavaScript
对象都从原型继承属性和方法
先来一段贯穿整个原型板块的代码
function Person(){
}
let person = new Person();
根据上面的学习,可以看出来,构造函数 Person
和 实例对象 person
-
prototype
每个函数都有一个
prototype
属性。每一个
JavaScript
对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。所以,上面代码用
prototype
所指向的原型,就是Person.prototype
-
__proto__
每一个
JavaScript
对象(除了 null )都具有的一个属性,叫__proto__
,这个属性会指向该对象的原型所以,上面代码用
__proto__
所指向的原型,就是person.__proto__
既然上下都指向原型,可以得出
person.__proto__ === Person.prototype
-
constructor
每个原型都有一个
constructor
属性指向关联的构造函数 原型指向构造函数即
Person === Person.prototype.constructor
了解了三个基础概念之后,下面我们来看一个例子
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
我们来分析下代码的运行过程
原型链
原型链的概念呢,其实有点类似于作用域链,也类似于 Promise
,一层套一层,仿佛又回想起了那天被 Promise
回调地狱支配的恐惧,哈哈哈哈。
当然,插个广告,都2021年了,连作用域链都不知道的话,那就快点击我的这篇博客吧 一文搞懂JS系列(一)之编译原理,作用域,作用域链,变量提升,暂时性死区
当然,扯回原型链,其实概念也很简单,就是原型组成的链
经过上面的学习,我们都知道对象的 __proto__
就是所谓的原型,而原型又是一个对象,它又有自己的 __proto__
,原型的 __proto__
又是原型的原型,就这样可以一直通过 __proto__
向上找,这就是原型链,当向上找找到 Object
的原型的时候,这条原型链就算到头了。如下图,找到了 Object.__proto__
就算到头了。
如下图,打印 person.__proto__.__proto__
,原型链查找就算到头了,也就是再无 __proto__
,一个简单的 person
实例对象,也有两层原型
ES6中的类
通过上面的学习,我们学会了原型,原型链,以及了解到了 Javascript
实现继承方式的根基,那就是原型。
可能很多人会说,都什么年代了,明明 ES6
也有类啊,但是,这些人都被表象所迷惑了,来看一段 MDN
的官方解释。
那么,既然是语法糖,肯定有它的相应实现方式,我们先用 class
的方式来实现一个 Person
类,相当于一个构造函数
class Person {
constructor(name ,age) {
this.name = name
this.age = age
}
sayHello() {
console.log('你好啊!')
}
}
其实,上面的方式等价于下面:
function Person(name, age) {
this.name = name
this.age = age
}
Dog.prototype.sayHello = function() {
console.log('你好啊!')
}
所以,虽然在 ES6
中有更新类,但是,它只是一种语法糖,真正实现继承的方式还是原型
本文参考如下:
js的原型和原型链
Javascript继承机制的设计思想
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!