一文详解-JavaScript中 es5 原型和 es6-class
原型真的有用吗
有不少小伙子应该会有这个感觉 大家都在说原型 prototype
很重要,那为什么我却用不到?
原因不外乎这几个:
框架重度使用者
,我们目前的前端主流业务, 几乎都是使用vue
,react,微信小程序
在开发项目。这些框架封装得太过完美了,几乎不需要我们去做额外封装
,哪怕有需求搞不定,变装一下,网络上寻求帮助,大把猥琐佬等着教你。- 基础知识
还不到家
,都在使用框架
做业务了,没有时间深入研究技术原理,也没有能力去封装造轮子
。 - 跳槽得太少了,没有怎么被面试官虐过,没有深刻体会过 面试造航母,工作拧螺丝 的快感。
于是乎,既然用不到,那就不用学,从自我做起,拒绝内卷。理解满分。。。。
然而残酷的真相是,只有技术是自己可以实实在在的去把控的,命运还是掌握在自己手中。
真相
刚刚想撸起袖子好好干, 一看这个神图。 “算了,上号吧”。
下面小弟尽量以最直白和简洁的图文给你梳理 ese5 和原型之间的关系。
什么时候需要用到原型
封装!!! 当我们想抽象某些公共业务 方便复用或者使结构更加清晰的时候便会用到。 面向对象三大特征:
- 封装
- 继承 (我把继承也归类到封装里面)
多态
比如 我们想创建一个
- 圆角的 div 标签
- 点击一下自己,会变大变小
将圆角 和 点击缩放 看成是公共业务即可。
我们会这么写
如果 我们这个时候想要创建一个图片标签,也是圆角的,也是可以点击放大缩小呢
直接写
那么我们可以看到 我们是相等于把代码复制了一次的
const div = document.querySelector("div");
div.onclick = function () {
this.classList.add("scale");
};
// 同样给图片绑定事件
const img = document.querySelector("img");
img.onclick = function () {
this.classList.add("scale");
};
上述代码没有体现出封装。 而且比较零散,代码和业务掺杂在一起了。(对于一些简单的业务,这么写是没有问题的,怎么简单直接怎么来。)
采用 封装后的写法
// 实例化 div
new CreateCurveElement("div");
// 实例化 图片
new CreateCurveElement("img", { src: "images/1.png" });
// 构造函数
function CreateCurveElement(elementName, option) {
const element = document.createElement(elementName);
element.onclick = function () {
element.classList.add("scale");
};
option &&
Object.keys(option).forEach((key) =>
element.setAttribute(key, option[key])
);
document.body.appendChild(element);
}
可以看到,以后想要创建带有 边框
、弯曲
的元素,就直接 new
即可。 它有以下优势
- 复用了公共代码,如
createElement
,onclick
,classList.add
- 隐藏了实现细节,让调用者只关注 业务本身,如 创建一个元素
new CreateCurveElement
有原型的什么事呢
上面的代码,也是存在弊端的,如 ,代码功能高度耦合
,如果我们想要做任何功能的拓展的话,那么将会对代码结构产生破坏性的影响。因此,我们对于代码考虑的更多:
- 能用
- 性能好
- 方便复用
基于以上需求,我们需要学习原型。
创建对象的方式
字面量
在 js 中,如果想要创建临时使用的对象,直接使用字面量
方式即可。如
const person = {
name: "路飞",
skill: "变大变小",
};
工厂函数
但是如果 我们想要创建多个类似结构的对象时,字面量的方式就不方便维护了。如
const person1 = {
name: "路飞",
skill: "变大变小",
};
const person2 = {
name: "金箍棒",
skill: "变粗边长",
};
const person3 = {
name: "熔岩巨兽",
skill: "变壮变硬",
};
此时想要将 属性 name
修改为 username
,那么就需要挨个修改了。
因此我们可以使用工厂函数的方式:
const person1 = createPerson("路飞", "变大变小");
const person2 = createPerson("金箍棒", "变粗边长");
const person3 = createPerson("熔岩巨兽", "变壮变硬");
// 工厂函数
function createPerson(name, skill) {
return {
name,
skill,
};
}
此时,当我们想要修改 属性 name
时,直接修改 函数 createPerson
即可,干净利索。
构造函数
上述的工厂函数虽然解决了多个对象批量修改属性的问题,但是也是存在弊端的。请看以下的打印。
在 javascript 中,万物皆对象
function createPerson(name, skill) {
return {
name,
skill,
};
}
// 创建一个普通的对象
const person1 = createPerson("路飞", "变大变小");
console.log(person1);
// 打印 字面量
console.log({ name: "春卷", skill: "内卷" });
// 创建一个日期对象
const date = new Date();
console.log(date);
// 创建一个数组对象
const array = new Array();
console.log(array);
可以看到,js 内置的对象 是有明显的标识的。如 Date
或者 Array
,这些标识我们一看就明白。是日期和数组。
但是,我们自己创建的两个对象很明显,只有一个Object
,而不具体其他的明显标识了。原因很简单
Date
,Array,Function
,Regex
,String
,Number,Boolean
等都是js
亲 生的。- 我们自己创建的对象 是野生的,所以不配有名字!
我不管,我也想要。
构造函数即可解决这个问题。
function SuperPerson(name, skill) {
this.name = name;
this.skill = skill;
}
// 创建一个普通的对象
const person1 = new SuperPerson("路飞", "变大变小");
console.log(person1);
构造函数解析
- 构造函数也是一个函数
- 构造函数就是要被
new
的 - 构造函数内的
this
相等于 下面person1
- 构造函数内不需要
return
默认就是return this
; - 每
new
一次,就在内存中开辟一个新的空间。
构造函数的弊端
先看代码
function SuperPerson(name) {
this.name = name;
this.say = function () {
console.log("拒绝内卷,从" + this.name + " 做起");
};
}
const p1 = new SuperPerson("路飞");
const p2 = new SuperPerson("乔巴");
console.log(p1 === p2); // false
console.log(p1.name === p2.name); // false
console.log(p1.say === p2.say); // false
我们知道,数据类型比较的关键是
-
简单类型的比较 值比较
路飞
≠乔巴
-
复杂类型的比较 引用地址比较
p1
≠p2
p1.say
≠p2.say
-
如图所示
提取公共函数
不同对象之间 name
不一样 好理解,但是 他们的行为 也就是方法 -say,应该是一致的。也就是应该可以共用的,也就更能节省内存。 总之,我们想要实现
p1.say = p2.say;
这个好做,我们看看
function say() {
console.log(this.name);
}
function SuperPerson(name) {
this.name = name;
// 指向外部的say
this.say = say;
}
const p1 = new SuperPerson("路飞");
const p2 = new SuperPerson("乔巴");
console.log(p1.say === p2.say); // true
原理如图:
两个对象中的 say 方法 指向了同一个
构造函数-原型
上述代码能够看出,虽然是解决了不同对象共享一个函数的弊端,但是代码的结构也未免太丑了,
- 一个功能 分成了两个入口
say
和SuperPerson
say
方法也导致了全局污染
function say() {
// 感情 say 这个名称就被你独占了
console.log(this.name);
}
function SuperPerson(name) {
this.name = name;
// 指向外部的say
this.say = say;
}
因此我们使用原型来解决 prototype
function SuperPerson(name) {
this.name = name;
}
// 在原型上定义方法
SuperPerson.prototype.say = function () {
console.log(this.name);
}
const p1 = new SuperPerson("路飞");
const p2 = new SuperPerson("乔巴");
console.log(p1.say === p2.say); // true
看到这里伙计们应该知道了这样写法没有问题了。但是底层的原因呢,我们现在就对原型做通俗讲解
原型的通俗讲解
在 JavaScript
中,任何对象都有一个原型,就像每一个人都有一个爸爸