原型真的有用吗
有不少小伙子应该会有这个感觉 大家都在说原型 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
即可,干净利索。
构造函数
上述的工厂函数虽然解决了多个对象批量修改属性的问题,但是也是存在弊端的。请看以下的打印。
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
中,任何对象都有一个原型,就像每一个人都有一个爸爸?一样。接下来为了方便讲解,我们统一技术名词
SuperPerson
称为构造函数
p1
,p2
称为实例
prototype
称为原型对象
其中
- 构造函数
SuperPerson
理解为父亲(母亲也ok) p1
,p2
理解为孩子
- 那
prototype
则理解为 父亲 传授给 孩子 的 那一条DNA
只要父亲生了孩子(理解为 new 对象),那么孩子就一定会有一条 DNA,只要父亲在DNA上做了任何 属性或者方法的声明,那么孩子就一定可以获取到。
function SuperPerson(name) {
this.name = name;
}
// DNA 上定义属性
SuperPerson.prototype.say = function () {
console.log(this.name);
}
SuperPerson.prototype.jump = function () {
console.log("you jump I jump ");
}
const p1 = new SuperPerson("路飞");
// 孩子可以获得
console.log(p1.say);
console.log(p1.jump);
原型的深入讲解
我们平时使用的字符串方法,数组方法,日期方法,以及正则方法等都是通过原型来获得的。
通过数组举例子
const list = [1, 2, 3, 4];
console.log(list);
能看到 在数组的 __protp__
属性上存到大量我们常用的方法。
简单解释下 __proto__
(一共四个下划线) 是什么。
__proto__
也叫原型。- 它是非标准属性,存在实例上。
- 它的作用只是方便我们在浏览器上查看原型,我们不要对它有任何操作,只能看不能摸。
- 构造函数的prototype 等于 实例的 proto
简单来说,我们是可以通过 prototype
和 __proto__
自下而上 找到 JavaScript的老祖宗的。
构造函数的prototype 等于 实例的 proto
// SuperPerson 是构造函数
function SuperPerson() {
}
const p1 = new SuperPerson();// p1 是实例
console.log(p1.__proto__ === SuperPerson.prototype); // true
任何构造函数都是 Function的实例
只要满足条件 构造函数的prototype 等于 实例的 proto 我们就能顺藤摸瓜,寻找老祖宗
const array = new Array(); // Array 是构造函数
console.log(Array.__proto__ === Function.prototype); // true
const date = new Date();// Date 是构造话剧
console.log(Date.__proto__ === Function.prototype); // true
function SuperPerson() { }
const p1 = new SuperPerson();// SuperPerson 是构造函数
console.log(SuperPerson.__proto__ === Function.prototype); // true
Function 和 Object 无绝对的关系
很多伙计多了这一层就过不去了,一直在找两者的直接联系。 这里需要明确,两者直接没有直接联系!!!
那么间接联系呢
Function的原型是Object原型的实例
console.log(Function.prototype.__proto__ === Object.prototype); // true
Object的原型和null直接的关系
console.log(Object.prototype.__proto__ === null);
将上图串联起来
锦上添花
任何的构造函数,都是 Function
的实例
// Object是构造函数
const obj = new Object();
console.log(Object.__proto__ === Function.prototype);
上图
小结
上述则为 JavaScript中令人闻风丧胆的原型链
我们判断构造函数和实例直接的关键是 一句话
任何构造函数都是Function的实例
以上你会画了没有
原型实际应用
学习原理,是为了更加方便的运用技术。
我们来改造下最开头的那个 创建圆角元素的案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 100px;
height: 100px;
background-color: aqua;
margin: 100px auto;
}
.rds {
border-radius: 50%;
}
@keyframes scale {
to {
transform: scale(2);
}
}
.scale {
animation: scale 1s linear infinite alternate;
}
</style>
</head>
<body>
<script>
// 构造函数
function CreateCurveElement(elementName) {
const element = document.createElement(elementName);
this.element = element;
this.addClass("rds");
}
// 指定该元素插入到哪里
CreateCurveElement.prototype.appendTo = function (parent) {
document.querySelector(parent).appendChild(this.element);
}
// 绑定事件
CreateCurveElement.prototype.on = function (eventName, cb) {
this.element.addEventListener(eventName, cb.bind(this));
}
CreateCurveElement.prototype.addClass = function (clsName) {
this.element.classList.add(clsName);
}
const div = new CreateCurveElement("div");
div.appendTo("body");
div.on("click", function () {
this.addClass("scale");
})
</script>
</body>
</html>
原型的继承
原型的继承是通过改造 原型对象来实现的!
我们新建一个子构造函数来继承父构造函数的功能 。
// 创建一个子构造函数 圆角图片 只传入图片路径即可,其他交给父构造函数实现
function CreateCurveImg(src) {
// 调用父构造函数 实现帮忙给this设置值
CreateCurveElement.call(this, "img");
this.element.src = src;
}
// 子构造函数 或者到了 父构造函数的原型上的功能 (存在弊端)
CreateCurveImg.prototype = CreateCurveElement.prototype;
const img1 = new CreateCurveImg("images/1.png");
img1.appendTo("body");
img1.on("click",function(){
this.addClass("scale");
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 100px;
height: 100px;
background-color: aqua;
margin: 100px auto;
}
.rds {
border-radius: 50%;
}
@keyframes scale {
to {
transform: scale(2);
}
}
.scale {
animation: scale 1s linear infinite alternate;
}
</style>
</head>
<body>
<script>
function CreateCurveElement(elementName) {
const element = document.createElement(elementName);
this.element = element;
this.addClass("rds");
}
CreateCurveElement.prototype.appendTo = function (parent) {
document.querySelector(parent).appendChild(this.element);
}
CreateCurveElement.prototype.on = function (eventName, cb) {
this.element.addEventListener(eventName, cb.bind(this));
}
CreateCurveElement.prototype.addClass = function (clsName) {
this.element.classList.add(clsName);
}
// 创建一个子构造函数 圆角图片 只传入图片路径即可,其他交给父构造函数实现
function CreateCurveImg(src) {
// 调用父构造函数 实现帮忙给this设置值
CreateCurveElement.call(this, "img");
this.element.src = src;
}
// 子构造函数 或者到了 父构造函数的原型上的功能 (存在弊端)
CreateCurveImg.prototype = new CreateCurveElement();
const img1 = new CreateCurveImg("images/1.png");
img1.appendTo("body");
img1.on("click",function(){
this.addClass("scale");
})
</script>
</body>
</html>
补充与拓展
-
判断 实例和构造函数之间的关系,我们使用 运算符
instanceof
即可// Array 是Function的实例 console.log(Array instanceof Function); // true
-
目前基本都是使用 es6的 class 代替es5 的原型。
-
class 称之为 类。 负责将定义对象相关的代码全部包装在一起
-
extend
继承 表示要继承谁。相等于CreateCurveImg.prototype = new CreateCurveElement();
-
super
调用父类构造函数CreateCurveElement.call(this, "img");
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div { border-radius: 50%; width: 100px; height: 100px; background-color: aqua; margin: 100px auto; } @keyframes scale { to { transform: scale(2); } } .rds { border-radius: 50%; } .scale { animation: scale 1s linear infinite alternate; } </style> </head> <body> <script> class CreateCurveElement { constructor(elementName) { const element = document.createElement(elementName); this.element = element; this.addClass("rds"); } appendTo(parent) { document.querySelector(parent).appendChild(this.element); } on(eventName, cb) { this.element.addEventListener(eventName, cb.bind(this)); } addClass(clsName) { this.element.classList.add(clsName); } } // const div = new CreateCurveElement("div"); // div.appendTo("body"); // div.on("click", function () { // console.log("那一夜~"); // }) class CreateCurveImg extends CreateCurveElement { constructor(src) { super("img"); this.element.src = src; } } const img1 = new CreateCurveImg("images/1.png"); img1.appendTo("body"); img1.on("click", function () { this.addClass("scale"); }) </script> </body> </html>
-
素材资料
好友微信号
添加大佬微信 和上千同伴一起提升技术交流生活
hsian_
最后
码字不容易 你的点击关注点赞留言就是我最好的驱动
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!