不可思议的BUG

莫看江面平如镜,要看水底万丈深——记一次由表象引起的不是BUG的BUG

起因

先上图
image
一位同学碰到了这样一个问题:明明配置的请求地址是ip,为什么变成域名了。

上图,第一行是实际请求报错的接口,第二行才是打印出的真正配置的接口地址,由于项目上会根据项目环境,会切换接口地址,方便开发、测试、生产。我的第一反应就是切换地址的逻辑有问题。仔细跟了他写的代码,确实在接口请求前,地址是开发地址,但请求之后变成了生产地址。

自己折腾了半天,尝试了各种方案:

  • 把地址换成固定的字符串,如aaa,没问题;
  • 将接口定义文件内容简化;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 简化前
    export default {
    dev: {
    DEV_URL: ''
    },
    test: {
    TEST_URL: ''
    },
    prod: {
    PROD_URL: ''
    }
    }
    // 简化后
    export default {
    dev: '',
    test: '',
    prod: ''
    }
  • 我把生产地址注释掉,发现还调用的是生产地址

然后我又把锅甩给了微信开发者工具,哈哈

好吧,我需要冷静,程序不可能出这种不可思议的错误,回想了最后尝试的一种情况,自己明明把地址注释了,为什么还能显示,然后又尝试全局搜索,还是没发现这个地址……

撞鬼了……

冷静……

冷静……

冷静……

我默默的打开了调试调试工具的network模块
image

我靠,301这不是重定向嘛,看了看详情,果真如此,本来我发送的求情开发库,结果被重定向到了生产环境了

总结

表象迷惑了我,如果早点打开network就不会遇到这么多问题,最终还是坚信有条理的代码,不会产生不可思议的bug。

前端自动化构建及部署

项目自动化构建、部署

前言

自动化这个字眼神圣而又高大上,对后端开发的同学来说也许再熟悉不过了,可对于前端开发来说,确实有些遥不可及,接下来分享下,我在前端项目的自动化实践。

实现方式

  • 一套是工作中经常使用的Docker + Jenkins。Jenkins是持续集成的关键。
  • 使用DaoClound

什么是持续集成?

jenkins的使用成本还是比较高的,因为需要搭建Jenkins的基本环境,但使用率想到高。我主要介绍的是第二套方案,成本低,但能实现自动化的功能。

DaoCloud的使用

  1. 主机接入与集群管理

    需要将自己的虚拟机接入到Daocloud,具体看这里

  2. 用Docker搭建前端应用

先看看我的项目image

具体实现步骤

这是wo博客实现自动化构建的过程

  1. 在项目的根目录加入dockerfile文件
    image
    上述内容就实现了将手动的命令,写成脚本让其实现自动执行的功能。dockerfile是实现项目容器化的必备配置文件。
  2. 镜像的自动化构建(使用github)

    代码源指定了您项目的代码仓库位置,关联代码源后,您对代码源的操作会自动触发项目的相应动作比如持续集成和镜像构建。
    image
    image

    选择目标项目,填写镜像名称,点击开始创建
    image
    点击前往流程定义按钮
    image
    点击手动构建选择需要构建的分支
    image

    image
    image
    上面一步就表示自动化构建成功了。

  3. 把构建的镜像发布到自己搭建的主机上
    image
    点击目标项目部署最新版按钮
    image
    image
    下面这一步就表示部署成功了
    image
    点击下面的提示的连接访问部署的项目
    image
    打开下面的自动发布按钮,每次项目push到目标分支都会进行自动构建和部署

最后

博客实例robinblog

angular实例 ng-ant-admin

步骤写的比较简单,如果有不懂的可以在我的blog给我发消息。

blog地址http://blog.rnode.me

前端进阶系列—HTML5新特性

HTML5 是对 HTML 标准的第五次修订。其主要的目标是将互联网语义化,以便更好地被人类和机器阅读,并同时提供更好地支持各种媒体的嵌入。HTML5 的语法是向后兼容的。现在国内普遍说的 H5 是包括了 CSS3,JavaScript 的说法(严格意义上说,这么叫并不合适,但是已经这么叫开了,就将错就错了)。

HTML5新特性

语义特性

HTML5赋予网页更好的意义和结构

  • 文件类型声明(<!DOCTYPE>)仅有一型:<!DOCTYPE HTML>。
  • 新的解析顺序:不再基于SGML。
  • 新的元素:section, video, progress, nav, meter, time, aside, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup, keygen, mark, output, rp, rt, ruby, source, summary, wbr。
  • input元素的新类型:date, email, url等等。
  • 新的属性:ping(用于a与area), charset(用于meta), async(用于script)。
  • 全域属性:id, tabindex, repeat。
  • 新的全域属性:contenteditable, contextmenu, draggable, dropzone, hidden, spellcheck。
  • 移除元素:acronym, applet, basefont, big, center, dir, font, frame, frameset, isindex, noframes, strike, tt。

本地存储特性

HTML5离线存储包含:应用程序缓存(Application Cache)本地存储索引数据库文件接口

应用程序缓存

通过创建cache manifest文件,可以轻松的创建web应用的离线版本
其优势在于:

  • 离线浏览-用户可在应用离线时使用它们
  • 速度-已缓存静态资源,使加载更快
  • 减少服务器负载-浏览器将只存服务器下载更新过或修改过的资源

本地存储

  • localStorage
  • sessionStorage

从名字上看就可以很清楚的辨认两者的区别,前者是一直存在本地的,后者只是伴随着session,窗口一旦关闭就没了。

索引数据库(indexed DB)

从本质上说,IndexedDB允许用户在浏览器中保存大量的数据。任何需要发送大量数据的应用都可以得益于这个特性,可以把数据存储在用户的浏览器端。当前这只是IndexedDB的其中一项功能,IndexedDB也提供了强大的基于索引的搜索api功能以获得用户所需要的数据。

用户可能会问:IndexedDB是和其他以前的存储机制(如cookie,session)有什么不同?

Cookies是最常用的浏览器端保存数据的机制,但其保存数据的大小有限制并且有隐私问题。Cookies并且会在每个请求中来回发送数据,完全没办法发挥客户端数据存储的优势。

再来看下Local Storage本地存储机制的特点。Local Storage在HTML 5中有不错的支持,但就总的存储量而言依然是有所限制的。Local Storage并不提供真正的“检索API”,本地存储的数据只是通过键值对去访问。Local Storage对于一些特定的需要存储数据的场景是很适合的,例如,用户的喜好习惯,而IndexedDB则更适合存储如广告等数据(它更象一个真正的数据库)。

一般来说,有两种不同类型的数据库:关系型和文档型(也称为NoSQL或对象)。关系数据库如SQL Server,MySQL,Oracle的数据存储在表中。文档数据库如MongoDB,CouchDB,Redis将数据集作为个体对象存储。IndexedDB是一个文档数据库,它在完全内置于浏览器中的一个沙盒环境中(强制依照(浏览器)同源策略)。

对数据库的每次操作,描述为通过一个请求打开数据库,访问一个object store,再继续。

IndexedDB是否适合应用程序的几个关键点

  • 你的用户通过浏览器访问您的应用程序,(浏览器)支持IndexedDB API吗 ?
  • 你需要存储大量的数据在客户端?
  • 你需要在一个大型的数据集合中快速定位单个数据点?
  • 你的架构在客户端需要事务支持吗?

如果你对其中的任何问题回答了“是的”,很有可能,IndexedDB是你的应用程序的一个很好的候选。

文件接口

看这里http://www.cnblogs.com/zichi/p/html5-file-api.html

设备访问特性

包括地理位置API媒体访问API访问联系人及事件设备方向

地理位置

看这里https://developer.mozilla.org/zh-CN/docs/Web/API/Geolocation/Using_geolocation

媒体访问

看这里https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Using_HTML5_audio_and_video

访问联系人及事件

看这里 https://blog.csdn.net/qq_27626333/article/details/51815229

设备方向

看这里 https://developer.mozilla.org/zh-CN/docs/Web/API/Detecting_device_orientation

连接特性

HTTP是无连接的,一次请求,一次响应。想要实现微信网页版扫一扫登录,网页版微信聊天的功能,需要使用轮询的方式达到长连接的效果,轮询的大部分时间是在做无用功,浪费网络,浪费资源。现在HTML5为我们带来了更高效的连接方案 Web SocketsServer-Sent Events

网页多媒体特性

HTML5支持原生的音视频能力:Audiovideo

三维、图形及特效特性

大致包含SVG, Canvas, WebGL, 和 CSS3 3D,下面分别进行研究。

性能与集成特性

性能与集成特性主要包括两个东西,Web Workers 和 XMLHttpRequest 2。

参考文章:

前端进阶系列—常见布局及居中

css常见布局解决方案

水平居中布局

margin+定宽

1
2
3
4
5
6
7
8
9
10
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.child {
width: 100px;
margin: 0 auto;
}
</style>
  • 想必是个前端都见过,这定宽的水平居中,我们还可以用下面这种来实现不定宽

    table+margin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="parent">
    <div class="child">Demo</div>
    </div>

    <style>
    .child {
    display: table;
    margin: 0 auto;
    }
    </style>
  • display:table在表现上类似block元素,但是宽度为内容宽。

  • 无需设置父元素样式 (支持 IE 8 及其以上版本)兼容 IE 8 一下版本需要调整为 <table>

inline-block+text-align

1
2
3
4
5
6
7
8
9
10
11
12
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.child {
display: inline-block;
}
.parent {
text-align: center;
}
</style>

兼容性佳(甚至可以兼容IE6和IE7)

absolute+margin-left

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
width: 100px;
margin-left: -50px; /* width/2 */
}
</style>
  • 宽度固定
  • 相比与使用transform兼容性更好

absolute+transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
</style>
  • 绝对定位脱离文档流,不会对后续元素的布局造成影响
  • transform为CSS3属性,有兼容性问题

flex+justify-content

1
2
3
4
5
6
7
8
9
10
  <div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
display: flex;
justify-content: center;
}
</style>
  • 只需设置父节点属性,无需设置子元素
  • flex有兼容性问题

    垂直居中

    table-cell+vertical-align

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="parent">
    <div class="child">Demo</div>
    </div>

    <style>
    .parent {
    display: table-cell;
    vertical-align: middle;
    }
    </style>
  • 兼容性好(IE 8以下版本需要调整页面结构至 table)

absolute+transform

强大的absolute对于这种小问题当然是很简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
</style>

  • 绝对定位脱离文档流,不会对后续元素的布局造成影响,但如果绝对定位元素是唯一的元素,则父元素也会失去高度。
  • transform 为CSS3属性,有兼容性问题

同水平居中,这也可以使用margin-top实现,原理水平居中

flex+align-items

如果说absolute强大,那flex只是笑笑,因为他才是最强的,但有兼容性问题

1
2
3
4
5
6
7
8
9
10
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
display: flex;
align-items: center;
}
</style>

水平垂直居中

absolute+transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
</style>
  • 绝对定位脱离文档流,不会对后续元素的布局造成影响
  • transform为CSS3属性,有兼容性问题

inline-block+text-align+table-cell+vertical-align

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="parent">
<div class="child">Demo</div>
</div>

<style>
.parent {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.child {
display: inline-block;
}
</style>
  • 兼容性好

    flex+justify-content+align-items

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <div class="parent">
    <div class="child">Demo</div>
    </div>

    <style>
    .parent {
    display: flex;
    justify-content: center; /* 水平居中 */
    align-items: center; /*垂直居中*/
    }
    </style>
  • 只需设置父节点属性,无需设置子元素

  • 还是存在兼容性问题

一列定宽,一列自适应

这种布局最常见的就是中后台类型的项目,如下图:

image

float+margin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="parent">
<div class="left">
<p>left</p>
</div>
<div class="right">
<p>right</p>
<p>right</p>
</div>
</div>

<style>
.left {
float: left;
width: 100px;
}
.right {
margin-left: 100px
/*间距可再加入 margin-left */
}
</style>

IE6中会有3px的BUG,解决方法可以在.left加入margin-left:-3px当然下面的方案也可以解决这个bug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div class="parent">
<div class="left">
<p>left</p>
</div>
<div class="right-fix">
<div class="right">
<p>right</p>
<p>right</p>
</div>
</div>
</div>

<style>
.left {
float: left;
width: 100px;
}
.right-fix {
float: right;
width: 100%;
margin-left: -100px;
}
.right {
margin-left: 100px
/*间距可再加入 margin-left */
}
</style>

float+overflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="parent">
<div class="left">
<p>left</p>
</div>
<div class="right">
<p>right</p>
<p>right</p>
</div>
</div>

<style>
.left {
float: left;
width: 100px;
}
.right {
overflow: hidden;
}
</style>

设置overflow:hidden会出发BFC模式(block formatting context)块级格式上下文。BFC是什么呢?用通俗的江就是,随便你在BFC里面干什么,外面都不会手段哦影响。此方法样式简单但不支持 IE 6

table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="parent">
<div class="left">
<p>left</p>
</div>
<div class="right">
<p>right</p>
<p>right</p>
</div>
</div>

<style>
.parent {
display: table;
width: 100%;
table-layout: fixed;
}
.left {
display: table-cell;
width: 100px;
}
.right {
display: table-cell;
/*宽度为剩余宽度*/
}
</style>

table 的显示特性为每列的单元格宽度和一定等与表格宽度。 table-layout: fixed 可加速渲染,也是设定布局优先。table-cell 中不可以设置margin但是可以通过 padding 来设置间距

flex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="parent">
<div class="left">
<p>left</p>
</div>
<div class="right">
<p>right</p>
<p>right</p>
</div>
</div>

<style>
.parent {
display: flex;
}
.left {
width: 100px;
margin-left: 20px;
}
.right {
flex: 1;
}
</style>
  • 低版本浏览器兼容性问题
  • 性能问题,只是适合小范围布局

以上就是常见的几种布局。

参考文章:

前端进阶系列—SEO和语义化

SEO和HTML语义化

基本概念

SEO:(Search Engine Optimization)意为搜索引擎优化。搜索引擎优化是一种利用搜索引擎的搜索规则来提高目的网站的自然排名的方式。说白了就是对品牌的自我宣传,从而获取收益。

HTML语义化:根据内容的结构的语义化,选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时利于SEO优化。

使用SEO和语义化的目的

前面也做了基本的阐述:

  • SEO就是为了对品牌的自我宣传,从而获得收益;
  • 语义化的目的:
    • 在没有css的情况下,页面也能呈现出很好的内容结构,代码结构;
    • 提升用户体验:例如title、alt用于解释名词和解释图片信息;
    • 有利于SEO优化:和搜索引擎建立良好的沟通,有助于爬虫抓取更多的有效信息,爬虫依赖于标签来确定上下文和各个关键字的权重;
    • 方便其他设备解析如屏幕阅读器、盲人阅读器、移动设备;
    • 语义化更据有可读性,便于团队开发和维护。

语义化和SEO的重要性

Google、Yahoo、Bing 约定,对 http://schema.org 所列出来的标签进行解析。

原因其实很简单,因为schema标签对搜索引擎很重要。
目前,搜索引擎的发展趋势是全能化,简单化,让用户在最短的时间里获取到想要的信息,让用户在点击搜索结果之前就获取到足够的信息进行判断。

比如Google general search,Google会根据你搜索的关键词推荐网页、新闻、图片、视频、音乐、商品、地图、本地结果、实时结果等等。

比如Google onebox,Google会根据某些特定的指令,直接给出针对性的结果,现在在搜索引擎可以直接查天气,查航班。

还有前段时间Google推出的网站截图预览,snippets lists等等。

Google的目标是要整合所有资源,让用户能方便获取到。资源有很多种,文字目前搜索引擎可以完全识别到,但是图片、flash、音乐、视频搜索引擎不能识别或只能识别一小部分,所以以目前的技术,尚不能整合网站上所有呈现出来的资源,所以需要用户进行配合。

我觉得这就是语义化标签出现的意义吧。

搜索引擎希望每个网站都把不同的资源标记出来,并打上搜索引擎容易识别的标签,利于搜索引擎进行判断归类,并在搜索结果中更好的呈现出来。

从SEO角度来看,对搜索引擎重要的就是对SEO重要的。

网站如果按照schema上的规则进行重写代码,方便了搜索引擎对信息的抓取,对网站内容在搜索结果中的呈现会有很好的帮助,能够适应搜索引擎的发展趋势。也许对某些关键词排名不会有明显的效果,但是从长远来看,对于从搜索引擎获取流量应该有不小的帮助。

HTML5的革新—语义化标签

html5布局

W3C定义了这些语义标签,不可能完全符合我们有时的设计目标,就像制定出来的法律不可能流传100年都不改变,更何况它才制定没多久,不可能这些语义标签对所以设计目标的适应。只是一定程度上的“通用”,我们的目标是让爬虫读懂重要的东西就够了。

结论:不能因为有了HTML 5标签就弃用了div,每个事物都有它的独有作用的。

参考文章:

前端进阶系列

HTML/CSS篇

  1. SEO和语义化
  2. 常见布局及居中
  3. HTML5新特性
  4. CSS3新特性
  5. flex布局
  6. 盒模型

JS篇

  1. 什么是执行上下文
  2. JS执行机制
  3. 从原型到原型链
  4. 词法作用域和动态作用域
  5. 变量对象
  6. 作用域链
  7. 从ECMAScript规范解读this
  8. 闭包
  9. 参数按值传递
  10. call和apply的模拟实现
  11. bind的模拟实现
  12. new的模拟实现
  13. 类数组对象与arguments
  14. 创建对象的多种方式以及优缺点
  15. 继承的多种方式及优缺点

算法篇

  1. 各种排序,重点是块排
  2. 动态规划,参见背包问题
  3. 二叉树

nodejs篇

  1. nodejs特性
  2. 事件循环
  3. 多进程,cluster及child process,pm2的原理
  4. koa的特性及中间件的原理
  5. express与koa的区别

网络篇

  1. https
  2. http2
  3. http状态码
  4. 网络安全,xss和csrf
  5. session,cookie和token
  6. OSI七层协议
  7. 缓存
  8. 跨域
  9. 模块化,commonjs,cmd,amd,umd
  10. cdn及dns

架构篇

  1. vue解决了什么问题
  2. vue和react的区别
  3. virtual dom的原理
  4. 双向绑定的原理
  5. 如何实现component
  6. 组件间通讯
  7. vuex
  8. vue-router

项目篇

  1. 性能优化
  2. webpack的打包原理,如何抽取css
  3. 提升webpack的编译速度
  4. 错误收集,错误排查
  5. 项目监控
  6. 项目部署

移动篇

  1. 自适应
  2. 兼容性
  3. PWA
  4. 小程序
  5. 移动端手势

补充篇

  1. 无限滚动方案
  2. 重绘重排重合成
  3. 浏览器的访问过程
  4. 如何处理兼容性问题
  5. 经常去的技术网站?读过什么书?
  6. 未来规划

深入理解JavaScript系列—从原型到原型链

本文主要摘抄自冴羽大佬的blog。

1
2
原文作者:冴羽
原文地址:https://github.com/mqyqingfeng/Blog/issues/2


学过Java的同学应该知道,有类继承的概念,对于JavaScript这种动态语言来说,不通过类继承的功能,即便在ES6里面引入了class关键字,也只是语法糖,基本值还是基于原型prototype实现的。

先介绍几个概念:

什么是对象

若干属性的集合

什么是构造函数

在JavaScript中,用new关键字来调用定义的构造函数。默认返回的是一个新对象,这个新对象具有构造函数定义的变量和函数/方法。

什么是原型

原型是一个对象,其他对象可以通过它实现继承

哪些对象有原型

所有的对象在默认情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一个例外,默认的对象原型在原型链的顶端)

下面介绍如何通过构造函数创建一个对象。

构造函数创建对象

1
2
3
4
5
6
function Person() {

}
var person = new Person();
person.name = 'Kevin';
console.log(person.name);// Kevin

在这个例子中,Person就是一个构造函数,我们使用new创建一个实例对象person。
很简单吧,接下来进入正题:

prototype

每个函数都有一个prototype属性,就是我们经常在各种例子中看到的那个prototype,比如:

1
2
3
4
5
6
7
8
9
10
function Person() {

}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name);// Kevin
console.log(person2.name);// Kevin

让我们用一张图表示构造函数和实例原型之间的关系:

在这张图中我们用Person.prototype表示实例原型。
那么我们该怎么表示实例与实例原型,也就是person和Person.prototype之间的关系呢,这时候就要讲到第二个属性:

proto

这是每个JavaScript对象(除null)都具有的一个属性,叫 proto ,这个属性会指向该对象的原型。

为了证明这一点,我们可以在火狐或者谷歌中输入:

1
2
3
4
5
function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype);// true

于是我们更新了关系图:

既然实例对象和构造函数都可以指定原型,那么原型是否有属性指向构造函数或者实例呢?

constructor

指向实例倒没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要降到第三个属性:constructor,每个原型都有一个constructor属性指向关联的构造函数。

为了验证这一点,我们可以尝试:

1
2
3
4
function Person() {

}
console.log(Person === Person.prototype.constructor); // true

所以再更新一下关系图:

综上我们已经得出:

1
2
3
4
5
6
7
8
9
10
function Person() {

}

var person = new Person();

console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

了解了构造函数、实例原型、和实例之间的关系,接下来我们讲讲实例和原型的关系:

实例与原型

当读取实例的属性时,如果找不到,就会找到与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层位置。
举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

在这个例子中,我们给实例对象person添加了name属性,当我们打印person.name的时候,结果自然为Daisy.

但是当我们删除了person的name属性室,读取person.name,从person对象中找不到name属性就会从person的原型也就是person. proto ,也就是Person.prototype中查找,幸运得失,我们找到了name属性,结果为Kevin。

但是万一还没有找到呢?原型的原型又是什么呢?

原型的原型

在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:

1
2
3
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin

其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:

原型链

那 Object.prototype 的原型呢?

null,我们可以打印:

1
console.log(Object.prototype.__proto__ === null) // true

然而 null 究竟代表了什么呢?

引用阮一峰老师的 《undefined与null的区别》 就是:

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

最后一张关系图也可以更新为:

顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

补充

最后,补充三点大家可能不会注意的地方:

constructor

首先是 constructor 属性,我们看个例子:

1
2
3
4
5
function Person() {

}
var person = new Person();
console.log(person.constructor === Person); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:

1
person.constructor === Person.prototype.constructor

proto

其次是 proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。

真的是继承吗?

最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

深入理解JavaScript系列—constructor、prototype、_proto_ 详解

本文为了解决一下问题:

  • _proto_(实际原型)和prototype(原型属性)不一样!!!
  • constructor属性(原型对象中包含这个属性,实例当中也同样会继承这个属性)
  • prototype属性(constructor.prototype原型对象)
  • _proto_属性(实例指向原型对象的指针)

首先弄清楚几个概念:

什么是对象

若干属性的集合

什么是原型

原型是一个对象,其他对象可以通过它实现继承

哪些对象有原型

所有的对象在默认情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一个例外,默认的对象原型在原型链的顶端)


constructor 属性

constuctor属性始终指向创建当前对象的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr=[1,2,3];
console.log(arr.constructor); //输出 function Array(){}
var a={};
console.log(arr.constructor);//输出 function Object(){}
var bool=false;
console.log(bool.constructor);//输出 function Boolean(){}
var name="hello";
console.log(name.constructor);//输出 function String(){}
var sayName=function(){}
console.log(sayName.constructor)// 输出 function Function(){}

//接下来通过构造函数创建instance
function A(){}
var a=new A();
console.log(a.constructor); //输出 function A(){}

以上部分解释了任何一个对象都有constuctor属性,指向创建这个对象的构造函数

prototype属性

注意:prototype是每个函数对象都具有的属性,被称为原型对象,而_proto_属性才是每个对象才有的是属性,一但原型对象被赋予属性和方法,那么由相应的构造函数创建的实例会继承prototype上的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//constructor : A
//instance : a
function A(){}
var a=new A();

A.prototype.name="xl";
A.prototype.sayName=function(){
console.log(this.name);
}

console.log(a.name);// "xl"
a.sayName();// "xl"

//那么由constructor创建的instance会继承prototype上的属性和方法

constructor属性和prototype属性

每个函数都有prototype属性,而这个prototypeconstructor属性会指向这个函数。

1
2
3
4
5
6
7
8
9
10
function Person(name){
this.name=name;
}
Person.prototype.sayName=function(){
console.log(this.name);
}
var person=new Person('x1');
console.log(person.constructor); // function Person(){}
console.log(person.prototype.constructor); // function Person(){}
console.log(Person.constructor); // function Function(){}

如果我们的重写(重新定义)这个Person.constructor属性,那么constructor属性的指向就会发生改变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Person.prototype={
setName:function(){
console.log(this.name);
}
}
console.log(person.constructor==Person); // 输出 false (为什么会输出false后面讲)
console.log(Person.constructor==Person); //输出 false

console.log(Person.prototype.constructor); // 输出 function Object(){}
//这里为什么会输出function Object(){}
//还记得之前说过constructor属性始终指向创建当前对象的构造函数吗?

Person.prototype={
sayName:function(){
console.log(this.name)
}
}
//这里实际上是对原型对象的重写
Person.prototype=new Object(){
sayName:function(){
console.log(this.name)
}
}
// 看到了吧。现在Person.prototype.constructor属性实际上指向Object的。

// 那么我如何将constructor属性再次指向Person呢?

Person.prototype.constructor=Person;

接下来解释为什么,看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name){
this.name=name;
}

var personOne=new Person('x1');

Person.prototype={
sayName:function(){
console.log(this.name);
}
}
var personTwo=new Person('x2');
console.log(personOne.costructor==Person); // true
console.log(personTwo.constructor==Person); // false

// 大家可能会对这个地方产生疑惑?为何第二个会输出false,personTwo不也是由Person创建的吗?这个地方应该要输出true啊?
// 这里涉及到js里面的原型继承
// 这个地方是因为person实例继承了Person.prototype原型对象的所有的方法和属性,包括constructor属性。当Person.prototype的constructor发生变化的时候,相应的person实例上的constructor属性也会发生变化。所以第二个会输出false
// 当然第一个是输出true,因为改变构造函数的prototype属性是在personOne被创建出来之后。

接下解释_proto_prototype属性
同样拿上面的代码来解释:

1
2
3
4
5
6
7
8
9
10
11
function Person(name){
this.name=name;
}
Person.prototype.sayName=function(){
console.log(this.name);
}
var person=name Person('x1');
person.sayName(); //输出 x1
//constructor : Person
//instance : person
//prototype : Person.prototype

首先给构造函数的原型对象Person.prototype赋值sayName方法,由构造函数Person创建的实例person会继承原型对象上的sayName方法。

为什么会继承原型对象的方法?

因为ECMAscript的发明者为了简化这门语言,同时又保证继承性,采用了链式继承的方法。
constructor创建的每个instance都有个_proto_属性,它指向constructor.prototype。那么constructor.prototype上定义的属性和方法都会被instance所继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name){
this.name=name;
}
Person.prototype.sayName=function(){
console.log(this.name);
}

var personOne=new Person("a");
var personTwo=new Person("b");

personOne.sayName(); // 输出 "a"
personTwo.sayName(); //输出 "b"

console.log(personOne.__proto__==Person.prototype); // true
console.log(personTwo.__proto__==Person.prototype); // true

console.log(personOne.constructor==Person); //true
console.log(personTwo.constructor==Person); //true
console.log(Person.prototype.constructor==Person); //true

console.log(Person.constructor); //function Function(){}
console.log(Person.__proto__.__proto__); // Object{}

认识原型对象和原型链

在javascript中,万物皆对象,但对象也有区别,大致可以分为两类,即:普通对象Object函数对象 Function
一般而言,通过new Function参省的对象时函数对象,其他对象都是普通对象。
举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function f1(){
//todo
};

var f2=function(){
//todo
};

var f3=new Function('x','console.log(x)');
var o1={};
var o2=new Object();
var o3=new f1();
console.log(
typeof f1,//function
typeof f2,//function
typeof f3,//function
typeof o1,//object
typeof o2,//object
typeof o3 //object
);
>> function function function object object object

f1属于函数的声明,最常见的函数定义,f2实际上是匿名函数,属于函数表达式,f3不常见,但也是一种函数对象。

Function是JS的自带的对象,f1,f2在创建的时候,JS会自动通过new Function()的方式来构建这些对象,因此这三个对象都是通过new Function()创建的。

在JavaScript中创建对象的方式有两种:对象字面量和使用new表达式,o1和o2的创建恰好对应了这两种方式,重点讲一下03,如果用java和c#的思路来理解的话,o3是f1的实例对象,03和f1是同类型,至少以前我是这么认为的,其实不然……

怎么理解呢?很简单,看o3是不是通过new Fuction()产生的,显然不是,既然不是函数对象,那就是普通对象。

通过对函数对象和普通对象的理解之后,我们来了解一下JavaScript中原型和原型链:

在JS中,每当创建一个函数对象 f1 时,该对象中都会内置一些属性,其中包括 prototype 和 proto, prototype 即原型对象,它记录着f1的一些属性和方法。

需要注意的是,prototype 对 f1 是不可见的,也就是说,f1 不会查找 prototype 中的属性和方法。

1
2
3
function f(){}
f.prototype.foo = "abc";
console.log(f.foo); //undefined

prototype的主要作用就是继承。通俗一点讲,prototype中定义的属性和方法都是留给自己”后代”用的,因此子类完全可以访问prototype上的属性和方法。

想要知道 f1 是如何把 prototype 留给“后代”,我们需要了解一下 JS 中的原型链。此时,JS中的 proto 入场了,这哥们长的很奇特,隐藏的也很深,以致于你经常见不到它,但它在普通对象和函数对象中都存在, 它的作用就是引用父类的 prototype 对象,JS在通过 new 操作符创建一个对象的时候,通常会把父类的 prototype 赋值给新对象的proto属性,这样就形成了一代代传承…

1
2
3
4
5
function f(){}
f.prototype.foo = "abc";
var obj = new f();
console.log(obj.foo); //abc
obj.__proto__===f.prototype; //true

现在我们知道,obj中proto保存的是 f 的 prototype,那么 f 的 prototype 中的 proto 中保存的是什么呢? 看下图:
image

如图所示,f.prototype 的 proto 中保存的是 Object.prototype,Object.prototype 对象中也有 proto,而从输出结果看,Object.prototype.proto 是 null,表示 obj 对象原型链的终结。如下图所示:

image

obj对象拥有这样一个原型链以后,当obj.foo执行时,obj会县查找自身是否有该属性,但不会查找自己的prototype,当找不到foo时,obj就沿着原型链一次去查找……
在上面的例子中,我们在f的prototype上定义了foo属性,这是obj就会在原型链上找到这个属性并执行。

最后,总结一下本文中涉及到的重点:

  • 原型链的形成真正靠的是proto而非prototype,当JS引擎执行对象的方式时,先查找对象本身是否存在该方法,如果不存在,会在原型链上查找,但不会查找自身的prototype.
  • 一个对象的proto记录着自己的原型链,决定了自身的数据类型,改变proto就等于改变了对象的数据类型。
  • 函数的prototype不属于自身的原型链,他是创建子类的核心,决定了子类的数据类型,是链接子类原型链的桥梁。
  • 在原型对象上定义方法和属性,是为了被子类继承和使用。

转载地址

语音控制灯

这是之前做的一个物联网的demo,给大家做个演示。