Repository: qianguyihao/Web Branch: master Commit: 2beefdef2059 Files: 255 Total size: 1.4 MB Directory structure: gitextract_v215t2g_/ ├── .gitignore ├── .prettierrc ├── 00-前端工具/ │ ├── 01-VS Code的使用.md │ ├── 02-Git的使用.md │ ├── 03-网络抓包和代理工具:Whistle.md │ ├── 04-解决 Git 不区分大小写导致的文件冲突问题.md │ ├── Atom在前端的使用.md │ ├── Emmet in VS Code.md │ ├── GitHub的使用.md │ ├── Mac安装和配置iTerm2.md │ ├── Sublime Text在前端中的使用.md │ ├── VS Code的使用积累.md │ ├── WebStorm的使用.md │ ├── chrome浏览器.md │ └── iconMoon.md ├── 01-HTML/ │ ├── 01-认识Web和Web标准.md │ ├── 02-浏览器的介绍.md │ ├── 03-初识HTML.md │ ├── 04-HTML标签:排版标签.md │ ├── 05-HTML标签:字体标签和超链接.md │ ├── 06-HTML标签:图片标签.md │ ├── 07-html标签图文详解(二).md │ ├── 08-HTML5详解.md │ ├── 09-HTML5举例:简单的视频播放器.md │ ├── 10-HTML5详解(二).md │ ├── 11-HTML5详解(三).md │ └── 12-HTML基础回顾.md ├── 02-CSS基础/ │ ├── 01-CSS属性:字体属性和文本属性.md │ ├── 02-CSS属性:背景属性.md │ ├── 03-CSS样式表和选择器.md │ ├── 04-CSS选择器:伪类.md │ ├── 05-CSS样式表的继承性和层叠性.md │ ├── 06-CSS盒模型详解.md │ ├── 07-浮动.md │ ├── 08-CSS属性:定位属性.md │ ├── 09-CSS案例讲解:博雅互动.md │ ├── 10-CSS3选择器详解.md │ ├── 11-CSS3属性详解(一).md │ ├── 12-CSS3属性详解:动画详解.md │ ├── 13-CSS3属性:Flex布局图文详解.md │ ├── 14-CSS3属性详解:Web字体.md │ ├── 15-Sass入门.md │ ├── 16-浏览器的兼容性问题.md │ ├── 17-CSS3的常见边框汇总.md │ └── others.md ├── 03-CSS进阶/ │ ├── 00-准备.md │ ├── 01-CSS中的非布局样式.md │ ├── 02-CSS布局.md │ ├── 03-网页设计和开发中,关于字体的常识.md │ ├── 04-如何让一个元素水平垂直居中?.md │ ├── CSS开发积累.md │ ├── CSS文章推荐.md │ ├── CSS的一些小知识.md │ └── CSS面试题.md ├── 04-JavaScript基础/ │ ├── 01-编程语言和JavaScript简介.md │ ├── 02-开始写JavaScript:hello world.md │ ├── 03-常量和变量.md │ ├── 04-标识符、关键字、保留字.md │ ├── 05-变量的数据类型:基本数据类型和引用数据类型.md │ ├── 06-基本数据类型:String 和 Boolean.md │ ├── 07-基本数据类型:Number.md │ ├── 08-基本数据类型:Undefined 和 Null.md │ ├── 09-数据类型转换.md │ ├── 10-运算符.md │ ├── 11-流程控制语句:选择结构(if和switch).md │ ├── 12-流程控制语句:循环结构(for和while).md │ ├── 13-对象简介.md │ ├── 14-基本包装类型.md │ ├── 15-内置对象 String:字符串的常见方法.md │ ├── 16-内置对象:Number和Math.md │ ├── 17-内置对象:Date.md │ ├── 18-数组简介.md │ ├── 19-数组的常见方法.md │ ├── 20-函数简介.md │ ├── 21-递归函数.md │ ├── 22-立即执行函数.md │ ├── 23-作用域、变量提升、函数提升.md │ ├── 24-预编译.md │ ├── 25-this指向.md │ ├── 26-闭包.md │ ├── 27-面向对象简介.md │ ├── 28-对象的创建&构造函数.md │ ├── 29-对象的基本操作.md │ ├── 30-浅拷贝和深拷贝.md │ ├── 31-对象的高级操作.md │ ├── 32-原型链和原型继承(待更新).md │ ├── 33-类和构造继承(待更新).md │ ├── 34-正则表达式.md │ ├── 35-事件简介.md │ ├── 36-DOM简介和DOM操作.md │ ├── 37-通过style对象获取和设置行内样式.md │ ├── 38-offset相关属性和匀速动画(含轮播图的实现).md │ ├── 39-scroll相关属性和缓动动画.md │ ├── 40-client(可视区)相关属性.md │ ├── 41-事件的绑定和事件对象Event.md │ ├── 42-事件的传播和事件冒泡.md │ ├── 43-事件委托.md │ ├── 44-键盘事件.md │ ├── 45-BOM简介和navigator.userAgent&History&Location.md │ ├── 46-定时器.md │ ├── 47-jQuery的介绍和选择器.md │ ├── 48-jQuery动画详解.md │ ├── 49-jQuery操作DOM.md │ ├── 50-jQuery的事件机制和其他知识.md │ ├── 51-Zepto入门.md │ ├── BOM的常见内置方法和内置对象.md │ ├── 原型对象.md │ ├── 原型链.md │ └── 常见代码解读.md ├── 05-JavaScript基础:ES6语法/ │ ├── 01-ES5和ES6的介绍.md │ ├── 02-ES5中的严格模式.md │ ├── 03-ES5中的一些扩展.md │ ├── 04-ES6:变量 let、const 和块级作用域.md │ ├── 05-ES6:变量的解构赋值.md │ ├── 06-ES6:箭头函数.md │ ├── 07-剩余参数和扩展运算符.md │ ├── 08-字符串、数组、对象的扩展.md │ ├── 09-内置对象扩展:Set数据结构.md │ └── ES6:Symbol.md ├── 06-JavaScript基础:异步编程/ │ ├── 00-服务器分类及PHP入门.md │ ├── 01-单线程和异步任务.md │ ├── 02-Ajax入门和发送http请求.md │ ├── 03-Ajax传输json和XML.md │ ├── 04-同源和跨域.md │ ├── 05-回调函数.md │ ├── 06-Promise入门详解.md │ ├── 07-Promise实例的方法.md │ ├── 08-Promise的链式调用.md │ ├── 09-Promise类的方法.md │ ├── 10-async异步函数.md │ ├── 11-异常处理方案.md │ ├── 12-事件循环机制、宏任务和微任务.md │ ├── 13-Promise的高级用法.md │ └── 14-Promise常见面试题.md ├── 07-JavaScript进阶/ │ ├── 01-var、let、const的区别.md │ ├── 02-浅拷贝和深拷贝.md │ ├── 03-迭代器和生成器.md │ ├── JavaScript开发积累.md │ ├── Promise的一些题目.md │ ├── call、apply、bind的区别.md │ ├── this.md │ ├── 作用域.md │ ├── 创建对象和继承.md │ ├── 数据的赋值.md │ ├── 数组的进阶操作.md │ └── 高阶函数.md ├── 08-前端基本功:CSS和DOM练习/ │ ├── 01-CSS基础练习:JD首页的制作(顶部和底部).md │ ├── 02-CSS基础练习:JD首页的制作(快捷导航部分).md │ ├── 03-DOM操作练习:基础练习.md │ ├── 04-DOM操作练习:Tab栏切换(通过className设置样式).md │ ├── 05-DOM操作练习:访问关系的封装.md │ └── 07-DOM操作练习:innerHTML的方式创建元素.md ├── 09-移动Web开发/ │ ├── 01-Bootstrap入门.md │ ├── 02-Bootstrap使用.md │ └── 03-Less详解.md ├── 10-MySQL数据库/ │ ├── 01-数据库的基础知识.md │ ├── 02-MySQL的安装和Navicat软件使用.md │ ├── 03-MySQL的基本操作.md │ ├── 04-MySQL字段的数据类型.md │ ├── 05-MySQL数据库的常用命令.md │ ├── MySQL设计三大范式.md │ └── 事务.md ├── 11-Node.js/ │ ├── 01-Node.js介绍.md │ ├── 02-Node.js的特点.md │ ├── 03-Node.js开发环境安装.md │ ├── 04-Node.js模块化规范:CommonJS.md │ ├── 05-Node.js内置模块:fs文件模块.md │ ├── 06-Node.js内置模块:path路径模块.md │ ├── 07-Node.js操作MySQL数据库.md │ ├── CommonJS.md │ ├── ES6.md │ ├── JavaScript模块化:AMD.md │ ├── JavaScript模块化:CMD.md │ ├── JavaScript模块化:ES6.md │ ├── KOA2.md │ ├── Node.js代码举例.md │ ├── WebSocket.md │ └── 事件驱动和非阻塞机制.md ├── 12-Vue基础/ │ ├── 01-Vue的介绍和vue-cli.md │ ├── 02-Vue的系统指令.md │ ├── 03-v-on的事件修饰符.md │ ├── 04-Vue的系统指令(二).md │ ├── 05-Vue的举例:列表功能.md │ ├── 06-自定义过滤器:时间格式化举例.md │ ├── 07-自定义按键修饰符&自定义指令.md │ ├── 08-Vue实例的生命周期函数.md │ ├── 09-Vue中的Ajax请求.md │ ├── 10-Vue动画.md │ ├── 11-Vue组件的定义和注册.md │ ├── 12-Vue组件之间的传值.md │ ├── 13-Vue-router路由.md │ ├── Vue-router路由.md │ ├── Vue.js在开发中的常见写法积累.md │ ├── Vue开发积累.md │ └── Vue组件.md ├── 13-React基础/ │ ├── 01-React介绍.md │ ├── 02-JSX语法介绍.md │ ├── 03-React组件(一):生命周期.md │ ├── 04-React组件(二):常见属性和函数.md │ ├── 05-React中绑定this并给函数传参的几种方式.md │ ├── 06-React的单向数据绑定.md │ ├── 07-React路由的使用.md │ ├── 08-Ant Design的基本使用.md │ ├── 09-AntD框架的upload组件上传图片时遇到的一些坑.md │ ├── 10-AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为.md │ └── 11-React Navive初识.md ├── 14-前端性能优化/ │ ├── 00-前端性能优化认知.md │ ├── 01-前端性能分析工具和指标.md │ ├── 02-浏览器渲染机制.md │ ├── 03-渲染优化.md │ ├── 04-静态资源优化.md │ ├── 05-页面渲染性能优化.md │ └── lazyload&防抖动和节流阀.md ├── 15-前端工程化/ │ ├── 01-前端代码规范.md │ ├── 02-前端书籍推荐.md │ ├── Vue开发积累.md │ ├── 前端工程化.md │ ├── 前端常见专有名词.md │ ├── 前端的几道题目.md │ ├── 前端监控技术.md │ └── 数组的常见操作.md ├── 16-前端综合/ │ ├── 01-Web前端开发流程和学习路线(详尽版).md │ ├── 02-Web前端入门自学路线(精简版).md │ ├── 2018年-前端日记.md │ ├── 2019年-前端日记.md │ ├── 2020年-前端日记.md │ ├── 2022年-前端日记.md │ ├── 2024年-前端日记.md │ ├── CSS开发总结.md │ ├── Express.md │ ├── ajax相关.md │ ├── html相关.md │ ├── json字符串的解析和遍历.md │ ├── json相关.md │ ├── 上海有哪些IT互联网大厂.md │ ├── 前端分享群整理.md │ ├── 前端博客推荐.md │ ├── 前端开发积累.md │ ├── 前端语录.md │ ├── 北京有哪些IT互联网大厂.md │ ├── 模板引擎.md │ ├── 深圳有哪些IT互联网大厂.md │ └── 网友对本项目提的建议.md ├── 17-资源推荐/ │ ├── 01-前端书籍推荐.md │ ├── 02-Web前端最新导航.md │ ├── 03-前端学习资源推荐.md │ ├── 04-前端大佬名单.md │ ├── 05-前端GitHub项目整理.md │ ├── 06-前端文章推荐.md │ ├── 2018-推荐文章.md │ ├── 2019-推荐文章.md │ ├── 2020-推荐文章.md │ └── 2022-推荐文章.md ├── LICENSE.md └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .vscode .DS_Store images ================================================ FILE: .prettierrc ================================================ { "printWidth": 300, "tabWidth": 4, "semi": true, "singleQuote": true, "trailingComma": "es5", "tslintIntegration": true, "insertSpaceBeforeFunctionParenthesis": false } ================================================ FILE: 00-前端工具/01-VS Code的使用.md ================================================ --- title: 01-VS Code的使用 --- ## 前言 > 文章标题:《第一次使用 VS Code 时你应该知道的一切配置》。本文的最新内容,更新于 2021-10-09。大家完全不用担心这篇文章会过时,因为随着 VS Code 的版本更新和插件更新,本文也会随之更新。 > 本文的最新内容,也会在[GitHub](https://github.com/qianguyihao/Web/blob/master/00-%E5%89%8D%E7%AB%AF%E5%B7%A5%E5%85%B7/01-VS%20Code%E7%9A%84%E4%BD%BF%E7%94%A8.md)上同步更新,欢迎 star。 VS Code 软件实在是太酷、太好用了,越来越多的新生代互联网民工正在使用它。 前端男神**尤雨溪**大大这样评价 VS Code: ![](http://img.smyhvae.com/20200619_0133.png) 有一点你可能会感到惊讶:VS Code 这款软件本身,是用 JavaScript 语言编写的(具体请自行查阅基于 JS 的 PC 客户端开发框架 `Electron`)。Jeff Atwood 在 2007 年提出了著名的 Atwood 定律: > **任何能够用 JavaScript 实现的应用系统,最终都必将用 JavaScript 实现**。 Jeff Atwood 这个人是谁不重要(他是 Stack Overflow 网站的联合创始人),重要的是这条定律。 前端目前是处在春秋战国时代,各路英雄豪杰成为后浪,各种框架工具层出不穷,VS Code 软件无疑是大前端时代最骄傲的工具。 如果你是做前端开发(JavaScript 编程语言为主),则完全可以将 VS Code 作为「**主力开发工具**」。这款软件是为前端同学量身定制的,开箱即用。 如果你是做其他语言方向的开发,并且不需要太复杂的集成开发环境,那么,你可以把 VS Code 作为「**代码编辑器**」来使用,纵享丝滑。 甚至是一些写文档、写作的同学,也经常把 VS Code 作为 markdown **写作工具**,毫无违和感。 退而求其次,即便你不属于以上任何范畴,你还可以把 VS Code 当作最简单的**文本编辑器**来使用,完胜 Windows 系统自带的记事本。 写下这篇文章,是顺势而为。 ## 一、惊艳登场:VS Code 的介绍 VS Code 的全称是 Visual Studio Code,是一款开源的、免费的、跨平台的、高性能的、轻量级的代码编辑器。它在性能、语言支持、开源社区方面,都做得很不错。 微软有两种软件:一种是 VS Code,一种是其他软件。 在2015年4月29日的微软Build开发者大会上,微软宣布推出 VS Code之后,这个轻量级的编辑器成为全球无数开发者们最喜爱的开发工具。VS Code基于开源且跨平台的理念,每月都会进行迭代,并提供每天发布的 insider 版本(insider是微软的一种公测计划,类似于国内软件所说的内测版)。它拥有至少几万个插件,生态极为活跃和丰富。 ### IDE 与 编辑器的对比 IDE 和编辑器是有区别的: - **IDE**(Integrated Development Environment,集成开发环境):对代码有较好的智能提示和相互跳转,同时侧重于工程项目,对项目的开发、调试工作有较好的图像化界面的支持,因此比较笨重。比如 Eclipse 的定位就是 IDE。 - **编辑器**:要相对轻量许多,侧重于文本的编辑。比如 Sublime Text 的定位就是编辑器。再比如 Windows 系统自带的「记事本」就是最简单的编辑器。 需要注意的是,VS Code 的定位是**编辑器**,而非 IDE ,但 VS Code 又比一般的编辑器的功能要丰富许多。可以这样理解:VS Code 的体量是介于编辑器和 IDE 之间。VS Code 的使命,是让开发者在编辑器里拥有 IDE 那样的开发体验。 VS Code流行起来之后,使用 Sublime Text、Atom 这类编辑器软件的人,自然就越来越少了。 ### VS Code 的特点 - 跨平台:支持 MacOS、Windows 和 Linux 等多个平台。在这多种平台下,拥有一致的用户界面和开发体验。 - 开源:VS Code 的源代码以 MIT 协议开源。不仅代码开源,而且整个产品的开发计划和发布管理也都是开源的。VS Code团队每年都会在 GitHub 的Wiki上发布 [Roadmap](https://github.com/microsoft/vscode/wiki/Roadmap),列出一整年的规划图。VS Code 软件的官方文档也托管在了 [GitHub](https://github.com/Microsoft/vscode-docs) 上。 - 自带终端、图形化的调试工具、Git 版本控制。 - 插件扩展:支持第三方插件,功能强大。既有中心化的插件市场,也可以直接在 VS Code里搜索你想要的插件。 - 生态:社区生态活跃且丰富,社区氛围浓厚。 - 自带 emmet:支持代码自动补全,快速生成简单的语法结构。要知道,这个功能在 Sublime Text中,得先安装插件才行。 - 语法支持:VS Code 自带了 JavaScript、TypeScript 和 Node.js 的**语法支持**,包括:**语法高亮、代码智能提示和补全、括号匹配、颜色区分、代码片段提示**等。也就是说,你在书写 JS 和 TS 时,这些语法支持都是自带的。其他的一些语言,你需要先安装相应的**扩展包**插件,就出现语法支持。 - 在修改配置方面,既有图形化的配置界面,也有 基于 JSON 文件的配置方式,满足不同人群的使用习惯。 ### 前端利器之争: VS Code 与 WebStorm 前端小白最喜欢问的一个问题是:哪个编辑器/IDE 好用?是 VS Code 还是 WebStorm (WebStorm 其实是 IntelliJ IDEA 的定制版)?我来做个对比: - **哪个更酷**:显然 VS Code 更酷。 - **内存占用情况**:根据我的观察,VS Code 是很占内存的(尤其是当你打开多个窗口的时候),但如果你的内存条够用,使用起来是不会有任何卡顿的感觉的。相比之下,IntelliJ IDEA 不仅非常占内存,而且还非常卡顿。如果你想换个既轻量级、又不占内存的编辑器,最好还是使用「Sublime Text」编辑器。 - **使用比例**:当然是 VS Code 更胜一筹。先不说别的,我就拿数据说话,我目前所在的研发团队有 200 人左右(120个后台、80个前端),他们绝大部分人都在用 VS Code 编码,妥妥的。 所以,如果你以后还问这个问题,那就真有些掉底了。 ### VS Code 的技术栈、核心组件 了解 VS Code的技术栈和核心组件,可以让我们对 VS Code 有更深入的认识。此小段,了解即可。 - 开发框架:Electron。Electron可以使用 Node.js + JS这样的技术栈开发桌面GUI应用程序。 - 编辑器:Monaco Editor。Monaco Editor 是一款开源的在线代码编辑器,是 **VS Code 浏览器版本**的最核心组件。[#](https://zhuanlan.zhihu.com/p/88828576) - 编程语言:TypeScript。TypeScript 是 JavaScript的严格超集。TS 在JS的基础上添加了许多功能,引入了声明文件,而且支持类型扩展。TS 适合长期的、多人开发的大型项目开发。 - 让编辑器支持语言功能:Language Server Protocol (LSP) 语言服务协议。LSP是编辑器/IDE 与语言服务器之间的一种协议,通过 JSON-PRC 传输消息,可以让编辑器嵌入并支持各种编程语言。开发者可以在编辑器中使用各种语言来编写程序。 - 让编辑器支持调试功能:Debug Adapter Protocol(DAP)。DAP 是基于 JSON的协议,它抽象了开发工具与调试工具质检的通信。 - 集成终端:Xterm.js。VS Code的集成终端是基于开源项目 [Xterm.js](https://github.com/xtermjs/xterm.js/) 进行开发的。Xterm.js 是一个使用 TS 开发的终端组件。另外,Xterm.js 并不是直接下来下来就能用的终端应用,它只是一个前端组件,可以与 bash这样的进程进行连接,然后让用户通过 Xterm.js 进行交互。 ### VS Code 的安装 - VS Code 官网: VS Code 的安装很简单,直接去官网下载安装包,然后双击安装即可。 ![](http://img.smyhvae.com/20190313_1750_3.png) 上图中,直接点击 download,一键下载安装即可。 VS Code支持以下平台: ![](https://img.smyhvae.com/20210930_1930.png) 安装完成后的界面如下: ![](https://img.smyhvae.com/20211011_1703.png) VS Code被分为以下五个区域: - 编辑器 - 侧边栏 - 状态栏 - 活动栏 - 面板 VS Code在功能上非常克制,只包含了大多数开发流程中所需要的基础模块,包括:编辑器、文件管理、窗口管理、首选项设置、终端等。 你需要根据具体需要安装额外的组件或者插件。比如说,如果开发TS项目,则需要安装 TS编译器,以及ESLint、TSLint等语法规则&代码风格的检查工具。如果开发C语言项目,则需要安装gcc、Clang等编译工具。 ## 二、崭露锋芒:VS Code 快捷键 VS Code 用得熟不熟,首先就看你是否会用快捷键。以下列出的内容,都是常用快捷键,而加粗部分的快捷键,使用频率则非常高。 任何工具,掌握 20%的技能,足矣应对 80% 的工作。既然如此,你可能会问:那就只保留 20% 的特性,不久可以满足 80%的用户了吗? 但我想说的是:**那从来都不是同样的 20%**,每个人都会用到不同的功能。 掌握下面这些高频核心快捷键,你和你的工具,足矣露出锋芒。 ### 1、工作区快捷键 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :--------------------- | :----------------------- | :-------------------------------------------- | :------------------- | | **Cmd + Shift + P** | **Ctrl + Shift + P**,F1 | 显示命令面板 | | | **Cmd + B** | **Ctrl + B** | 显示/隐藏侧边栏 | 很实用 | | `Cmd + \` | `Ctrl + \` | **拆分为多个编辑器** | 【重要】抄代码利器 | | **Cmd + 1、2** | **Ctrl + 1、2** | 聚焦到第 1、第 2 个编辑器 | 同上重要 | | **Cmd + +、Cmd + -** | **ctrl + +、ctrl + -** | 将工作区放大/缩小(包括代码字体、左侧导航栏) | 在投影仪场景经常用到 | | Cmd + J | Ctrl + J | 显示/隐藏控制台 | | | **Cmd + Shift + N** | **Ctrl + Shift + N** | 重新开一个软件的窗口 | 很常用 | | Cmd + Shift + W | Ctrl + Shift + W | 关闭软件的当前窗口 | | | Cmd + N | Ctrl + N | 新建文件 | | | Cmd + W | Ctrl + W | 关闭当前文件 | | ### 2、跳转操作 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :----------------------------------------------------------------- | :--------------------- | :----------------------------------- | :----------------- | | Cmd + ` | 没有 | 在同一个软件的**多个工作区**之间切换 | 使用很频繁 | | **Cmd + Option + 左右方向键** | Ctrl + Pagedown/Pageup | 在已经打开的**多个文件**之间进行切换 | 非常实用 | | Ctrl + Tab | Ctrl + Tab | 在已经打开的多个文件之间进行跳转 | 不如上面的快捷键快 | | Cmd + Shift + O | Ctrl + shift + O | 在当前文件的各种**方法之间**(符号:Symbol)进行跳转 | | | Cmd + T | Ctrl + T | 在当前**工作区**的各种方法之间(符号:Symbol)进行跳转 | | | Ctrl + G | Ctrl + G | 跳转到指定行 | | | `Cmd+Shift+\` | `Ctrl+Shift+\` | 跳转到匹配的括号 | | ### 3、移动光标 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :---------------------------- | :----------------------------------------- | :----------------------------------------------------------- | :------------- | | 方向键 | 方向键 | 在**单个字符**之间移动光标 | 大家都知道 | | **option + 左右方向键** | **Ctrl + 左右方向键** | 在**单词**之间移动光标 | 很常用 | | **Cmd + 左右方向键** | **Fn + 左右方向键**(或 Win + 左右方向键) | 将光标定位到当前行的最左侧、最右侧(在**整行**之间移动光标) | 很常用 | | **Option + Alt + 左右方向键** | **Alt + Shift + 左右方向键** | 左右扩大/缩小选中的范围 | 很酷,极为高效 | | Cmd + ↑ | Ctrl + Home | 将光标定位到文件的第一行 | | | Cmd + ↓ | Ctrl + End | 将光标定位到文件的最后一行 | | | Cmd + Shift + \ | | 在**代码块**之间移动光标 | | ### 4、编辑操作 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :--------------------- | :------------------ | :----------------------------------- | :------------------------------------- | | Cmd + C | Ctrl + C | 复制 | | | Cmd + X | Ctrl + X | 剪切 | | | Cmd + V | Ctrl + V | 粘贴 | | | **Cmd + Enter** | **Ctrl + Enter** | 在当前行的下方新增一行,然后跳至该行 | 即使光标不在行尾,也能快速向下插入一行 | | Cmd+Shift+Enter | Ctrl+Shift+Enter | 在当前行的上方新增一行,然后跳至该行 | 即使光标不在行尾,也能快速向上插入一行 | | **Option + ↑** | **Alt + ↑** | 将代码向上移动 | 很常用 | | **Option + ↓** | **Alt + ↓** | 将代码向下移动 | 很常用 | | Option + Shift + ↑ | Alt + Shift + ↑ | 将代码向上复制一行 | | | **Option + Shift + ↓** | **Alt + Shift + ↓** | 将代码向下复制一行 | 写重复代码的利器 | 另外再补充一点:将光标点击到某一行的任意位置时,默认就已经是**选中全行**了,此时可以直接**复制**或**剪切**,无需点击鼠标。这个非常实用,是所有的编辑操作中,使用得最频繁的。它可以有以下使用场景: - 场景1:假设光标现在处于第5行的**任意位置**,那么,直接依次按下 `Cmd + C` 和 `Cmd + V`,就会把这行代码复制到第6行。继续按 `Cmd + C` 和 `Cmd + V`,就会把这行代码复制到第7行。copy代码so easy。 - 场景2:假设光标现在处于第5行,那么,先按下 `Cmd + C`,然后按两下`↑` 方向键,此时光标处于第3行;紧接着,继续按下`Cmd + V`,就会把刚刚那行代码复制到第3行,原本处于第3行的代码会整体**下移**。 你看到了没?上面的两个场景,我全程没有使用鼠标,只通过简单的复制粘贴和方向键,就做到了如此迅速的copy代码。你说是不是很高效? ### 5、删除操作 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :--------------------- | :------------------- | :--------------------- | :---------------------------------------- | | Cmd + shift + K | Ctrl + Shift + K | 删除整行 | 「Cmd + X」的作用是剪切,但也可以删除整行 | | **option + Backspace** | **Ctrl + Backspace** | 删除光标之前的一个单词 | 英文有效,很常用 | | option + delete | Ctrl + delete | 删除光标之后的一个单词 | | | **Cmd + Backspace** | | 删除光标之前的整行内容 | 很常用 | | Cmd + delete | | 删除光标之后的整行内容 | | 备注:上面所讲到的移动光标、编辑操作、删除操作的快捷键,在其他编辑器里,大部分都适用。 ### 6、多光标选择/多光标编辑 多光标选择在编程的**提效**方面可谓立下了汗马功劳。因为比较难记住,所以你要时不时回来复习这一段。 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | --------------------------------- | ------------------------------ | ------------------------------------------------------------ | ---------------------------------------- | | **Option + 鼠标连续点击任意位置** | **Alt + 鼠标连续点击任意位置** | 在任意位置,同时出现多个光标 | 很容易记住 | | Cmd + D | Ctrl + D | 将光标放在某个单词的位置(或者先选中某个单词),然后反复按下「 **Cmd + D** 」键, 即可将下一个相同的词逐一加入选择。 | 较常用 | | **Cmd + Shift + L** | **Ctrl + Shift + L** | 将光标放在某个单词的位置(或者先选中某个单词),然后按下快捷键,则所有的相同内容处,都会出现光标。 | 很常用。比如变量重命名的时候,就经常用到 | ### 7、多列选择/多列编辑 多列选择是更高效的多光标选择,所以单独列成一小段。 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | ------------------------- | ---------------------- | ------------------------------------------------------------ | -------------------- | | Cmd + Option + 上下键 | Ctrl + Alt + 上下键 | 在连续的多列上,同时出现多个光标 | 较常用 | | Option + Shift + 鼠标拖动 | Alt + Shift + 鼠标拖动 | 按住快捷键,然后把鼠标从区域的左上角拖至右下角,即可在选中区域的每一行末尾,出现光标。 | 很神奇的操作,较常用 | | **Option + Shift + i** | **Alt + Shift + I** | 选中一堆文本后,按下快捷键,既可在**每一行的末尾**都出现一个光标。 | 很常用 | ### 8、编程语言相关 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :--------------------- | :-------------- | :--------------------------- | :------------------------------- | | Cmd + / | Ctrl + / | 添加单行注释 | 很常用 | | **Option + Shift + F** | Alt + shift + F | 代码格式化 | 很常用 | | F2 | F2 | 以重构的方式进行**重命名** | 改代码备 | | Ctrl + J | | 将多行代码合并为一行 | Win 用户可在命令面板搜索”合并行“ | | Cmd + | | | | | Cmd + U | Ctrl + U | 将光标的移动回退到上一个位置 | 撤销光标的移动和选择 | ### 9、搜索相关 | Mac 快捷键 | Win 快捷键 | 作用 | 备注 | | :------------------ | :------------------ | :----------------------------------------- | :----- | | **Cmd + Shift + F** | **Ctrl + Shift +F** | 全局搜索代码 | 很常用 | | **Cmd + P** | **Ctrl + P** | 在当前的项目工程里,**全局**搜索文件名 | | | Cmd + F | Ctrl + F | 在当前文件中搜索代码,光标在搜索框里 | | | **Cmd + G** | **F3** | 在当前文件中搜索代码,光标仍停留在编辑器里 | 很巧妙 | ### 10、自定义快捷键 按住快捷键「Cmd + Shift + P」,弹出命令面板,在命令面板中输入“快捷键”,可以进入快捷键的设置。 当然,你也可以选择菜单栏「偏好设置 --> 键盘快捷方式」,进入快捷键的设置: ![](http://img.smyhvae.com/20190329_2120.png) 此外,如果你输入这个快捷键后没起作用,那有可能是与其他软件(比如 PicGo 软件)的快捷键冲突了,请检查一下。 ### 11、快捷键列表 你可以点击 VS Code 左下角的齿轮按钮,效果如下: ![](http://img.smyhvae.com/20190418_1738.png) 上图中,在展开的菜单中选择「键盘快捷方式」,就可以查看和修改所有的快捷键列表了: ![](http://img.smyhvae.com/20190418_1739_2.png) ### 快捷键参考表(官方) VS Code官网提供了 PDF版本的键盘快捷键参考表,转需: - Windows版本:https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf - Mac 版本:https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf - Linux版本:https://code.visualstudio.com/shortcuts/keyboard-shortcuts-linux.pdf 我们在 VS Code软件里通过菜单栏「帮助-->键盘快捷方式参考」也可以打开相应平台的快捷键大全(PDF版本)。 ## 三、高端访问:命令面板的使用 Mac 用户按住快捷键 `Cmd+Shift+P` (Windows 用户按住快捷键`Ctrl+Shift+P`),可以打开快速命令面板。效果如下: ![](http://img.smyhvae.com/20190329_1750_2.png) 命令面板的作用是**希望解放开发者的鼠标,让一些操作和配置可以直接通过键盘进行**。如果让开发者记住所有的配置项在菜单的哪个位置是不现实的,而且有些命令并不在菜单中。 有了命令面板之后,如果你需要修改一些设置项,或者进行一些快捷操作,则可以通过「命令面板」来操作,效率会更高。接下来列举一些。 ### 1、VS Code 设置为中文语言 Mac 用户按住快捷键 `Cmd+Shift+P` (Windows 用户按住快捷键`Ctrl+Shift+P`),打开命令面板。 在命令面板中,输入`Configure Display Language`,选择`Install additional languages`,然后安装插件`Chinese (Simplified) Language Pack for Visual Studio Code`即可。 或者,我们可以直接安装插件`Chinese (Simplified) Language Pack for Visual Studio Code`,是一样的。 安装完成后,重启 VS Code。 ### 2、设置字体大小 在命令面板输入“字体”,可以进行字体的设置,效果如下: ![](http://img.smyhvae.com/20190329_2110.png) 当然,你也可以在菜单栏,选择「首选项-设置-常用设置」,在这个设置项里修改字体大小。 ### 3、快捷键设置 在命令面板输入“快捷键”,就可以进入快捷键的设置。 ### 4、大小写转换 选中文本后,在命令面板中输入`transfrom`,就可以修改文本的大小写了。 ![](http://img.smyhvae.com/20190414_1751.png) ### 5、使用命令行启动 VS Code (1)输入快捷键「Cmd + Shift + P 」,选择`install code command`: ![](http://img.smyhvae.com/20191103_1327.png) (2)使用命令行: - `code`命令:启动 VS Code 软件 - `code pathName/fileName`命令:通过 VS Code 软件打开指定目录/指定文件。 备注:这种方法快捷简单,但是在电脑重启之后就失效了。稍后在第五段,我会介绍更常见的方法。 ### 6、修改特定编程语言的设置项 输入快捷键「Cmd + Shift + P 」打开命令面板,然后输入并执行 `Configure Language Specific Settings`即可。 ![](https://img.smyhvae.com/20211012_1039.png) ## 四、私人订制:VS Code 的常见配置 ### 0、设置项介绍 在修改 VS Code配置之前,我们需要知道,在哪里可以找到设置项的入口。 **方式1**:Mac用户选择菜单栏「Code--> 首选项-->设置」,即可打开配置项: ![](http://img.smyhvae.com/20210930_2009.png) **方式2**:点击软件右下角的设置图标: ![](http://img.smyhvae.com/20210930_2016.png) ![](https://img.smyhvae.com/20211012_1017.png) 如上图所示,VS Code提供两种不同范围的设置: - **用户**设置:全局生效。 - **工作区**设置:只针对当前项目生效。工作区设置会覆盖用户设置。适用于团队协作场景。工作区的设置文件是保存在当前项目根目录的`.vscode/settings.json`中,可以被提交到Git仓库,方便共享给项目组的其他成员。 操作技巧: (1)我们可以在设置面板的顶部搜索框,输入关键词,就能迅速定位到你想要的设置项。 (2)上图中,点击右上角的icon,可以通过 json文件的形式修改设置项。 ### 1、修改主题 1)修改颜色主题: 选择菜单栏「Code --> 首选项 --> 颜色主题」: ![](http://img.smyhvae.com/20210930_2017.png) 在弹出的对话框中,挑选你一个你喜欢的的颜色主题吧,或者安装其他颜色的主题: ![20211013_1018](http://img.smyhvae.com/20211013_1018.png) 或者在设置项里搜索`Workbench: Color Theme`,进行修改。 2)修改文件图标的主题: 选择菜单栏「Code --> 首选项 --> 文件图标主题」: ![20211013_1015](http://img.smyhvae.com/20211013_1015.png) 在弹出的对话框中,挑选你一个你喜欢的的主题吧,或者安装其他的主题: ![20211013_1019](http://img.smyhvae.com/20211013_1019.png) 或者在设置项里搜索`Workbench: Icon Theme`,进行修改。 ### 2、面包屑(Breadcrumb)导航 打开 VS Code 的设置项,选择「用户设置 -> 工作台 -> 导航路径」,如下图所示: ![](http://img.smyhvae.com/20191108_1550.png) 上图中,将红框部分打钩即可。 设置成功后,我们就可以查看到当前文件的「层级结构」,非常方便。如下图所示: ![](http://img.smyhvae.com/20190415_2009.png) 有了这个面包屑导航,我们可以点击它,在任意目录、任意文件之间随意跳转。使用频繁非常高。 ### 3、是否显示代码的行号 VS Code 默认显示代码的行号。你可以在设置项里搜索 `editor.lineNumbers`修改设置,配置项如下: ![](http://img.smyhvae.com/20190417_2140.png) 我建议保留这个设置项,无需修改。 ### 4、右侧是否显示代码的缩略图 如果某个文件的代码量很大,缩略图就很有用了,可以预览全局,并在当前文件中快速跳转。 VS Code 会在代码的右侧,默认显示缩略图。你可以在设置项里搜索 `editor.minimap` 进行设置,配置项如下: ![](http://img.smyhvae.com/20211012_1507.png) 上面这张图,你仔细琢磨下会发现,中文翻译十分精准。 ### 5、将当前行代码高亮显示(更改光标所在行的背景色) 当我们把光标放在某一行时,这一行的背景色并没有发生变化。如果想**高亮显示**当前行的代码,需要设置两步: (1)在设置项里搜索`editor.renderLineHighlight`,将选项值设置为`all`或者`line`。 (2)在设置项里增加如下内容: ```json "workbench.colorCustomizations": { "editor.lineHighlightBackground": "#00000090", "editor.lineHighlightBorder": "#ffffff30" } ``` 上方代码,第一行代码的意思是:修改光标所在行的背景色(背景色设置为全黑,不透明度 90%);第二行代码的意思是:修改光标所在行的边框色。 ### 6、改完代码后立即自动保存 **方式一**: 改完代码后,默认不会自动保存。你可以在设置项里搜索`files.autoSave`,修改参数值为`afterDelay` ,即可自动保存。如下: ![](https://img.smyhvae.com/20211012_2000.png) files.autoSave的参数值有以下几种: - off(默认值):不自动保存。 - afterDelay(建议配置):文件修改超过一定时间(默认1秒)后,就自动保存。 - onFocusChange:当前编辑器失去焦点时,则自动保存。如果我们将配置项修改为`onFocusChange`之后,那么,当光标离开该文件后,这个文件就会自动保存了。 - onWindowChange:VS Code软件失去焦点时,则自动保存。 **方式二**: 当然,你也可以直接在菜单栏选择「文件-自动保存」。勾选后,当你写完代码后,文件会立即实时保存。 ### 7、热退出 当VS Code退出后,它可以记住未保存的文件。如果你希望达到这种效果,那么,你需要先将设置项`files.hotExit`的值改为 `onExitAndWindowClose`。这个配置项要不要改,看你个人需要。比如我自己平时设置的值是`onExit`。 ![20211012_2014](http://img.smyhvae.com/20211012_2014.png) ### 8、保存代码后,是否立即格式化 保存代码后,默认**不会立即**进行代码的格式化。你可以在设置项里搜索`editor.formatOnSave`查看该配置项: ![](http://img.smyhvae.com/20190417_2213.png) 我觉得这个配置项保持默认就好,不用打钩。 ### 9、自动格式化粘贴的内容 在设置项里搜索 `editor.formatOnPaste`,将设置项改为`true`: ![20211012_1049](https://img.smyhvae.com/20211012_1049.png) ### 10、设置字体大小 在设置项里搜索`fontSize`,然后根据需要设置各种模块的字体大小: ![20211012_1053](http://img.smyhvae.com/20211012_1053.png) ### 11、空格 or 制表符 VS Code 会根据你所打开的文件来决定该使用空格还是制表。也就是说,如果你的项目中使用的都是制表符,那么,当你在写新的代码时,按下 tab 键后,编辑器就会识别成制表符。 (1)建议的设置项如下: - **editor.detectIndentation**:自动检测(默认开启)。建议把这个配置项修改为 false,截图如下: ![20211012_1139](https://img.smyhvae.com/20211012_1139.png) 这样做,是为了取消系统的自动缩进,建议自己手动格式化比较好。 参考链接:https://www.yisu.com/zixun/327399.html - **editor.insertSpaces**:按 Tab 键时插入空格(默认值为true)。截图如下: ![](http://img.smyhvae.com/20190417_2207.png) - **editor.tabSize**:一个制表符默认等于四个空格。截图如下: ![](http://img.smyhvae.com/20190417_2209.png) (2)状态栏也会显示当前的缩进值。点击状态栏,可以直接修改 tabSize 缩进值: ![](http://img.smyhvae.com/20211009_1610.png) (3)另外,我们还可以安装 prettier 插件,设置代码在格式化时默认缩进值。prettier 是做代码格式化的最常见工具。 ![](https://img.smyhvae.com/20211009_1637.png) (4)去掉每一行末尾的空格。在设置项里搜索`空格`或者`"files.trimTrailingWhitespace"`,将值设置为 true: ![20211012_1231](http://img.smyhvae.com/20211012_1231.png) 一般来说,每一行代码末尾的空格是多余的,所以建议去掉。 ### 12、直观地显示代码里的空格和缩进 ✨ 代码里如果有缩进或者空格,肉眼是看不出来的,但是我们可以修改配置项,把它揪出来。 在配置项里搜索`editor.renderWhitespace`,修改为`all`: ![20211012_1150](http://img.smyhvae.com/20211012_1150.png) 修改之后,代码里的空格、缩进的展示效果如下: ![20211012_1258](http://img.smyhvae.com/20211012_1258.png) 看到了没?哪里有空格、哪里是缩进,全都一目了然。 ### 13、新建文件后的默认文件类型 当我们按下快捷键「Cmd + N」新建文件时,VS Code 默认无法识别这个文件到底是什么类型的,因此也就无法识别相应的语法高亮。 如果你想修改默认的文件类型,可以在设置项里搜索`files.defaultLanguage`,设置项如下: ![](http://img.smyhvae.com/20190417_2221.png) 上图中的红框部分,填入你期望的默认文件类型。我填的是`html`类型,你也可以填写成 `javascript` 或者 `markdown`,或者其他的语言类型。 ### 14、删除文件时,是否弹出确认框 当我们在 VS Code 中删除文件时,默认会弹出确认框。如果你想修改设置,可以在设置项里搜索`xplorer.confirmDelete`。截图如下: ![](http://img.smyhvae.com/20190418_1758.png) 我建议这个设置项保持默认的打钩就好,不用修改。删除文件前的弹窗提示,也是为了安全考虑,万一手贱不小心删了呢? ### 15、在新窗口打开文件/文件夹 通过 `window.openFoldersInNewWindow`(默认值为off)和`window.openFilesInNewWindow`(默认值为default),可以配置在打开文件夹、打开文件时,是否开启一个新的窗口。我个人建议,把这两个配置项都设置为 on,避免旧的窗口被覆盖: ![](http://img.smyhvae.com/20211012_1700.png) 补充知识—— `window.restoreWindows`可以用来配置 如何恢复之前的会话窗口。涉及到的场景是:你把 VS Code 关闭了,然后又打开了,是否要展示之前打开过的文件、文件夹?参数值有以下几种: - one(默认配置):只会重新打开上一次回话中最后操作的那一个窗口。 - none:打开一个空的窗口,不包含任何文件、文件夹。 - all(建议配置):恢复上一次会话中的所有窗口。 - folders:恢复上一次会话中包含文件夹的窗口。 ![20211012_1704](http://img.smyhvae.com/20211012_1704.png) ### 16、自动删除行尾的空格 打开设置项,搜索`files.trimTrailingWhitespace`,将选项勾选,即可在保存文件时自动删除行尾的空格。 ### 17、突出显示成对的括号 我们可以用不同颜色显示代码中成对的括号,并用连线标注括号范围。简称**彩虹括号**。 最早是通过`Bracket Pair Colorizer 2`插件支持的,但是这个插件已经被废弃了,因为 VS Code 已经内置了该功能。我们可以通过 VS Code的如下配置项,达到效果: ```json { "editor.bracketPairColorization.enabled": true, "editor.guides.bracketPairs":"active" } ``` ### 18、自动换行 自动换行:意味着当文本到达屏幕或页面的右边缘时,自动换行会将文本移到下一行,以便继续输入或显示。 打开设置项,搜索`Editor:Word Wrap`,将选项值设置为 on。 此外,你还可以选择菜单栏`查看-->自动换行`,即可将当前文件设置为自动换行。 > 接下来,我们来讲一些更高级的操作。 ## 五、纵享丝滑:常见操作和使用技巧 ### 1、快速生成HTML骨架 先新建一个空的html文件,然后通过以下方式,可以快速生成html骨架。 **方式1**:输入`!`,然后按下`enter`键,即可生成html骨架。如下图: ![](https://img.smyhvae.com/20210623-2115.gif) **方式2**:输入`html:5`,然后按住 `Tab`键,即可生成html骨架。 生成的骨架,内容如下: ```html Document ``` 有了上面的html骨架之后,我们就可以快乐地在里面插入CSS 代码和 JS 代码。 ### 2、并排编辑:左右(上下)显示多个编辑器窗口(copy代码利器) > 并排编辑是所有的编辑操作中最常用的一个技巧,十分有用。比如我们在开发一个项目时,可能需要同时打开 HTML 文件和 CSS 文件,很常见。 Mac 用户按住快捷键 `Cmd + \`, Windows 用户按住快捷键`Ctrl + \`,即可同时打开多个编辑器窗口,进行并排编辑。效果如下: ![](http://img.smyhvae.com/20200619_0030.gif) 按快捷键「Cmd + 1 」切换到左边的窗口,按快捷键「Cmd + 2 」切换到右边的窗口,以此类推。随时随地,想切就切。 学会了这一招,以后 copy 代码的时候,leader 再也不用担心我抄得慢了,一天工资到手。 --- 当然,使用快捷键`Cmd + \`只是其中一种方式,我们还有很多种方式打开并行编辑。我们来做一个汇总。 如果你已经打开了一个编辑器,那么可以通过以下几种方式在另一侧打开另一个编辑器:(按照使用频率,从高到低排序) - 使用快捷键`Cmd + \`将编辑器一分为二。 - 使用快捷键`Cmd + P`调出文件列表,选择要打开的文件,然后按下 `Cmd + Enter`快捷键。【重要】 - 按住 Option 键的同时,单击资源管理器的文件(Windows 用户是按 Alt 键)。 - 点击编辑器右上角的 `Split Editor`按钮。 - 选择菜单栏「查看--> 编辑器布局」,然后选择你具体想要的布局,如下图所示: ![20211012_1451](http://img.smyhvae.com/20211012_1451.png) - 通过拖拽,把当前文件移动到任意一侧。 补充知识:通过配置项`worbench.editor.OpenSideBySideDirection`可以控制编辑器在并排打开时出现的默认位置(默认值为right,你也可以根据需要改为 down)。如下图所示: ![20211012_1455](http://img.smyhvae.com/20211012_1455.png) ### 3、从终端 code 命令启动 VS Code(Mac电脑) 在终端输入`code`或者输入 `code + 指定项目的目录`,就可以启动 VS Code,十分便捷。即: - `code` 命令:启动 VS Code 软件。 - `code pathName/fileName` 命令:通过 VS Code 软件打开指定目录/指定文件。 为了达到目的,我们需要先将 VS Code的软件安装路径添加到环境变量,一劳永逸。具体操作如下: (1)打开 `bash_profile`文件: ```bash cd ~ vim ./bash_profile ``` (2)在 bash_profile 中添加如下内容: ```bash # 从终端启动VS Code,并设置vscode启动的命令别名 alias code="/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code" ``` 注意,由于`Visual Studio Code.app`这个路径里有空格,所以需要在空格前面加上反斜杠`\`。 (3)重启环境变量的配置: ``` # 重启 source ~/.bash_profile ``` 大功告成。 改完之后,如果没生效,那你把 `bash_profile`文件 换成 `zshrc`文件试试。 参考链接: - [mac通过终端code 命令打开vscode](https://blog.csdn.net/logan_LG/article/details/106800904) 当然,还可以通过命令面板,一键设置环境变量。具体做法是:输入快捷键「Cmd + shift + P」打开命令面板,然后选择 `shell 命令:从 PATH 中卸载 “code”命令`: ![](https://img.smyhvae.com/202310201605408.png) 完成后就可以在终端输入命令+文件路径来启动 VS Code 了。 ### 3、从终端 code 命令启动 VS Code(Windows电脑) 在终端输入`code`或者输入 `code + 指定项目的目录`,就可以启动 VS Code,十分便捷。即: - `code` 命令:启动 VS Code 软件。 - `code pathName/fileName` 命令:通过 VS Code 软件打开指定目录/指定文件。 为了达到目的,我们需要先将 VS Code的软件安装路径添加到环境变量,一劳永逸。具体操作如下: (1)打开 VS Code 的安装位置,进入bin文件夹,复制路径。比如:`D:\Microsoft VS Code\bin`。 (2)回到桌面,右键我的电脑-->高级系统设置-->环境变量-->编辑path值,在原来的path后面,追加内容`;D:\Microsoft VS Code\bin`(即英文的分号+VS Code 的 bin 路径) (3)重启电脑,大功告成。 改完之后,如果没生效,那八成是因为你填的 path 值有问题。 参考链接: - [windows使用 code . 命令打开vscode](https://www.cnblogs.com/zyl-Tara/p/10642704.html) ### 4、在当前文件中搜索 在上面的快捷键列表中,我们已经知道如下快捷键: - Cmd + F(Win 用户是 Ctrl + F):在当前文件中搜索,光标在搜索框里 - Cmd + G(Win 用户是 F3):在当前文件中搜索,光标仍停留在编辑器里 多个搜索结果出来之后,按下 Enter 键之后跳转到下一个搜索结果,按下 Shift + Enter 键之后跳转到上一个搜索结果。 另外,你可能会注意到,搜索框里有很多按钮,每个按钮都对应着不同的功能,如下图所示: ![](http://img.smyhvae.com/20190415_2052.png) 上图中,你可以通过「Tab」键和「Shift + Tab」键在输入框和替换框之间进行切换。 「在选定内容中查找」这个功能还是比较实用的。你也可以在设置项里搜索 `editor.find.autoFindInSelection`,勾选该设置项后,那么,当你选中指定内容后,然后按住「Cmd + F」,就可以**自动**只在这些内容里进行查找。该设置项如下图所示: ![](http://img.smyhvae.com/20191108_1655.png) ### 5、全局搜索 在上面的快捷键列表中,我们已经知道如下快捷键: - Cmd + Shift + F(Win 用户是 Ctrl + Shift +F):在全局的文件夹中进行搜索。效果如下: ![20211012_1548](http://img.smyhvae.com/20211012_1548.png) 上图中,你可以点击**红框**部分,展开更多的配置项。然后点击**红圈**部分,进行过滤搜索。注意,第二个红圈那里会经常用到,它可以在搜索时过滤掉 `.git`、`.node_modules`等忽略文件。 上图中,我们还可以点击“在编辑器中打开”,在一个单独的文件中聚合展示搜索结果: ![](https://img.smyhvae.com/20211012_1609.png) ### 6、文件名/文件夹的搜索 前面的快捷键那一段我们讲过,通过 「Cmd + P」可以快速搜索并打开**文件**/文件夹。这种方式,一般用于快速打开最近编辑过的文件。 其实还有一种很巧妙的方式,可以在整个项目里,既能搜到文件,也能搜到**文件夹**。这种方式,常用于**过滤项目的目录**。操作方法很简单: > 直接在文件资源管理器输入关键字就行。搜索结果会自动出现;使用方向键进行上下移动,可以在搜索的文件和文件夹之间进行跳转。 > > 另外,右上角会看到一个过滤器,点击下图中的红圈部分,则只显示匹配的文件和文件夹。 ![20211012_1616](http://img.smyhvae.com/20211012_1616.png) 当然,这招也有一点不足:不能搜中文。 ### 7、大纲视图 如下图所示,大纲视图可以展示当前代码的方法结构、文件的目录结构: ![20211012_1628](http://img.smyhvae.com/20211012_1628.png) ![20211012_1636](http://img.smyhvae.com/20211012_1636.png) ### 8、文件对比 VS Code 默认支持**对比两个文件的内容**。选中两个文件,然后右键选择「将已选项进行比较」即可,效果如下: ![](http://img.smyhvae.com/20190329_1756.png) VS Code 自带的对比功能并不够强大,我们可以安装插件`compareit`,进行更丰富的对比。比如说,安装完插件`compareit`之后,我们可以将「当前文件」与「剪切板」里的内容进行对比: ![](http://img.smyhvae.com/20190329_1757.png) 如果你安装了 GitLens 插件,还可以将两个git分支的代码进行比对,非常完美。 ### 9、查找某个函数在哪些地方被调用了 比如我已经在`a.js`文件里调用了 `foo()`函数。那么,如果我想知道`foo()`函数在其他文件中是否也被调用了,该怎么做呢? 做法如下:在 `a.js` 文件里,选中`foo()`函数(或者将光标放置在`foo()`函数上),然后按住快捷键「Shift + F12」,就能看到 `foo()`函数在哪些地方被调用了,比较实用。 ### 10、鼠标操作 - 在当前行的位置,鼠标三击,可以选中当前行。 - 用鼠标单击文件的**行号**,可以选中当前行。 - 在某个**行号**的位置,**上下移动鼠标,可以选中多行**。 ### 11、重构 重构分很多种,我们来举几个例子。 **命名重构**: 当我们尝试去修改某个函数(或者变量名)时,我们可以把光标放在上面,然后按下「F2」键,那么,这个函数(或者变量名)出现的地方都会被修改。 **方法重构**: 选中某一段代码,这个时候,代码的左侧会出现一个「灯泡图标」,点击这个图标,就可以把这段代码提取为一个单独的函数。 ### 12:终端配置 VS Code软件自带了终端,但我个人认为不是很好用,而且VS Code 软件关了之后,终端也没了。建议大家使用其他的终端软件,专业的事情交给专业的人做。 - Windows平台的终端:推荐 PowerShell 软件。远程终端推荐 xshell 软件。 - Mac平台的终端:推荐 [iTerm2 ](https://iterm2.com/)。 iTerm2 是Mac平台最好用的终端软件,没有之一。 **右键行为**: > 在终端上,单击右键所产生的行为在不同的系统里是不同的。 - Windows:如果有**选定**文本,则复制当前文本;如果没有选定文本,则粘贴。 - macOS:选中光标所在位置的单词,并显示右键菜单。 - Linux:显示右键菜单。 ### 13、Git 版本管理 在 VS Code中使用Git之前,需要你先安装 Git 环境。 VS Code 自带了 Git 版本管理的功能,如下图所示: ![](http://img.smyhvae.com/20190418_1850.png) 上图中,我们可以在这里进行常见的 git 命令操作。如果你还不熟悉 **Git 版本管理**,可以先去补补课。 我自己用的最多的功能是**diff 代码**和**合并冲突**,自从用上了 VS Code 的这两个功能,简直离不开它。 我们先来看看 diff 代码的效果: ![20211013_1411](https://img.smyhvae.com/20211013_1411.png) 上图中,点击右上角的`...`,然后点击`内联视图`,则可以换一种视图 diff 代码: ![](https://img.smyhvae.com/20211013_1415.png) **Git状态栏**: ![20211013_1421](http://img.smyhvae.com/20211013_1421.png) 在VS Code的左下角会显示Git状态栏。如果当前代码仓库配置了远程仓库,那么“同步更改”会显示以下信息: - 左边的数字:表示远程分支比本地分支多了XX个 Git commit。 - 右边的数字:表示本地分支比远程分支多了XX个 Git commit。 点击“同步更改”按钮,会拉取(pull)远程分支到本地分支,并推送(push)本地的Git commit到远程分支。 如果当前代码仓库没有配置远程仓库,则会显示“发布更改”的按钮。点击“发布更改”按钮,会把当前分支push到远程仓库。 --- 另外,我建议安装插件`GitLens`搭配使用,它是 VS Code 中我最推荐的一个插件,简直是 Git 神器,码农必备。 我还要补充一句: 有人说,高手都是直接用命令行操作Git。然而,根据我多年的经验来看,如果你的代码仓库需要管理的分支特别多,与团队的其他成员需要经常协作,那么,我建议你**优先使用** GUI 图形化工具来操作Git,避免出错。 我推荐的GUI版的Git工具有: - [Tower](https://www.git-tower.com/) - [Sourcetree](https://www.sourcetreeapp.com/) - [GitKraken](https://www.gitkraken.com/) ### 14、将工作区放大/缩小 我们在上面的设置项里修改字体大小后,仅仅只是修改了代码的字体大小。 如果你想要缩放整个工作区(包括代码的字体、左侧导航栏的字体等),可以按下快捷键「**cmd +/-**」。windows 用户是按下「ctrl +/-」 **当我们在投影仪上给别人演示代码的时候,这一招十分管用**。 如果你想恢复默认的工作区大小,可以在命令面板输入`重置缩放`(英文是`reset zoom`) f### 11、创建多层子文件夹 我们可以在新建文件夹的时候,如果直接输入`aa/bb/cc`,比如: ![](http://img.smyhvae.com/20190418_2022.png) 那么,就可以创建多层子文件夹,效果如下: ![](http://img.smyhvae.com/20190418_2023.png) ### 15、`.vscode` 文件夹的作用 为了统一团队的 vscode 配置,我们可以在项目的根目录下建立`.vscode`目录,在里面放置一些配置内容,比如: - `settings.json`:工作空间设置、代码格式化配置、插件配置。 - `sftp.json`:ftp 文件传输的配置。 `.vscode`目录里的配置只针对当前项目范围内生效。将`.vscode`提交到代码仓库,大家统一配置时,会非常方便。 ### 16、自带终端 我们可以按下「Ctrl + `」打开 VS Code 自带的终端。我认为内置终端并没有那么好用,我更建议你使用第三方的终端 **item2**。 ### 17、markdown 语法支持 VS Code 自带 markdown 语法高亮。也就是说,如果你是用 markdown 格式写文章,则完全可以用 VS Code 进行写作。 写完 md 文件之后,你可以点击右上角的按钮进行预览,如下图所示: ![](http://img.smyhvae.com/20190418_1907.png) 我一般是安装「Markdown Preview Github Styling」插件,以 GitHub 风格预览 Markdown 样式。样式十分简洁美观。 你也可以在控制面板输入`Markdown: 打开预览`,直接全屏预览 markdown 文件。 ### 18、Emmet in VS Code `Emmet`可以极大的提高 html 和 css 的编写效率,它提供了一种非常简练的语法规则。 举个例子,我们在编辑器中输入缩写代码:`ul>li*6` ,然后按下 Tab 键,即可得到如下代码片段: ```html ``` VS Code 默认支持 Emmet。更多 Emmet 语法规则,可以自行查阅。 ### 19、修改字体,使用「Fira Code」字体 这款字体很漂亮,很适合用来写代码: ![](https://img.smyhvae.com/20200516_1633-2.png) 安装步骤如下: (1)进入 网站,下载并安装「Fira Code」字体。 (2)打开 VS Code 的「设置」,搜索`font`,修改相关配置为如下内容: ```json "editor.fontFamily": "'Fira Code',Menlo, Monaco, 'Courier New', monospace", // 设置字体显示 "editor.fontLigatures": false,//控制是否启用字体连字,true启用,false不启用 ``` 上方的第二行配置,取决于个人习惯,我是直接设置为`"editor.fontLigatures": null`,因为我不太习惯连字。 ### 20、代码格式化 VS Code 默认对 JavaScript、TypeScript、JSON、HTML 提供了开箱即用的代码格式化支持。其他语言则需要先安装相应的插件才能支持。 另外,我们还可以安装 Prettier 插件进行**更精细**的代码格式化。下一段将插件的时候,会讲解。 ### 21、智能提示 IntelliSense VS Code 默认对 JavaScript、TypeScript、JSON、HTML、CSS、SCSS、Less这7种语言(文件)提供了**智能提示**的支持。其他编程语言则需要先安装相应的插件才能支持。 在 VS Code插件职场中,下图是最受欢迎的8种[编程语言插件](https://marketplace.visualstudio.com/search?target=VSCode&category=Programming%20Languages&sortBy=Installs): ![20211013_1120](https://img.smyhvae.com/20211013_1120.png) 智能提示的功能很强大, 包括函数介绍、代码自动补全等等。 ### 22、调试与运行 VS Code **内置**了对 Node.js 运行时的调试支持,可以直接调试 JavaScript 和 TypeScript。其他编程语言的调试,则需要先安装相应的插件才能支持。 在 VS Code插件市场中,下图是最受欢迎的几种调试插件: ![](https://img.smyhvae.com/20211013_1650.png) ### 23、文件传输:sftp 如果你需要将本地文件通过 ftp 的形式上传到局域网的服务器(需要先把服务端的配置搭建好),可以安装`sftp`这个插件,很好用。在公司会经常用到。 步骤如下: (1)安装插件`sftp`。 (2)配置 `sftp.json`文件。 插件安装完成后,输入快捷键「cmd+shift+P」弹出命令面板,然后输入`sftp:config`,回车,当前工程的`.vscode`文件夹下就会自动生成一个`sftp.json`文件,我们需要在这个文件里配置的内容可以是: - `host`:服务器的 IP 地址 - `username`:用户名 - `privateKeyPath`:存放在本地的已配置好的用于登录工作站的密钥文件(也可以是 ppk 文件) - `remotePath`:工作站上与本地工程同步的文件夹路径,需要和本地工程文件根目录同名,且在使用 sftp 上传文件之前,要手动在工作站上 mkdir 生成这个根目录 - `ignore`:指定在使用 sftp: sync to remote 的时候忽略的文件及文件夹,注意每一行后面有逗号,最后一行没有逗号 举例如下:(注意,其中的注释需要去掉) ```json { "host": "192.168.xxx.xxx", //服务器ip "port": 22, //端口,sftp模式是22 "username": "", //用户名 "password": "", //密码 "protocol": "sftp", //模式 "agent": null, "privateKeyPath": null, "passphrase": null, "passive": false, "interactiveAuth": false, "remotePath": "/root/node/build/", //服务器上的文件地址 "context": "./server/build", //本地的文件地址 "uploadOnSave": true, //监听保存并上传 "syncMode": "update", "watcher": { //监听外部文件 "files": false, //外部文件的绝对路径 "autoUpload": false, "autoDelete": false }, "ignore": [ //忽略项 "**/.vscode/**", "**/.git/**", "**/.DS_Store" ] } ``` (3)在 VS Code 的当前文件里,选择「右键 -> upload」,就可以将本地的代码上传到 指定的 ftp 服务器上(也就是在上方 `host` 中配置的服务器 ip)。 我们还可以选择「右键 -> Diff with Remote」,就可以将本地的代码和 ftp 服务器上的代码做对比,非常方便。 ### 24、沉浸模式/禅模式 程序员写代码需要专注,有时需要进入一种心流。VS Code给我们提供了一种全屏下的沉浸模式,周围的面板都会被隐藏起来,只显示编辑器部分。 操作方法:菜单栏选择「查看-外观-禅模式」即可;或者按下快捷键`Cmd + K`,放手,再按`Z`也可以达到目的。 ### 25、远程同步 VS Code 配置项 北京时间的[2020年8月14日](https://zhuanlan.zhihu.com/p/184868336),微软发布 Visual Studio Code 1.48 稳定版。此版本**原生**支持用户同步 VS Code的配置,只需要登录微软账号或者 GitHub 账号即可。 有了这个功能之后,我们可以先在老电脑上登录账号,即可将软件的配置项自动开启云同步。当你下次换一个新的电脑,下载安装 VS Code,点击软件左下角的设置按钮,登录此前的微软账号或GitHub账号,即可自动将旧电脑的软件配置项,同步到新电脑的软件上。极其方便。 ### 26、正则表达式批量删除字符串 **需求**:将文本中的字符串`axxxxb`,批量替换为`ab`。其中,开头字符 a 和 结尾字符 b 固定,中间xxx长度不确定。 **解决**:传统查找替换无法胜任。可以使用VScode正则表达式功能,查找`a.*?b`替换为`ab`即可。其中`?`是禁止贪婪匹配,否则会误删很多内容。 --- **拓展需求**:需求——将文本中的字符串`axxxx`,批量替换为`a`。其中,开头字符 a 固定,后面的xxx长度不确定。 **解决**:传统查找替换无法胜任。可以使用VScode正则表达式功能,查找`a.*?\n`替换为`a\n`即可。 ## 六、三头六臂:VS Code 插件介绍 & 插件推荐 VS Code 有一个很强大的功能就是支持插件扩展,让你的编辑器仿佛拥有了三头六臂。 ### 安装插件 ![](http://img.smyhvae.com/20191108_1553_2.png) 上图中,点击红框部分,即可在顶部输入框里,查找你想要的插件名,然后进行安装。 插件安装完成后,记得重启软件(或者点击插件位置的“重新加载”),插件才会生效。 另外,我们还可以访问官网的插件市场来安装插件: - VS Code插件市场(官方):https://marketplace.visualstudio.com/vscode **插件的安装目录**: - Windows::`%USERPROFILE%\.vscode\extensions` - macOS:`~/.vscode/extensions` - macOS:`~/.vscode/extensions` ### 插件的类型 ![20211013_1757_2](http://img.smyhvae.com/20211013_1757_2.png) 插件市场的首页有四个模块,可以作为重要的信息来源: - Featured:由 VS Code团队精心推荐的插件。 - Trending:近期热门插件。 - Most Popular:按总安装量排序的插件。 - Recently Added:最新发布的插件。 ![20211013_1758](http://img.smyhvae.com/20211013_1758.png) ![20211013_1955](http://img.smyhvae.com/20211013_1955.png) 插件市场至少有17种类型的插件:(按照数量排序) - Themes:主题插件 - Programming Languages:编程语言插件 - Snippets:代码片段 - Extension Packs:插件包,里面包括多个插件 - Formatters:代码格式化 - Linters:静态检查 - Debuggers:调试器 - Keymaps:快捷键映射 - Visualization:可视化 - Language Packs:各国的语言插件 - Azure:Azure 云计算 - Data Science:数据科学 - SCM Providers:源代码控制管理器(source control manager) - Notebooks - Education:教育 - Testing:测试相关 - Machine Learning:机器学习 - Others:其他 ### 插件的过滤显示 在 VS Code中打开插件管理视图,可以针对已安装的插件,进行过滤展示。 1)点击插件视图右上角的`...`按钮,可以展示不同状态的插件: ![20211013_2011](http://img.smyhvae.com/20211013_2011.png) 2)在搜索框输入字符`@`,会展示出不同类型的过滤器: ![20211013_2015](http://img.smyhvae.com/20211013_2015.png) **常见的过滤器如下**: 1)按大类搜: - `@builtin`:显示 VS Code内置的插件 - `@disabled`:显示被禁用的插件 - `@enabled`:显示已启用的插件 - `@installed`:显示已安装的插件 - `@outdated`:显示待更新的插件 2)精准搜索: - `@id`:按id显示插件 - `@tag`:根据标签显示插件。 3)对插件进行排序: - `@sort:installs`:根据插件的安装量排序 - `@sourt:rating`:根据插件的评分排序 - `@sort:name`:根据插件名字的字母顺序排序 4)组合搜索:(举例) - `@installed @category:themes`:显示已安装的主题插件。 - `@sort:installs java`:对 Java 相关的插件按照安装量排序。 下面的内容,我来列举一些常见的插件,这些插件都很实用,小伙伴们可以按需安装。注意:每一类插件里,**顺序越靠前,越实用**。 ### 1、基本插件 #### Chinese (Simplified) Language Pack for Visual Studio Code 让软件显示为简体中文语言。 ### 2、Git 相关插件 #### GitLens 【荐】 我强烈建议你安装插件`GitLens`,它是 VS Code 中我最推荐的一个插件,简直是 Git 神器,码农必备。如果你不知道,那真是 out 了。 GitLens 在 Git 管理上有很多强大的功能,比如: - 将光标放置在代码的当前行,可以看到这样代码的提交者是谁,以及提交时间。这一点,是 GitLens 最便捷的功能。 - 查看某个 commit 的代码改动记录 - 查看不同的分支 - 可以将两个 commit 进行代码对比 - 甚至可以将两个 branch 分支进行整体的代码对比。这一点,简直是 GitLens 最强大的功能。当我们在不同分支 review 代码的时候,就可以用到这一招。 打开你的 Git仓库,未安装 GitLens 时是这样的: ![](http://img.smyhvae.com/20211009_1400.png) 安装了 GitLens 之后是这样的: ![](http://img.smyhvae.com/20211009_1430.png) 上图中,红框部分就是 GitLens 的功能,诸君可以自由发挥。 补充一个有意思的趣事:Python插件、Ruby插件、GitLens插件、Vetur插件,这四个插件的开发者先后加入了微软。 #### Git History 有些同学习惯使用编辑器中的 Git 管理工具,而不太喜欢要打开另外一个 Git UI 工具的同学,这一款插件满足你查询所有 Git 记录的需求。 #### Local History 【荐】 维护文件的本地历史记录。代码意外丢失时,有时可以救命。 ![](http://img.smyhvae.com/20200618_2246.png) ### 3、代码智能提示插件 #### Vetur Vue 多功能集成插件,包括:语法高亮,智能提示,emmet,错误提示,格式化,自动补全,debugger。VS Code 官方钦定 Vue 插件,Vue 开发者必备。 #### ES7 React/Redux/GraphQL/React-Native snippets React/Redux/react-router 的语法智能提示。 安装该插件后,在代码中只需要输入`clg`即可自动补全`console.log()`这行代码。 #### JavaScript(ES6) code snippets ES6 语法智能提示,支持快速输入。 #### javascript console utils:快速打印 log 日志【荐】 安装这个插件后,当我们按住快捷键「Cmd + Shift + L」后,即可自动出现日志 `console.log()`。简直是日志党福音。 当我们选中某个变量 `name`,然后按住快捷键「Cmd + Shift + L」,即可自动出现这个变量的日志 `console.log(name)`。 其他的同类插件还有:Turbo Console Log。 不过,生产环境的代码,还是尽量少打日志比较好,避免出现一些异常。 编程有三等境界: - 第三等境界是打日志,这是最简单、便捷的方式,略显低级,一般新手或资深程序员偷懒时会用。 - 第二等境界是断点调试,在前端、Java、PHP、iOS 开发时非常常用,通过断点调试可以很直观地跟踪代码执行逻辑、调用栈、变量等,是非常实用的技巧。 - 第一等境界是测试驱动开发,在写代码之前先写测试。与第二等的断点调试刚好相反,大部分人不是很习惯这种方式,但在国外开发者或者敏捷爱好者看来,这是最高效的开发方式,在保证代码质量、重构等方面非常有帮助,是现代编程开发必不可少的一部分。 #### Code Spell Checker:单词拼写错误检查 这个拼写检查程序的目标是帮助捕获常见的单词拼写错误,可以检测驼峰命名。从此告别 Chinglish. #### Auto Close Tag、Auto Rename Tag 自动闭合配对的标签、自动重命名配对的标签。 ### 4、代码显示增强插件 #### highlight-icemode:选中相同的代码时,让高亮显示更加明显 VSCode 自带的高亮显示,实在是不够显眼。可以用插件支持一下。 所用了这个插件之后,VS Code 自带的高亮就可以关掉了: 在用户设置里添加`"editor.selectionHighlight": false`即可。 参考链接:[vscode 选中后相同内容高亮插件推荐](https://blog.csdn.net/palmer_kai/article/details/79548164) #### vscode-icons vscode-icons 会根据文件的后缀名来显示不同的图标,让你更直观地知道每种文件是什么类型的。 #### indent-rainbow:突出显示代码缩进 `indent-rainbow`插件:突出显示代码缩进。 安装完成后,效果如下图所示: ![](http://img.smyhvae.com/20190418_1958.png) #### TODO Highlight 写代码过程中,突然发现一个 Bug,但是又不想停下来手中的活,以免打断思路,怎么办?按照代码规范,我们一般是在代码中加个 TODO 注释。比如:(注意,一定要写成大写`TODO`,而不是小写的`todo`) ``` //TODO:这里有个bug,我一会儿再收拾你 ``` 或者: ``` //FIXME:我也不知道为啥, but it works only that way. ``` 安装了插件 `TODO Highlight`之后,按住「Cmd + Shift + P」打开命令面板,输入「Todohighlist」,选择相关的命令,我们就可以看到一个 todoList 的清单。 #### Better Comments 为注释添加更醒目、带分类的色彩。 ### 5、代码格式化插件 #### Prettier:代码格式化 Prettier 是一个代码格式化工具,**只关注格式化,但不具备校验功能**。在一个多人协同开发的团队中,统一的代码编写规范非常重要。一套规范可以让我们编写的代码达到一致的风格,提高代码的可读性和统一性。自然维护性也会有所提高,代码的展示也会更加美观。 步骤如下: (1)安装插件 `Prettier`。 (2)在项目的根路径下,新建文件`.prettierrc`,并在文件中添加如下内容: ```json { "printWidth": 150, "tabWidth": 4, "semi": true, "singleQuote": true, "trailingComma": "es5", "tslintIntegration": true, "insertSpaceBeforeFunctionParenthesis": false } ``` 上面的内容,是我自己的配置,你可以参考。更多配置,可见官方文档: (3)Mac用户按快捷键「Option + Shift + F」,Win 用户按快捷键「Alt + shift + F」,即可完成代码的格式化。如果你的VS Code 设置的是自动格式化代码,那么这一步可以忽略。 #### ESLint:代码格式的校验 日常开发中,建议用 Prettier 做**代码格式化**,然后用 eslint 做**格式校验**。很多人把这两个插件的功能弄混了。 一般做法是:格式化建议是由程序员手动触发,格式校验由系统强制校验。通过 Prettier **手动**触发格式化,是为了让用户有感知;通过eslint 做**强制**校验之后,如果代码的格式不符合要求,系统就禁止你提交代码。 #### Beautify 代码格式化工具。 备注:相比之下,Prettier 是当前最流行的代码格式化工具,比 Beautify 用得更多。 #### Paste JSON as Code 此插件可以将剪贴板中的 JSON 字符串转换成工作代码。支持多种语言。 #### JS-CSS-HTML Formatter【荐】 保存文件时,自动格式化 HTML、CSS、JS代码。 ### 6、图片相关插件 #### Polacode-2020:生成代码截图 【荐】 可以把代码片段保存成美观的图片,主题不同,代码的配色方案也不同,也也可以自定义设置图片的边框颜色、大小、阴影。 尤其是在我们做 PPT 分享时需要用到代码片段时,或者需要在网络上优雅地分享代码片段时,这一招很有用。 生成的效果如下: ![](http://img.smyhvae.com/20200619_1403.png) 其他同类插件:`CodeSnap`。我们也可以通过 这个网站生成代码图片 有人可能会说:直接用 QQ 截图不行吗?可以是可以,但不够美观、不够干净。 #### Image Preview 【荐】 图片预览。鼠标移动到图片 url 上的时候,会自动显示图片的预览和图片尺寸。 ### 7、CSS相关插件 #### CSS Peek 增强 HTML 和 CSS 之间的关联,快速查看该元素上的 CSS 样式。 #### Vue CSS Peek CSS Peek 对 Vue 没有支持,该插件提供了对 Vue 文件的支持。 #### Color Info 这个便捷的插件,将为你提供你在 CSS 中使用颜色的相关信息。你只需在颜色上悬停光标,就可以预览色块中色彩模型的(HEX、 RGB、HSL 和 CMYK)相关信息了。 ### 8、Mardown 相关插件 #### Markdown Preview Github Styling 【荐】 以 GitHub 风格预览 Markdown 样式,十分简洁优雅。就像下面这样,左侧书写 Markdown 文本,右侧预览 Markdown 的渲染效果: ![](http://img.smyhvae.com/20200618_2025.png) #### Markdown Preview Enhanced 预览 Markdown 样式。 #### Markdown All in One 这个插件将帮助你更高效地在 Markdown 中编写文档。 ### 9、通用工具类插件 #### sftp:文件传输 【荐】 如果你需要将本地文件通过 ftp 的形式上传到局域网的服务器,可以安装`sftp`这个插件,很好用。在公司会经常用到。 详细配置已经在上面讲过。 #### Live Server 【荐】 在本地启动一个服务器,代码写完后可以实现「热更新」,实时地在网页中看到运行效果。就不需要每次都得手动刷新页面了。 使用方式:安装插件后,开始写代码;代码写完后,右键选择「Open with Live Server」。 #### open in browser 安装`open in browser`插件后,在 HTML 文件中「右键选择 --> Open in Default Browser」,即可在浏览器中预览网页。 #### Project Manager 工作中,我们经常会来回切换多个项目,每次都要找到对应项目的目录再打开,比较麻烦。Project Manager 插件可以解决这样的烦恼,它提供了专门的视图来展示你的项目,我们可以把常用的项目保存在这里,需要时一键切换,十分方便。 #### WakaTime 【荐】 统计在 VS Code 里写代码的时间。统计效果如下: ![](http://img.smyhvae.com/20200618_2300.png) #### Code Time `Code Time`插件:记录编程时间,统计代码行数。 安装该插件后,VS Code 底部的状态栏右下角可以看到时间统计。点击那个位置之后,选择「Code Time Dashboard」,即可查看统计结果。 备注:团长试了一下这个 code time 插件,发现统计结果不是很准。 #### File Tree to Text Generator:快速生成文件的目录树 如题。 #### minapp:小程序支持 小程序开发必备插件。 #### Search node_modules `node_modules`模块里面的文件夹和模块实在是太多了,根本不好找。好在安装 `Search node_modules` 这个插件后,输入快捷键「Cmd + Shift + P」,然后输入 `node_modules`,在弹出的选项中选择 `Search node_modules`,即可搜索 node_modules 里的模块。 ![](http://img.smyhvae.com/20200618_2100.png) #### RemoteHub 不要惊讶,RemoteHub 和 GitLens 是同一个作者开发出来的。 `RemoteHub`插件的作用是:可以在本地查看 GitHub 网站上的代码,而不需要将代码下载到本地。 ![](http://img.smyhvae.com/20190418_1937.png) 这个插件目前使用的人还不多,赶紧安装起来尝尝鲜吧。 #### Live Share:实时编码分享 `Live Share`这个神奇的插件是由微软官方出品,它的作用是:**实时编码分享**。也就是说,它可以实现你和你的同伴一起写代码。这绝对就是**结对编程**的神器啊。 安装方式: 打开插件管理,搜索“live share”,安装。安装后重启 VS Code,在左侧会多出一个按钮: ![](http://img.smyhvae.com/20190418_2012.png) 上图中,点击红框部分,登录后就可以分享你的工作空间了。 ![](http://img.smyhvae.com/20190418_2005.png) #### Import Cost 在项目开发过程中,我们会引入很多 npm 包,有时候可能只用到了某个包里的一个方法,却引入了整个包,导致代码体积增大很多。`Import Cost`插件可以在代码中友好的提示我们,当前引入的包会增加多少体积,这很有助于帮我们优化代码的体积。 ### 10、主题插件 给你的 VS Code 换个皮肤吧,免费的那种。 - Dracula Theme - Material Theme - Nebula Theme - [One Dark Pro](https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme) - One Monokai Theme - Monokai Pro - Ayu - [Snazzy Plus](https://marketplace.visualstudio.com/items?itemName=akarlsten.vscode-snazzy-akarlsten) - [Dainty](https://marketplace.visualstudio.com/items?itemName=alexanderte.dainty-vscode) - `SynthWave '84` - GitHub Plus Theme:白色主题 - Horizon Theme:红色主题 ## 七、无缝切换:VS Code 配置云同步 我们可以将配置云同步,这样的话,当我们换个电脑时,即可将配置一键同步到本地,就不需要重新安装插件了,也不需要重新配置软件。 下面讲的两个同步方法,都可以,看你自己需要。方法1是 VS Code自带的同步功能,操作简单。方法2 需要安装插件,支持更多的自定义配置。 ### 方法1:使用 VS Code 自带的同步功能 1、**配置同步**: (1)在菜单栏选择「 Code --> 首选项 --> 打开设置同步」: ![](https://img.smyhvae.com/20211008_1713.png) (2)选择需要同步的配置: ![](http://img.smyhvae.com/20211008_1716.png) (3)通过Microsoft或者GitHub账号登录。 上图中,点击“登录并打开”,然后弹出如下界面: ![](http://img.smyhvae.com/20211008_1717.png) 上图中,使用 微软账号或者 GitHub账号登录: ![](https://img.smyhvae.com/20211008_1718.png) (4)同步完成后,菜单栏会显示“首先项同步已打开”,最左侧也会多出一个同步图标,如下图所示: ![](https://img.smyhvae.com/20211008_1720.png) 2、**管理同步**: (1)点击菜单栏「Code --> 首选项 --> 设置同步已打开」,会弹出如下界面,进行相应的同步管理即可: ![](https://img.smyhvae.com/20211008_1736.png) (2)换另外一个电脑时,登录相同的账号,即可完成同步。 参考链接: - [VS Code原生的配置同步功能——Settings Sync](https://blog.csdn.net/baidu_33340703/article/details/106967884) ### 方法2:使用插件 `settings-sync` 使用方法2,我们还可以把配置分享其他用户,也可以把其他用户的配置给自己用。 1、**配置同步**:(将自己本地的配置云同步到 GitHub) (1)安装插件 `settings-sync`。 (2)安装完插件后,在插件里使用 GitHub 账号登录。 (3)登录后在 vscode 的界面中,可以选择一个别人的 gist;也可以忽略掉,然后创建一个属于自己的 gist。 (4)使用快捷键 「Command + Shift + P」,在弹出的命令框中输入 sync,并选择「更新/上传配置」,这样就可以把最新的配置上传到 GitHub。 2、**管理同步**:(换另外一个电脑时,从云端同步配置到本地) (1)当我们换另外一台电脑时,可以先在 VS Code 中安装 `settings-sync` 插件。 (2)安装完插件后,在插件里使用 GitHub 账号登录。 (3)登录之后,插件的界面上,会自动出现之前的同步记录: ![](http://img.smyhvae.com/20200521_1530.png) 上图中,我们点击最新的那条记录,就可将云端的最新配置同步到本地: ![](http://img.smyhvae.com/20200521_1550.png) 如果你远程的配置没有成功同步到本地,那可能是网络的问题,此时,可以使用快捷键 「Command + Shift + P」,在弹出的命令框中输入 sync,并选择「下载配置」,多试几次。 **使用其他人的配置**: 如果我们想使用别人的配置,首先需要对方提供给你 gist。具体步骤如下: (1)安装插件 `settings-sync`。 (2)使用快捷键 「Command + Shift + P」,在弹出的命令框中输入 sync,并选择「下载配置」 (3)在弹出的界面中,选择「Download Public Gist」,然后输入别人分享给你的 gist。注意,这一步不需要登录 GitHub 账号。 ## 最后一段 如果你还有什么推荐的 VS Code 插件,欢迎留言。 大家完全不用担心这篇文章会过时,随着 VS Code 的版本更新和插件更新,本文也会随之更新。关于 VS Code 内容的后续更新,你可以关注我在 GitHub 上的前端入门项目,项目地址是: > https://github.com/qianguyihao/Web 一个超级详细和真诚的前端入门项目。 ## todo - [issues 84](https://github.com/qianguyihao/Web/issues/84) ## 参考链接 ### 2021年 - 中文版 Awesome VS Code:https://github.com/formulahendry/awesome-vscode-cn ### 2020年 - [VSCode 插件大全| VSCode 高级玩家之第二篇](https://juejin.im/post/5ea40c6751882573b219777d) - - [如何让 VS Code 更好用 10 倍?这里有一份 VS Code 新手指南](https://zhuanlan.zhihu.com/p/99462672) - [那些你应该考虑卸载的 VSCode 扩展](https://lyreal666.com/%E9%82%A3%E4%BA%9B%E4%BD%A0%E5%BA%94%E8%AF%A5%E8%80%83%E8%99%91%E5%8D%B8%E8%BD%BD%E7%9A%84-VSCode-%E6%89%A9%E5%B1%95/#more) - [VS Code 折腾记 - (16) 推荐一波实用的插件集](https://juejin.im/post/5d74eb5c51882525017787d9) - [VSCode 前端必备插件,有可能你装了却不知道如何使用?](https://juejin.im/post/5db66672f265da4d0e009aad) - [能让你开发效率翻倍的 VSCode 插件配置(上)](https://juejin.im/post/5a08d1d6f265da430f31950e) - [https://segmentfault.com/a/1190000012811886](https://segmentfault.com/a/1190000012811886) - [「Vscode」打造类 sublime 的高颜值编辑器](https://idoubi.cc/2019/07/08/vscode-sublime-theme/) - [Mac Vscode 快捷键](https://lsqy.tech/2020/03/14/20200314Mac-Vscode%E5%BF%AB%E6%8D%B7%E9%94%AE/) - [使用 VSCode 的一些技巧](https://mp.weixin.qq.com/s?src=11×tamp=1591581536&ver=2387&signature=i4xLZlLe1Gkl7OiBIhPO*VSeNB5lzFgTY-dgNW9E9ZbtIAv4bnJ1RdAAZdhvDw*cg-DmMcUa-V8NSUdV-tthmXZCq3ht4edCweq6v0QxKjnh8IuAxyyh5qymdRui*8iE&new=1) --- 本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 00-前端工具/02-Git的使用.md ================================================ --- title: 02-Git的使用 --- ## 常见操作 ### 全局配置用户信息 ``` git config --global user.name "smyhvae" git config --global user.email "smyhvae@163.com" ``` ## 分支的合并 ### 场景:基于master分支的代码,开发一个新的特性 如果你直接在master分支上开发这个新特性,是不好的,万一你在开发`特性1`的时候,领导突然又要叫你去开发`特性2`,就不好处理了。难道开发的两个特性都提交到master?一会儿提交特性1的commit,一会儿提交特性2的commit?这会导致commit记录很混乱。 所以,我给你的建议做法是:给每个特性都单独建一个的新的分支。 比如说,我专门给`特性1`建一个分支`feature_item_recommend`。具体做法如下: (1)基于master分支,创建一个新的分支,起名为`feature_item_recommend`: ``` $ git checkout -b feature_item_recommend Switched to a new branch 'feature_item_recommend' ``` 上面这行命令,相当于: ```bash $ git branch feature_item_recommend // 创建新的分支 $ git checkout feature_item_recommend //切换到新的分支 ``` (2)在新的分支`feature_item_recommend`上,完成开发工作,并 commit 、push。 (3)将分支`feature_item_recommend`上的开发进度**合并**到master分支: ```bash $ git checkout master //切换到master分支 $ git merge feature_item_recommend //将分支 feature_item_recommend 的开发进度合并到 master 分支 ``` 合并之后,`master`分支和`feature_item_recommend`分支会指向同一个位置。 (3)删除分支`feature_item_recommend`: > 既然 特性1 开发完了,也放心地提交到master了,那我们就可以将这个分支删除了。 ``` git branch -d feature_item_recommend ``` 注意,我们当前是处于`master`分支的位置,来删除`feature_item_recommend`分支。如果当前是处于`feature_item_recommend`分支,是没办法删除它自己的。 同理,当我转身去开发`特性2`的时候,也是采用同样的步骤。 ### 合并分支时,如果存在分叉 ![](http://img.smyhvae.com/20180610_1650.png) 比如说上面这张图中,最早的时候,master分支是位于`C2`节点。我基于`C2`节点,new出一个新的分支`iss53`,我在`iss53`上提交了好几个commit。 现在,我准备把`iss53`上的几个commit合并到master上,此时发现,master分支已经前进到C4了。那该怎么合并呢? 合并的命令仍然是: ```bash $ git checkout master $ git merge iss53 ``` **解释**: 这次合并的实现,并不同于简单的并入方式。这一次,我的开发历史是从更早的地方开始分叉的。 由于当前 master 分支所指向的commit (C4)并非想要并入分支(iss53)的直接祖先,Git 不得不进行一些处理。就此例而言,Git 会用两个分支的末端(C4 和C5)和它们的共同祖先(C2)进行一次简单的三方合并计算。 Git 没有简单地把分支指针右移,而是对三方合并的结果作一新的快照,并自动创建一个指向它的commit(C6)(如下图所示)。我们把这个特殊的commit 称作合并提交(mergecommit),因为它的祖先不止一个。 值得一提的是Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和CVS 或Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让Git 的合并操作比其他系统都要简单不少。 ![](http://img.smyhvae.com/20180610_1710.png) ### 解决合并时发生的冲突 ![](http://img.smyhvae.com/20180610_1740.png) 如果 feature1和feature2修改的是同一个文件中**代码的同一个位置**,那么,把feature1合并到feature2时,就会产生冲突。这个冲突需要人工解决。步骤如下: (1)手动修改文件:手动修改冲突的那个文件,决定到底要用哪个分支的代码。 (2)git add:解决好冲突后,输入`git status`,会提示`Unmerged paths`。这个时候,输入`git add`即可,表示:**修改冲突成功,加入暂存区**。 (3)git commit 提交。 然后,我们可以继续把 feature1 分支合并到 master分支,最后删除feature1、feature2。 **注意**:两个分支的同一个文件的不同地方合并时,git会自动合并,不会产生冲突。 比如分支feture1对index.html原来的第二行之前加入了一段代码。 分支feature2对index.html在原来的最后一行的后面加入了一段代码。 这个时候在对两个分支合并,git不会产生冲突,因为两个分支是修改同一文件的不同位置。 git自动合并成功。不管是git自动合并成功,还是在人工解决冲突下合并成功,提交之前,都要对代码进行测试。 ## 日常操作积累 ### 修改密码(曲线救国) > 网上查了很久,没找到答案。最终,在cld童鞋的提示下,采取如下方式进行曲线救国。 ```bash # 设置当前仓库的用户名为空 git config user.name "" ``` 然后,当我们再输入`git pull`等命令行时,就会被要求重新输入*新的*账号密码。此时,密码就可以修改成功了。最后,我们还要输入如下命令,还原当前仓库的用户名: ``` git config user.name "smyhvae" ``` ### 修改已经push的某次commit的作者信息 已经push的记录,如果要修改作者信息的话,只能 通过--force命令。我反正是查了很久,但最终还是不敢用公司的仓库尝试。 参考链接: - [git 修改已提交的某一次的邮箱和用户信息](https://segmentfault.com/q/1010000006999861) 看最后一条答案。 - [修改 git repo 历史提交的 author](http://baurine.github.io/2015/08/22/git_update_author.html) ### 将 `branch1`的某个`commit1`合并到`branch2`当中 切换到branch2中,然后执行如下命令: ``` git cherry-pick commit1 ``` ### 20190118-修改GitHub已提交的用户名和邮箱 参考链接:(亲测有效) - [修改Git全部Commit提交记录的用户名Name和邮箱Email](https://cloud.tencent.com/developer/article/1352623) - [Mac 运行sh文件,也就是传说中的shell脚本](https://blog.csdn.net/yusufolu9/article/details/53706269) 在执行`./email.sh`后,如果出现`permission denied`的错误,可以先执行`chmod 777 email.sh`,修改文件的权限。 ### 20200520-将Git 项目迁移到另一个仓库 我们假设旧仓库的项目名称叫`old-repository`,新仓库的项目名称叫`new-repository`。操作如下: (1)创建旧仓库的裸克隆: ```bash git clone --bare https://github.com/exampleuser/old-repository.git ``` 执行上述命令后,会在本地生成一个名叫 `old-repository.git`的文件夹。 (2)迁移到新仓库: ```bash cd old-repository.git git push --mirror https://github.com/exampleuser/new-repository.git ``` 这样的话,项目就已经迁移到新仓库了。 注意,我们**不需要**手动新建一个空的新仓库,当我们执行上述命令之后,新仓库就已经自动创建好了。 参考链接: - [复制仓库](https://help.github.com/cn/github/creating-cloning-and-archiving-repositories/duplicating-a-repository) - [Git 本地仓库和裸仓库](https://moelove.info/2016/12/04/Git-%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E5%92%8C%E8%A3%B8%E4%BB%93%E5%BA%93/) ### 2021-11-10-提交代码时,绕过 eslint 检查 需求:提交代码时,绕过 eslint 检查 解决办法:用命令行提交,末尾追加`--no-verify`。例如: ```bash # 提交代码 git commit -m '千古壹号的commit备注' --no-verify # 推送到远程时,也可以追加 --no-verify,以免远程仓库做了 eslint 限制。 git push origin --no-verify ``` ### 2021-12-29-切换仓库的源地址 查看源地址: ``` git remote -v ``` 切换源地址: ```bash git remote set-url origin https://xxx.git ``` ## git客户端推荐 市面上的Git客户端我基本都用过了,我最推荐的一款Git客户端是:[Tower](https://www.git-tower.com) 或者 [Fork](https://git-fork.com)。 - GitUp: 20180623时,网上看了下Git客户端的推荐排名: ![](http://img.smyhvae.com/20180623_1210.png) **SmartGit**: 商业用途收费, 个人用户免费: ![](http://img.smyhvae.com/20180623_1305.png) ## 推荐书籍 - 《pro.git中文版》 ## 推荐连接 ### 2018-06 - [聊下git pull --rebase](https://www.cnblogs.com/wangiqngpei557/p/6056624.html) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 00-前端工具/03-网络抓包和代理工具:Whistle.md ================================================ --- title: 03-网络抓包和代理工具:Whistle --- ## Whistle 官网 - Whistle 官网: - Whistle 的 GitHub: ## Whistle 安装启动 ### 1、Whistle 安装 (1)通过 npm 安装 Whistle ### 2、启动 whistle ```bash w2 start ``` 然后在浏览器输入`http://127.0.0.1:8899/` 即可打开代理配置的页面。 ### 3、配置代理 **chrome浏览器配置代理**: 可参考官方文档。 **Firefox浏览器配置代理**: ![](https://img.smyhvae.com/20200420_1357.png) ### 4、安装证书并添加信任: ![](https://img.smyhvae.com/20200420_0922.png) 证书下载后,双击安装,安装目录选择“登录”这个tab。安装完成后,记得执行 `w2 restart`重启 whistle。 ### 手机设置代理 连接好指定的wifi后,点击那个wifi里的设置,将「代理」那一项,设置为手动,然后输入ip(电脑上的ip)、端口号(8899)。然后就可以通过电脑上的whistle工具,查看手机的网页请求。 注意,要保证手机和电脑在同一个网络下。 另外,还需要在手机的浏览器,地址栏输入`rootca.pro`,给手机安装证书。 ## 捕获和拦截https请求 whistle安装证书后,可以拦截 https 请求。但是,我现在又不想拦截https请求了,该怎么卸载证书呢? 我发现,证书无法卸载,正确的操作是: ![](http://img.smyhvae.com/20180426_1621.png) 上图中,把红框部分,去掉勾选,就不捕获https了。谢谢azh童鞋。 参考链接: - [Android 手机如何设置http代理?](https://www.zhihu.com/question/21474174) - [使用 Whistle 对 iOS HTTPS 进行抓包](http://zhuscat.com/2017/09/20/https-proxy-on-ios/) ## 移动端调试神器:eruda > 手机连接代理时,如何看console.log的日志信息? > 现在,代码里有console.log,如果是在电脑浏览器上看,可以直接在控制台查看console.log的内容。但是,如果手机连接代理,在手机上打开网页的话,要怎么查看console.log的内容呢?具体做法如下: (1)在 whistle中,新建一个名叫`Eruda H5`的代理,代理中的内容是: ``` http://xxx.com htmlAppend://{eruda.html} ``` (2)新建一个values,里面的内容是: ```html ``` 然后就OK了。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 00-前端工具/04-解决 Git 不区分大小写导致的文件冲突问题.md ================================================ --- title: 04-解决 Git 不区分大小写导致的文件冲突问题 --- ## 解决 Git 不区分大小写导致的文件冲突问题 - 文章发布时间:2022-02-17 - 作者:@千古壹号 有些同学在 Git 仓库对文件/文件夹进行命名时,刚开始是小写,后来为了保持团队一致,又改成了大写,然而 Git 不会发现大小写的变化,此时就出了问题:导致仓库里出现了 大小写 同时存在的两个文件。但在 Windows、Mac 的电脑磁盘里,肉眼却能只看到一个文件,实在奇葩。 这个问题的根本原因是,Windows、Mac 的**文件系统**不区分大小写。 Linux的文件系统是区分大小写的。Git 默认是不区分大小写的,也可以通过改配置项,改为区分大小写。 ### 问题复现路径 (1)新建一个 test 文件(大小写不敏感的状态下),并提交。 (2)本地修改 test 变为 Test,文件内容无变更,无法提交。 (3)执行 `git config core.ignorecase false`,设 置Git的规则为 区分大小写(大小写敏感),然后 git push 提交,查看结果,此时远程仓库会存在 大小写 同时存在的文件,但本地仓库却只看到其中一个文件。 (4)甚至可能出现这种异常情况:本地暂存区的文件,怎么删也删不掉。再之后,从 test 尝试改为 Test 时,提示命名冲突。 ### 错误的解决办法 ```bash git mv test Test ``` 执行上面的命令时,会报错:`fatal: renaming 'Test' failed: Invalid argument` ### 正确的解决办法 ```bash # 将本地的 test、Test 目录都删掉,并生成一个新的目录 Temp git mv Test Temp # 将 Temp 目录改成 Test 目录。此时,项目中只会存在 Test 目录,不会存在 test 目录。目标达成。 git mv Temp Test ``` 执行完上面的两个命令之后,项目中只会存在 Test 目录,不会存在 test 目录。目标达成。 ### 关于 是否区分大小写 的补充说明 我们知道:针对文件/文件夹,Windows 系统和 Mac 系统是不区分大小写的;Linux 系统是区分大小写的;Git 默认是不区分大小写的,也可以通过改配置项,改为区分大小写。 不分区大小写,也有它的好处,比如:文件夹/文件的路径,很多时候就代表了网站地址、页面url的路径。而**网站地址也是不区分大小写的**,这是很关键的原因之一。 总的来说,根本原因是文件系统、url在底层设计上不区分大小写。磁盘路径、页面地址,本质上都是 url 。 ### 关于 Git是否区分大小写 的补充 前面讲到,Git 默认是不区分大小写的,可以通过命令`git config --get core.ignorecase`查到,默认值是 true。 我们也可以修改 Git 的这一配置项,改为区分大小写,配置命令为`git config core.ignorecase false`。 但我建议你**保留 Git 的默认配置项**,不要随意自行修改,避免产生其他的麻烦。 ### 参考链接 - [Mac 中 git 大小写问题的解决方案](https://shanyue.tech/bug/mac-git-ignorecase.html) - [git 大小写问题 踩坑笔记](https://blog.csdn.net/u013707249/article/details/79135639) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 00-前端工具/Atom在前端的使用.md ================================================ ## 常用插件 - `Emmet`:快速生成HTML片段,比如输入ul>li*3可以快速生成: ```html
``` [详细地址](https://atom.io/packages/emmet),[Emmet教程](https://docs.emmet.io/cheat-sheet/) - `Snippets`:快速生成 js 代码片段(用来处理代码片段的模板输出),[详细地址](https://atom.io/packages/snippets) - `Tree View`:文件浏览器,[详细地址](https://atom.io/packages/tree-view) - `file icons`:文件识别图标,使用这个插件会让你的编辑器显示对应的图标,[详细地址](https://atom.io/packages/file-icons) - `language-javascript-jsx`:jsx语法高亮 ,[详细地址](https://atom.io/packages/language-javascript-jsx) - `language-vue`:vue语法高亮,[详细地址](https://atom.io/packages/language-vue) - `linter-eslint`:eslint插件,[详细地址](https://atom.io/packages/linter-eslint) - `vue-snippets`:vue代码片段,[详细地址](https://atom.io/packages/vue-snippets) - `pigments`:颜色显示器,[详细地址](https://atom.io/packages/pigments) - `linter-eslint`:语法检查 - `Atom-Beautify`:代码格式化 参考链接: ## 插件无法安装的问题 ### 方法一:设置代理 `C:\Users\smyhvae\.atom\.apm`目录下的.apmrc配置文件(没有就新建一个),然后添加代理信息: ``` strict-ssl=false https-proxy=http://127.0.0.1:1080/ http-proxy =http://127.0.0.1:1080/ ``` 这里的 http://127.0.0.1:1080,是我自己的 Shadowsocks 代理,你需要设置成自己的可用代理。 然后再执行: ``` apm install --check ``` 应该可以测试成功,祝好运~~ 参考链接: - - ## Markdown相关 ### 在编辑器中预览 2018-06-JD日记.md Packages -> Markdown Preview -> Toggle Preview 快捷键:Shift + Ctrl + M ### 参考链接: - [使用Atom打造无懈可击的Markdown编辑器](http://www.cnblogs.com/fanzhidongyzby/p/6637084.html) ## 相关设置 ### 显示缩进线 settings -->Editor --> Show Indent Guide ================================================ FILE: 00-前端工具/Emmet in VS Code.md ================================================ ## 前言 `Emmet`可以极大的提高 html 和 css 的编写效率,它提供了一种非常简练的语法规则。 举个例子,我们在编辑器中输入缩写代码:`ul>li*6` ,然后按下 Tab 键,即可得到如下代码片段: ```html
``` ## 如何在某个语言中打开 Emmet 支持 默认情况下,你可以直接在 html、haml、jade、slim、jsx、xml、xsl、css、scss、sass、less、stylus、handlebars、php 和 javascriptreact 中使用 Emmet 。 但对于其他语言,你也可以通过如下设置将其打开: ```json "emmet.includeLanguages": { "javascript": "javascriptreact", "vue-html": "html", "razor": "html", "plaintext": "jade" } ``` 上方代码的意思是,将某个 Emmet 默认不支持的语言,映射到某个 Emmet 支持的语言上。比如上面的设置里,我们把 vue-html 映射成了 html,那么当你在 vue-html 使用 Emmet 时,Emmet 就会把它当作 html 来处理了。 ## Emmet 语法规则 语法规则: ``` ul>li*6 ``` 最终效果: ```html
``` --- 语法规则: ``` p5 ``` 最终效果: ``` padding: 5px; ``` ================================================ FILE: 00-前端工具/GitHub的使用.md ================================================ ## GitHub的使用 ### GitHub添加wiki 参考链接: - ### GitHub项目添加 license 参考链接: - ### GitHub 引用图片的另一种方式 参考链接: - [关于markdown文件插入图片遇到的小问题和解决办法](https://www.cnblogs.com/cxint/p/7200164.html) ================================================ FILE: 00-前端工具/Mac安装和配置iTerm2.md ================================================ ## 参考链接 - [iTerm2 + Oh My Zsh 打造舒适终端体验](https://github.com/sirius1024/iterm2-with-oh-my-zsh) - [安装oh my zsh失败:curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused](https://blog.csdn.net/huangpin815/article/details/105606135) - ================================================ FILE: 00-前端工具/Sublime Text在前端中的使用.md ================================================ ### 新建文件时快速生成Html **安装如下插件:** - FileHeader:自动创建文件开头模板,并且会根据最后的保存时间修改更新时间。[官网链接](https://github.com/shiyanhui/FileHeader)。 - CSS Format:css格式化。 - Emmet:它能够让你在编辑器中书写CSS和HTML的缩写并且动态地拓展它,是一个能大幅度提高前端开发效率的一个工具。这个软件的安装过程比较久。[官网教程](http://docs.emmet.io/)。 **开始使用:** 新建文件,输入`html:5`,按[Ctrl + E] 或者 Tab 键, 参考链接:[Sublime Text 新建文件快速生成Html【头部信息】和【代码补全】、【汉化】](http://www.jianshu.com/p/f44e91bf9dfb) ## 常用插件 ### SublimeCodeIntel:JavaScript代码自动提示(不好用) 安装完成后,通过路径Perferences->Package Settings->SublimeCodeIntel->Setting - Defalut打开配置文件。 将 ``` "codeintel_selected_catalogs": ["jQuery"] ``` 改为: ``` "codeintel_selected_catalogs": ["JavaScript"] ``` 保存后重启软件。 参考链接:[#](http://blog.csdn.net/hehexiaoxia/article/details/54134756) ### javascript complet:JavaScript代码自动提示 - [官网链接](https://packagecontrol.io/packages/JavaScript%20Completions) - [GitHub链接](https://github.com/pichillilorenzo/JavaScript-Completions) 在google上搜关键字"sublime javascript complete"找到的。另外还搜到一个[SublimeAllAutocomp lete](https://github.com/alienhard/SublimeAllAutocomplete) ### JsFormat:JS代码格式化 快捷键是:选中JS代码,然后按ctrl+alt+f。 或者,先用快捷键打开命令面板 “ctrl + shift + p”, 再输入 “Format: Javascript” 就可以使用格式化命令 ### Sublime Server 我们如果右键在浏览器中打开html网页,其实是以**文件路径**的方式打开的,导致很多api无法操作。最好的办法是:将html在服务器上打开。 我们如果安装 `Sublime Server`,就可以让网页在本地的服务器上打开。 安装成功之后,使用步骤如下: (1)选择菜单栏"Tools --> SublimeServer --> Start SublimeServer"启动服务器。 (2)在html文档里,右键选择 "View in SublimeServer"。 如果想关闭服务器,直接把Sublime Text 整个软件关掉就好。其他的关闭方式容易导致软件卡死。 ## 代码快速生成 (1)`>`符号的技巧: 输入`ul>li*9`,然后按tab键,生成的代码如下;(符号`>`是包含的关系) ```html
  • a
  • a
  • a
  • a
  • a
  • a
  • a
  • a
  • a
  • ``` ## sublime text 快捷键 | Win快捷键 | Mac快捷键 |作用 | 备注 | |:-------------|:-------------|:-----|:-----| |html:5+tab||html结构代码|| |tab||补全标签代码|| |tab|补全标签代码| |比如,在html文件中输入`div`,然后 按住tab键后,会自动生成`
    `。|| | **Ctrl + Shift + D** | Cmd + Shift + D|复制当前行到下一行 | | | Ctrl+Shift+K || 快速删除整行 | | |Ctrl+鼠标左键单击||集体输入|| |Ctrl+H|Option+Cmd+F|查找替换||| | Ctrl+/ || 注释单行 | | | Ctrl+Shift+/ || 注释多行 | | |Ctrl+L| | 快速选中整行,继续操作则继续选择下一行,效果和 `Shift + ↓` 效果一样| | |**Ctrl+Shift+L**| | 先选中多行,再按下快捷键,会在每行行尾插入光标,即可同时编辑这些行| 经常与上一个快捷键结合起来使用 | |**Ctrl + Shift +【↑/↓**| Ctrl + Cmd +↑/↓ | 移动当前行 | | |F11||全屏|| ## 推荐阅读 - [Sublime Text使用技巧](https://github.com/smyhvae/tools/blob/master/01-%E4%B8%AA%E4%BA%BA%E6%95%B4%E7%90%86/Sublime%20Text%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7.md) 我自己整理的。 ## 参考链接 - [像 Sublime Text 一样使用 Chrome DevTools](https://chinagdg.org/2015/12/%E5%83%8F-sublime-text-%E4%B8%80%E6%A0%B7%E4%BD%BF%E7%94%A8-chrome-devtools/) ================================================ FILE: 00-前端工具/VS Code的使用积累.md ================================================ ## 常见配置 **自动保存**: ``` "files.autoSave": "onFocusChange" ``` 参考链接: **在新的窗口中打开文件**: ```json "workbench.editor.enablePreview": false, ``` ## 常见操作 ### 如何查看代码结构 - 方法一:「Cmd + Shift + O」 - 方法二:安装插件`Code Outline` 参考链接: ### 在本地开启服务器 ```bash # 安装 npm install -g live-server # 启动 live-server ``` 参考链接:[Visual Studio Code + live-server编辑和浏览HTML网页](http://www.cnblogs.com/1zhk/p/5699379.html) ## 常用插件 方式一:打开VS Code,左侧有五个按钮,点击最下方的按钮,然后就可以开始安装相应的插件了。 方式二:在vscode中输入快捷键「ctrl+shift+P」,弹出指令窗口,输入`extension:install`,回车,左侧即打开扩展安装的界面。 ### sftp:文件传输 输入快捷键「ctrl+shift+P」,弹出指令窗口,输入`sftp:config`,回车,当前工作工程的`.vscode`文件夹下就会自动生成一个`sftp.json`文件,我们需要在这个文件里配置的是: - `host`:服务器的IP地址 - `username`:工作站自己的用户名 - `privateKeyPath`:存放在本地的已配置好的用于登录工作站的密钥文件(也可以是ppk文件) - `remotePath`:工作站上与本地工程同步的文件夹路径,需要和本地工程文件根目录同名,且在使用sftp上传文件之前,要手动在工作站上mkdir生成这个根目录 - `ignore`:指定在使用sftp: sync to remote的时候忽略的文件及文件夹,注意每一行后面有逗号,最后一行没有逗号 举例如下:(注意,其中的注释不能保留) ```json { "host": "", //服务器ip "port": 22, //端口,sftp模式是22 "username": "", //用户名 "password": "", //密码 "protocol": "sftp", //模式 "agent": null, "privateKeyPath": null, "passphrase": null, "passive": false, "interactiveAuth": false, "remotePath": "/root/node/build/", //服务器上的文件地址 "context": "./server/build", //本地的文件地址 "uploadOnSave": true, //监听保存并上传 "syncMode": "update", "watcher": { //监听外部文件 "files": false, //外部文件的绝对路径 "autoUpload": false, "autoDelete": false }, "ignore": [ //忽略项 "**/.vscode/**", "**/.git/**", "**/.DS_Store" ] } ``` ### Sass Formatter Sass 文件格式化。 ### Code Outline:显示代码结构 安装好插件「Code Outline」后,可以在左侧的资源管理器中,显示当前文件的代码结构: ![](http://img.smyhvae.com/20180420_0925.png) 参考链接: - - - ### vscode-fileheader:添加顶部注释模板(签名) (1)安装插件vscode -fileheader,并重启。 (2)在首选项-》设置-》中搜索fileheader,找到头部模板修改。 默认的快捷键是:「Ctrl + option + I」。 参考链接: - ### Express 在本地开启Node服务器: ![](http://img.smyhvae.com/20180611_2230.png) 然后在浏览器的地址栏输入`http://localhost/` + 文件的相对路径,就可以通过服务器的形式打开这个文件。 ### Copy Relative Path > 这个插件很有用,使用频率很高。 复制文件的相对路径:(相对于根路径而言) ![](http://img.smyhvae.com/20180611_2235.png) ### open in browser 在浏览器中打开。 ### Auto Rename Tag 适用于 JSX、Vue、HTML。在修改标签名时,能在你修改开始(结束)标签的时候修改对应的结束(开始)标签,帮你减少 50% 的击键。 ###Project Manager 项目管理,让我们方便的在命令面板中切换项目文件夹,当然,你也可以直接打开包含多个项目的父级文件夹,但这样可能会让 VSCode 变慢。 ### highlight-icemode:选中相同的代码时,让高亮显示更加明显【荐】 VSCode自带的高亮显示,实在是不够显眼。用插件支持一下吧。 所用了这个插件之后,VS Code自带的高亮就可以关掉了: 在用户设置里添加`"editor.selectionHighlight": false`即可。 参考链接:[vscode 选中后相同内容高亮插件推荐](https://blog.csdn.net/palmer_kai/article/details/79548164) ### highlight-words:全局高亮(跨文件多色彩) 参考链接:[Visual Studio Code全局高亮着色插件(跨文件多色彩)经验纪要](https://zhuanlan.zhihu.com/p/31163793) ### color-exchange:颜色格式转换【荐】 安装完插件后,在css中输入颜色,然后按`cmd + .`,就能进行颜色的格式转换。 20181013_1450.png 我在写这一段时,安装的人还不多,赶紧上车。 ## Vue 相关的插件 ### vetur:vue 文件的基本语法高亮 安装完 vetur 后还需要加上这样一段配置下: ``` "emmet.syntaxProfiles": { "vue-html": "html", "vue": "html" } ``` 参考链接: - - ### 参考链接 - - ## 常用快捷键 | Win快捷键 |Mac快捷键| 作用 | 备注 | |:-------------|:-------------|:-----|:-----| | Shift + Alt + F |Shift + option + F| 代码格式化 | | | Ctrl + Shift + N | |在当前行上面增加一行并跳至该行 | | | **Ctrl + Shift + D** | |复制当前行到下一行 | | ### 如何同时打开多个窗口 ## 问题 问题1 解决;You can kill the Microsoft.VSCode.Cpp.IntelliSense.Msvc process to save the file successfully. 也就是 IntelliSense 这个进程。 ## 参考链接 - [能让你开发效率翻倍的 VSCode 插件配置(上)](https://zhuanlan.zhihu.com/p/30976584) ### 某网友的VS Code 插件截图 ![](http://img.smyhvae.com/20180611_2255.png) ================================================ FILE: 00-前端工具/WebStorm的使用.md ================================================ ## WebStorm的简单设置 #### 1、主题修改: 可能大家会觉得软件的界面不太好看,我们可以换一下主题。选择菜单栏“File--settings--appearance--theme”,主题选择 Dracula: ![](http://img.smyhvae.com/20180118_1600.png) #### 2、导入第三方主题: 系统提供的两种主题可能都不太好看,我们可以进入网站来获取第三方主题,这里推荐两个主题,大家二选一即可: - [Sublime](https://github.com/y3sh/Intellij-Colors-Sublime-Monokai) - [Material](https://github.com/ChrisRM/material-theme-jetbrains) ![](http://img.smyhvae.com/20180118_1636.png) ![](http://img.smyhvae.com/20180118_1637.png) 上图中,在网站中将主题下载之后,是一个jar包。那怎么导入到WebStorm呢? 别着急,回到WebStorm,选择菜单栏“ File-Import Settings”,将下载好的jar包导入即可。 #### 3、代码字体修改: 选择菜单栏“File--settings--Editor--Font”: ![](http://img.smyhvae.com/20180118_1627.png) 上图中,点击红框部分,然后弹出如下界面: ![](http://img.smyhvae.com/20180118_1628.png) 我们在上图中修改代码的字体。 修改完之后发现 WebStorm 的一些默认字体(比如侧边栏的工程目录的字体)并没有发生变化,如果想改的话,也可以改(我个人一般是不改的)。 #### 4、关闭更新: 如下图所示: ![](http://img.smyhvae.com/20180118_1646.png) #### 5、快捷键习惯的修改: #### 7、配置代码的自动提示: #### 14、修改文件编码为UTF-8: WebStorm 2017.3.3版本的默认编码方式是 GBK,我们还是统一设置为UTF-8吧,不要坑队友哦: ![](http://img.smyhvae.com/20180124_1856.png) ### 新建一个空的项目 配置完成后,可以开始新建一个项目文件夹(站点),项目通常包含如下内容: - 首页:index.html - 样式:css文件夹 - index.css - 相同样式:全局样式、公共样式。起名为:base.css(基本样式)或者 global.css (全局样式) - 图片:images文件夹、文件 - 特效:js文件夹、js文件 **步骤如下:** (1)新建一个空的项目: ![](http://img.smyhvae.com/20180118_1720.png) (2)然后新建一个html文件: ![](http://img.smyhvae.com/20180118_1602.png) (3)新建一个空的文件夹,命名为`css`: ![](http://img.smyhvae.com/20180118_1725.png) 然后在这个css文件夹中,新建样式表:(比如index.css\base.css) ![](http://img.smyhvae.com/20180118_1730.png) (4)最后新建一个images文件夹,用于存放土片。这样的话,一个基本的项目结构就搭建好了: ![](http://img.smyhvae.com/20180118_1733.png) 接下来,开始运用起你们的前端知识吧。 (5)如果要新建JS文件的话,操作如下: ![](http://img.smyhvae.com/20180124_1859.png) ## 使用技巧 #### 多光标编辑 我们可以按住鼠标不松手,选中多个光标,然后同时编辑: #### 随时在浏览器中看代码效果 20180118_1658.png 如上图所示,我们可以点击右上角的浏览器图标,在各个浏览器中看效果。 #### 实时查看颜色 写代码时如果想输入颜色,会自动提示颜色的预览。 ![](http://img.smyhvae.com/20180118_1702.png) 点击最左侧的颜色预览,还能弹出调色板: ![](http://img.smyhvae.com/20180118_1710.gif) ## 代码的自动补齐 (1)在html文档中,输入`div*10`,按tab键后,弹出的效果如下: ```html
    ``` (2)在html文档中,输入如下部分: ``` .search-logo+.search-input+.search-car+.search-moreA ``` 按tab键后,弹出的效果如下: ```html
    ``` 你看,京东的搜索框就包含了这几个div: 20180122_1045.png (3)方法的注释: 方法写完之后(注意,一定要先写完整),我们在方法的前面输入`/**`,然后回车,会发现,注释的格式会自动补齐。 比如: ```javascript /** * 功能:给定元素查找他的第一个元素子节点,并返回 * @param ele * @returns {Element|*|Node} */ function getFirstNode(ele){ var node = ele.firstElementChild || ele.firstChild; return node; } ``` ## 常用快捷键 #### 标签环绕 输入一段字符后,按住`Ctrl + Alt + T`,可以用标签将这段字符环绕: ![](http://img.smyhvae.com/20180118_1719.gif) #### 选中正行中的文本 比如下面这行: ``` text-align: center; /*让 li 里面的文本水平方向居中*/ ``` 如果直接按 【ctrl+C】的话,复制的是整行的内容,把前面的空格也包含进去了。如果不想复制空格,有另外一个办法:将光标放在行尾,然后按住【shift+home】,就能选中你想要的内容了。 ================================================ FILE: 00-前端工具/chrome浏览器.md ================================================ ## 控制台的使用 ### 控制台查看源码 控制台的`Sources`标签可以查看源码。按住快捷键「cmd + P」,可以根据文件名查找源码文件。 ## 其他 ### show user agent shadow DOM ![](http://img.smyhvae.com/20180206_1610.png) ![](http://img.smyhvae.com/20180206_1616.png) 把上图中的红框部分打钩。 ================================================ FILE: 00-前端工具/iconMoon.md ================================================ iconMoon.md - - - ================================================ FILE: 01-HTML/01-认识Web和Web标准.md ================================================ --- title: 01-认识Web和Web标准 publish: true --- ## Web、网页、浏览器 ### Web Web(World Wide Web)即全球广域网,也称为万维网。 我们常说的`Web端`就是网页端。 ### 网页 **网页是构成网站的基本元素**。网页主要由文字、图像和超链接等元素构成。当然,除了这些元素,网页中还可以包含音频、视频以及Flash等。 我们在浏览器上输入网址后,打开的任何一个页面,都是属于网页。 ### 浏览器 浏览器是网页运行的平台,常见的浏览器有谷歌(Chrome)、Safari、火狐(Firefox)、IE、Edge、Opera等。 关于浏览器的详细介绍,可以看下一篇文章:《浏览器的介绍》。 ## Web标准 ### W3C组织 **W3C**:World Wide Web Consortium,万维网联盟组织,用来制定web标准的机构(组织)。 W3C 万维网联盟是国际最著名的标准化组织。1994年成立后,至今已发布近百项相关万维网的标准,对万维网发展做出了杰出的贡献。 W3C 组织就类似于现实世界中的联合国。 为什么要遵循WEB标准呢?因为很多浏览器的浏览器内核不同,导致页面解析出来的效果可能会有差异,给开发者增加无谓的工作量。因此需要指定统一的标准。 ### Web 标准 **Web标准**:制作网页要遵循的规范。 Web标准不是某一个标准,而是由W3C组织和其他标准化组织制定的一系列标准的集合。 **1、Web标准包括三个方面**: - 结构标准(HTML):用于对网页元素进行整理和分类。 - 表现标准(CSS):用于设置网页元素的版式、颜色、大小等外观样式。 - 行为标准(JS):用于定义网页的交互和行为。 根据上面的Web标准,可以将 Web前端分为三层,如下。 **2、Web前端分三层**: - HTML(HyperText Markup Language):超文本标记语言。从**语义**的角度描述页面的**结构**。相当于人的身体组织结构。 - CSS(Cascading Style Sheets):层叠样式表。从**审美**的角度美化页面的**样式**。相当于人的衣服和打扮。 - JavaScript(简称JS):从**交互**的角度描述页面的**行为**,实现业务逻辑和页面控制。相当于人的动作,让人有生命力。 **3、打个比方**:(拿黄渤举例) HTML 相当于人的身体组织结构: ![](http://img.smyhvae.com/20200322_1250.png) CSS 相当于人的衣服和打扮: ![](http://img.smyhvae.com/20200322_1251.png) JS 相当于人的行为: ![](http://img.smyhvae.com/20200322_2220.gif) --- 本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。 ![](https://img.smyhvae.com/20210329_1930.png) ================================================ FILE: 01-HTML/02-浏览器的介绍.md ================================================ --- title: 02-浏览器的介绍 publish: true --- ## 常见的浏览器 浏览器是网页运行的平台,常见的浏览器有谷歌(Chrome)、Safari、火狐(Firefox)、IE、Edge、Opera等。如下图所示: ![](http://img.smyhvae.com/20191204_1900.png) 我们重点需要学习的是 Chrome 浏览器。 ## 浏览器的市场占有份额 浏览器的市场占有份额: ![](http://img.smyhvae.com/20200322_1058.png) 上面这张图的统计时间是2020年2月。 ## 浏览器的组成 浏览器分成两部分: - 1、渲染引擎(即:浏览器内核) - 2、JS 引擎 ### 1、渲染引擎(浏览器内核) 浏览器所采用的「渲染引擎」也称之为「浏览器内核」,用于解析 HTML和CSS、布局、渲染等工作。渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。 **渲染引擎是浏览器兼容性问题出现的根本原因。** 渲染引擎的英文叫做 Rendering Engine。通俗来说,它的作用就是:读取网页内容,计算网页的显示方式并显示在页面上。 常见浏览器的内核如下: |浏览器 | 内核| |:-------------:|:-------------:| | chrome | Blink | | 欧鹏 | Blink | |360安全浏览器| Blink| |360极速浏览器| Blink| |Safari|Webkit| |Firefox 火狐|Gecko| |IE| Trident | 备注:360的浏览器,以前使用的IE浏览器的Trident内核,但是现在已经改为使用 chrome 浏览器的 Blink内核。 另外,移动端的浏览器内核是什么?大家可以自行查阅资料。 ### 2、JS 引擎 也称为 JS 解释器。 用来解析和执行网页中的JavaScript代码。 浏览器本身并不会执行JS代码,而是通过内置 JavaScript 引擎(解释器) 来执行 JS 代码 。JS 引擎执行代码时会逐行解释每一句源码,转换为机器语言,然后由计算机去执行。 常见浏览器的 JS 引擎如下: |浏览器 | JS 引擎| |:-------------:|:-------------| |chrome、欧鹏 | V8 | |Mozilla Firefox 火狐|SpiderMonkey(1.0-3.0)/ TraceMonkey(3.5-3.6)/ JaegerMonkey(4.0-)| |Safari|JavaScriptCore,也称为Nitro,是 WebKit 引擎的一部分| |IE|Trident | |Edge|Chakra。此外,ChakraCore是Chakra的开源版本,可以在不同的平台上使用。 | |Opera|Linear A(4.0-6.1)/ Linear B(7.0-9.2)/ Futhark(9.5-10.2)/ Carakan(10.5-)| 补充说明: 1、SpiderMonkey 是第一款 JavaScript 引擎,由 JS语言的作者 Brendan Eich 开发。 2、先以WebKit为例,WebKit上由两部分组成: - WebCore:负责解析HTML和CSS、布局、渲染等工作。 - JavaScriptCore:负责解析和执行JavaScript 代码。 参考链接: - [主流浏览器内核及JS引擎](https://juejin.im/post/5ada727c518825670b33a584) ## 浏览器工作原理 > 这一小段有些深入,小白可以暂时跳过,以后学习JS的时候再回来看。 浏览器主要由下面这个七个部分组成: ![](http://img.smyhvae.com/20180124_1700.png) 1、User Interface(UI界面):包括地址栏、前进/后退按钮、书签菜单等。也就是浏览器主窗口之外的其他部分。 2、Browser engine (浏览器引擎):用来查询和操作渲染引擎。是UI界面和渲染引擎之间的**桥梁**。 3、Rendering engine(渲染引擎):用于解析HTML和CSS,并将解析后的内容显示在浏览器上。 4、Networking (网络模块):用于发送网络请求。 5、JavaScript Interpreter(JavaScript解析器):用于解析和执行 JavaScript 代码。 6、UI Backend(UI后端):用于绘制组合框、弹窗等窗口小组件。它会调用操作系统的UI方法。 7、Data Persistence(数据存储模块):比如数据存储 cookie、HTML5中的localStorage、sessionStorage。 参考链接:(关于浏览器的工作管理,下面这篇文章,是精品中的精品,是必须要知道的) - 英文版:[How Browsers Work: Behind the scenes of modern web browsers](https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/) - 中文版:[浏览器的工作原理:新式网络浏览器幕后揭秘](https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/) --- 本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。 ![](https://img.smyhvae.com/20210329_1930.png) ================================================ FILE: 01-HTML/03-初识HTML.md ================================================ --- title: 03-初识HTML publish: true --- ## 编辑器相关 前端开发的编辑器软件,我首先推荐 VS Code,其次推荐Sublime Text。 有人说 WebStorm 也不错?但真实情况是,自从VS Code 问世之后,用 WebStorm 的人越来越少了。 PS:文件的后缀名不能决定文件格式,只能决定打开文件打开的方式。 ### VS Code 的使用 详情请移步至:[第一次使用VS Code时你应该知道的一切配置](https://github.com/qianguyihao/Web/blob/master/00-%E5%89%8D%E7%AB%AF%E5%B7%A5%E5%85%B7/01-VS%20Code%E7%9A%84%E4%BD%BF%E7%94%A8.md) ### Sublime Text 的使用 详情请移步至:[Sublime Text使用技巧](https://github.com/qianguyihao/Mac/blob/master/03-%E5%85%A8%E5%B9%B3%E5%8F%B0%E8%BD%AF%E4%BB%B6/Sublime%20Text%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7.md) ## HTML的概述 ### HTML的概念 **HTML** 全称为 HyperText Markup Language,译为**超文本标记语言**。 HTML 不是一种编程语言,是一种描述性的**标记语言**。 **作用**:HTML是负责描述文档**语义**的语言。 ### 概念:超文本 所谓的超文本,有两层含义: (1)图片、音频、视频、动画、多媒体等内容,被称为超文本,因为它们超出了文本的限制。 (2)不仅如此,它还可以从一个文件跳转到另一个文件,与世界各地主机的文件进行连接。即:超级链接文本。 ### 概念:标记语言 HTML 不是一种编程语言,是一种描述性的**标记语言**。这主要有两层含义: (1)**标记语言是一套标记标签**。比如:标签``表示超链接、标签``表示图片、标签`

    `表示一级标题等等,它们都是属于 HTML 标签。 说的通俗一点就是:网页是由网页元素组成的,这些元素是由 HTML 标签描述出来,然后通过浏览器解析,就可以显示给用户看了。 (2)编程语言是有编译过程的,而标记语言没有编译过程,HTML标签是直接由浏览器解析执行。 ### HTML是负责描述文档语义的语言 HTML 格式的文件是一个纯本文文件(就是用txt文件改名而成),用一些标签来描述语义,这些标签在浏览器页面上是无法直观看到的,所以称之为“超文本标记语言”。 接下来,我们需要学习 HTML 中的很多“标签对儿”,这些“标签对儿”能够给文本不同的语义。 比如,面试的时候问你,`

    ` 标签有什么作用? - 正确答案:给文本增加主标题的语义。 - 错误答案:给文字加粗、加黑、变大。 关乎“语义”的更深刻理解,等接下来我们学习了各种标签,就明白了。 ## HTML的历史 ![html中标签发展趋势](http://img.smyhvae.com/20151001_1001.png) 其中,我们专门来对XHTML做一个介绍。 **XHTML介绍:** XHTML:Extensible Hypertext Markup Language,可扩展超文本标注语言。 XHTML的主要目的是为了**取代HTML**,也可以理解为HTML的升级版。 HTML的标记书写很不规范,会造成其它的设备(ipad、手机、电视等)无法正常显示。 XHTML与HTML4.0的标记基本上一样。 XHTML是**严格的、纯净的**HTML。 我们稍后将对XHTML的编写规范进行介绍。 ## HTML的专有名词 - 网页 :由各种标记组成的一个页面就叫网页。 - 主页(首页) : 一个网站的起始页面或者导航页面。 - 标记: 比如`

    `称为开始标记 ,`

    `称为结束标记,也叫标签。每个标签都规定好了特殊的含义。 - 元素:比如`

    内容

    `称为元素. - 属性:给每一个标签所做的辅助信息。 - XHTML:符合XML语法标准的HTML。 - DHTML:dynamic,动态的。`javascript + css + html`合起来的页面就是一个 DHTML。 - HTTP:超文本传输协议。用来规定客户端浏览器和服务端交互时数据的一个格式。SMTP:邮件传输协议,FTP:文件传输协议。 ## 书写第一个 HTML 页面 我们打开 VS Code 软件,新建一个文件,名叫`test.html`(注意,文件名是`test`,后缀名是`html`),保存到本地。 紧接着,在文件里,输入`html:5`,然后按一下键盘上的`Tab`键,就可以自动生成如下内容: ```html Document ``` 上面的内容,就是 html 页面的骨架。我们在此基础之上,新增几个标签,完整代码如下: ```html Document

    我是三级标题

    我是超链接,可以点击一下 ``` 标签写完之后,我们用 chrome 浏览器打开上面这个`test.html`文件,看看页面效果: 到此,第一个简单的 HTML 页面就写完了。是不是很有成就感? ## HTML结构详解 HTML标签通常是成对出现的(**双边标记**),比如 `
    ` 和 `
    `;也有少部分单标签(**单边标记**),如:`
    `、`
    `和``等。 属性与标记之间、各属性之间需要以空格隔开。属性值以双引号括起来。 #### html骨架标签分类 | 标签名 | 定义 | 说明 | | ---------------- | :----: | :----------------------------- | | `` | HTML标签 | 页面中最大的标签,我们成为根标签 | | `` | 文档的头部 | 注意在head标签中我们必须要设置的标签是title | | `` | 文档的标题 | 让页面拥有一个属于自己的网页标题 | | `` | 文档的主体 | 元素包含文档的所有内容,页面内容 基本都是放到body里面的 | ### 快速生成 html 的骨架 **方式1**:在 VS Code 中新建 html 文件,输入`html:5`,按 `Tab`键后,自动生成的代码如下: ```html Document ``` **方式2**:在Sublime Text中安装`Emmet`插件。新建html文件,输入`html:5`,按`Tab`键后,自动生成的代码如下: ```html Document ``` **方式3**:在Sublime Text中安装`Emmet`插件。新建html文件,输入`html:xt`,按`Tab`键后,自动生成的代码如下: ```html Document ``` 上面的方式2和方式3中,我们会发现,第一行的内容有些不太一样,这就是我们接下来要讲的**文档声明头**。 ### 1、文档声明头 任何一个标准的HTML页面,第一行一定是一个以``开头的语句。这一行,就是文档声明头,即 DocType Declaration,简称DTD。 **DTD可告知浏览器文档使用哪种 HTML 或 XHTML 规范**。 #### HTML4.01有哪些规范呢? **HTML4.01**这个版本是IE6开始兼容的。**HTML5是IE9开始兼容的**。如今,手机、移动端的网页,就可以使用HTML5了,因为其兼容性更高。 说个题外话,html1 至 html3 是美国军方以及高等研究所用的,并未对外公开。 HTML4.01里面有两大种规范,每大种规范里面又各有3种小规范。所以一共6种规范(见下图)。 HTML4.01里面规定了**普通**和**XHTML**两大种规范。HTML觉得自己有一些规定不严谨,比如,标签是否可以用大写字母呢?`

    `所以,HTML就觉得,把一些规范严格的标准,又制定了一个XHTML1.0。在XHTML中的字母X,表示“严格的”。 总结一下,HTML4.01一共有6种DTD。说白了,HTML的第一行语句一共有6种情况: ![](http://img.smyhvae.com/20170629_1600.png) 下面对上图中的三种小规范进行解释: **strict**: 表示“严格的”,这种模式里面的要求更为严格。这种严格体现在哪里?有一些标签不能使用。 比如,u标签,就是给一个本文加下划线,但是这和HTML的本质有冲突,因为HTML最好是只负责语义,不要负责样式,而u这个下划线是样式。所以,在strict中是不能使用u标签的。 那怎么给文本增加下划线呢?今后将使用css属性来解决。 XHTML1.0更为严格,因为这个体系本身规定比如标签必须是小写字母、必须严格闭合标签、必须使用引号引起属性等等。 **Transitional**:表示“普通的”,这种模式就是没有一些别的规范。 **Frameset**:表示“框架”,在框架的页面使用。 在sublime输入的html:xt,x表示XHTML,t表示transitional。 在HTML5中极大的简化了DTD,也就是说HTML5中就没有XHTML了。HTML5的DTD(文档声明头)如下: ``` ``` ### 2、页面语言 `lang` 下面这行标签,用于指定页面的语言类型: ```html ``` 最常见的语言类型有两种: - en:定义页面语言为英语。 - zh-CN:定义页面语言为中文。 ### 3、头标签 `head` #### html5 的比较完整的骨架: ```html Document ``` 面试题: - 问:网页的head标签里面,表示的是页面的配置,有什么配置? - 答:字符集、关键词、页面描述、页面标题、IE适配、视口、iPhone小图标等等。 头标签内部的常见标签如下: - ``:指定整个网页的标题,在浏览器最上方显示。 - `<base>`:为页面上的所有链接规定默认地址或默认目标。 - `<meta>`:提供有关页面的基本信息 - `<body>`:用于定义HTML文档所要显示的内容,也称为主体标签。我们所写的代码必须放在此标签內。 - `<link>`:定义文档与外部资源的关系。 **meta 标签**: meta表示“元”。“元”配置,就是表示基本的配置项目。 常见的几种 meta 标签如下: (1)字符集 charset: ```html <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> ``` 字符集用meta标签中的`charset`定义,charset就是character set(即“字符集”),即**网页的编码方式**。 **字符集**(Character set)是多个字符的集合。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。 上面这行代码非常关键, 是必须要写的代码,否则可能导致乱码。比如你保存的时候,meta写的和声明的不匹配,那么浏览器就是乱码。 utf-8是目前最常用的字符集编码方式,常用的字符集编码方式还有gbk和gb2312等。关于“编码方式”,我们在下一段会详细介绍。 (2)视口 viewport: ```html <meta name="viewport" content="width=device-width, initial-scale=1.0"> ``` `width=device-width` :表示视口宽度等于屏幕宽度。 viewport 这个知识点,初学者还比较难理解,以后学 Web 移动端的时候会用到。 (3)定义“关键词”: 举例如下: ```html <meta name="Keywords" content="网易,邮箱,游戏,新闻,体育,娱乐,女性,亚运,论坛,短信" /> ``` 这些关键词,就是告诉搜索引擎,这个网页是干嘛的,能够提高搜索命中率。让别人能够找到你,搜索到你。 (4)定义“页面描述”: meta除了可以设置字符集,还可以设置关键字和页面描述。 只要设置Description页面描述,那么百度搜索结果,就能够显示这些语句,这个技术叫做**SEO**(search engine optimization,搜索引擎优化)。 设置页面描述的举例: ```html <meta name="Description" content="网易是中国领先的互联网技术公司,为用户提供免费邮箱、游戏、搜索引擎服务,开设新闻、娱乐、体育等30多个内容频道,及博客、视频、论坛等互动交流,网聚人的力量。" /> ``` 效果如下: ![](http://img.smyhvae.com/20170629_1743.png) 上面的几种`<meta>`标签都不用记,但是另外还有一个`<meta>`标签是需要记住的: ```html <meta http-equiv="refresh" content="3;http://www.baidu.com"> ``` 上面这个标签的意思是说,3秒之后,自动跳转到百度页面。 **title 标签**: 用于设置网页标题: ```html <title>网页的标题 ``` title标签也是有助于SEO搜索引擎优化的。 **base标签**: ```html ``` base 标签用于指定基础的路径。指定之后,所有的 a 链接都是以这个路径为基准。 ### 4、``标签 ``标签的属性有: - `bgcolor`:设置整个网页的背景颜色。 - `background`:设置整个网页的背景图片。 - `text`:设置网页中的文本颜色。 - `leftmargin`:网页的左边距。IE浏览器默认是8个像素。 - `topmargin`:网页的上边距。 - `rightmargin`:网页的右边距。 - `bottommargin`:网页的下边距。 ``标签另外还有一些属性,这里用个例子来解释: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_39.png) 上方代码中,当我们对`点我点我`这几个字使用超链时,`link`属性表示默认显示的颜色、`alink`属性表示鼠标点击但是还没有松开时的颜色、`vlink`属性表示点击完成之后显示的颜色。效果如下: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_05.gif) ## 计算机编码介绍 计算机,不能直接存储文字,存储的是编码。 计算机只能处理二进制的数据,其它数据,比如:0-9、a-z、A-Z,这些字符,我们可以定义一套规则来表示。假如:A用110表示,B用111表示等。 **ASCII码:** 美国发布的,用1个字节(8位二进制)来表示一个字符,共可以表示2^8=256个字符。 美国的国家语言是英语,只要能表示0-9、a-z、A-Z、特殊符号。 **ANSI编码:** **每个国家为了显示本国的语言,都对ASCII码进行了扩展**。用2个字节(16位二进制)来表示一个汉字,共可以表示2^16=65536个汉字。例如: 中国的ANSI编码是GB2312编码(简体),对6763汉字进行编码,含600多特殊字符。另外还有GBK(简体)。 日本的ANSI编码是JIS编码。 台湾的ANSI编码是BIG5编码(繁体)。 **GBK:** 对GB2312进行了扩展,用来显示罕见的、古汉语的汉字。现在已经收录了2.1万左右。并提供了1890个汉字码位。K的含义就是“扩展”。 **Unicode编码(统一编码):** 用4个字节(32位二进制)来表示一个字符,想法不错,但效率太低。例如,字母A用ASCII表示的话一个字节就够,可用Unicode编码的话,得用4个字节表示,造成了空间的极大浪费。A的Unicode编码是0000 0000 0000 0000 0000 0000 0100 0000 **UTF-8(Unicode Transform Format)编码:** 根据字符的不同,选择其编码的长度。比如:一个字符A用1个字节表示,一个汉字用2个字节表示。 毫无疑问,开发中,都用**UTF-8**编码吧,准没错。 **中文能够使用的字符集两种:** - 第一种:UTF-8。UTF-8是国际通用字库,里面涵盖了所有地球上所有人类的语言文字,比如阿拉伯文、汉语、鸟语…… - 第二种:GBK(对GB2312进行了扩展)。gb2312 是国标,是中国的字库,里面**仅**涵盖了汉字和一些常用外文,比如日文片假名,和常见的符号。 字库规模: UTF-8(字很全) > gb2312(只有汉字) **重点1:避免乱码** 我们用meta标签声明的当前这个html文档的字库,一定要和保存的文件编码类型一样,否则乱码(重点)。 拿 sublime编辑器举例,当我们不设置的时候,sublime默认类型就是UTF-8。而一旦更改为gb2312的时候,就一定要记得设置一下sublime的保存类型: `文件→ set File Encoding to → Chinese Simplified(GBK)`。VS Code 的道理一样。 **重点2:UTF-8和gb2312的比较** 保存大小:UTF-8(更臃肿、加载更慢) > gb2312 (更小巧,加载更快) 总结: - UTF-8:字多,有各种国家的语言,但是保存尺寸大,文件臃肿; - gb2312:字少,只用中文和少数外语和符号,但是尺寸小,文件小巧。 列出2个使用情形: 1) 你们公司是做日本动漫的,经常出现一些日语动漫的名字,网页要使用UTF-8。如果用gb2312将无法显示日语。 2) 你们公司就是中文网页,极度的追求网页的显示速度,要使用gb2312。如果使用UTF-8将每个汉字多一个byte,所以5000个汉字,多5kb。 我们亲测: - qq网、网易、搜狐都是使用gb2312。这些公司,都追求显示速度。 - 新华网藏语频道,使用的是UTF-8,保证字符集的数量。 我们是怎么查看网页的编码方式的呢?在浏览器中打开网页,右键,选择“查看网页源代码”,找到meta标签中的charset属性即可。 那么,我们为什么可以查看网页的源代码呢?因为这个打开的html网页已经存到我的临时文件夹里了,临时文件夹里的html是纯文本文件,纯文本文件自然可以查看网页的源代码。 ## HTML的规范 - HTML不区分大小写,但HTML的标签名、类名、标签属性、大部分属性值建议统一用小写。 - HTML页面的后缀名是html或者htm(有一些系统不支持后缀名长度超过3个字符,比如dos系统) ### 1、编写XHTML的规范: (1)所有标记元素都要正确的嵌套,不能交叉嵌套。正确写法举例:`

    ` (2)所有的标记都必须小写。 (3)所有的标签都必须闭合。 - 双标签:`` - 单标签:`
    ` 建议写成 `
    ` `
    ` 建议转成 `
    `,还有`` (4)所有的属性值必须加引号。`` (5)所有的属性必须有值。`
    `、`` (6)XHTML文档开头必须要有DTD文档类型定义。 ### 2、HTML的基本语法特性 #### (1)HTML对换行不敏感,对tab不敏感 HTML只在乎标签的嵌套结构,嵌套的关系。谁嵌套了谁,谁被谁嵌套了,和换行、tab无关。换不换行、tab不tab,都不影响页面的结构。 也就是说,HTML不是依靠缩进来表示嵌套的,而是看标签的嵌套关系。但是,我们发现有良好的缩进,代码更易读。建议大家都正确缩进标签。 百度为了追求极致的显示速度,所有HTML标签都没有换行、都没有缩进(tab),HTML和换不换行无关,标签的层次依然清晰,只不过程序员不可读了。如下图所示: ![](http://img.smyhvae.com/20170629_2226.png) #### (2)空白折叠现象 HTML中所有的**文字之间**,如果有空格、换行、tab都将被折叠为一个空格显示。 举例如下: ![](http://img.smyhvae.com/20170629_2230.jpg) #### (3)标签要严格封闭 标签不封闭的结果是灾难性的。 标签不封闭的举例如下: ![](http://img.smyhvae.com/20170629_2245.jpg) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 01-HTML/04-HTML标签:排版标签.md ================================================ --- title: 04-HTML标签:排版标签 publish: true --- ## 本文主要内容 排版标签: - `

    ` - `

    ` - `


    ` - `
    ` - `
    ` - `` - `
    ` - `
    `
    
    
    下面来详细介绍一下排版标签。
    
    ## 标题标签
    
    标题使用`

    `至`

    `标签进行定义。`

    `定义最大的标题,`

    `定义最小的标题。具有align属性,属性值可以是:left、center、right。 代码举例: ```html Document

    H1:千古壹号,永不止步

    H3:千古壹号,永不止步

    H3:千古壹号,永不止步

    H4:千古壹号,永不止步

    H5:千古壹号,永不止步
    H6:千古壹号,永不止步
    ``` 效果演示: ![](http://img.smyhvae.com/20200402_1050.png) ## HTML 注释 HTML 注释的格式如下: ```html ``` ## 段落标签`

    ` 段落,是英语“paragraph“缩写。 **作用**:可以把 HTML 文档分割为若干段落。在网页中如果要把文字有条理地显示出来,离不开段落标签。就如同我们平常写文章一样,整个网页也可以分为若干个段落。 代码举例: ```html

    This is a paragraph

    This is another paragraph

    ``` 属性: - `align="属性值"`:对齐方式。属性值包括left center right。 属性举例: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html166440-1dcd2ad6e6353559.png) HTML标签是分等级的,HTML将所有的标签分为两种: - **文本级标签**:p、span、a、b、i、u、em。文本级标签里只能放**文字、图片、表单元素**。(a标签里不能放a和input) - **容器级标签**:div、h系列、li、dt、dd。容器级标签里可以放置任何东西。 从学习p的第一天开始,就要牢牢记住:**p标签是一个文本级标签,p里面只能放文字、图片、表单元素**。其他的一律不能放。 错误写法:(尝试把 h 放到 p 里) ```html

    我是一个小段落

    我是一级标题

    ``` 网页效果如下: ![](http://img.smyhvae.com/20170630_1102.png) 上图显示,浏览器不允许你这么做,我们使用Chrome的F12审查元素发现,浏览器自己把p封闭掉了,不让你去包裹h1。 PS:Chrome浏览器是HTML5支持度最好的浏览器。提供了非常好的开发工具,非常适合我们开发人员使用。审查元素功能的快捷键是F12。 ## 水平线标签`
    ` > horizontal 单词的发音:[ˌhɒrɪˈzɒntl]。 水平分隔线(horizontal rule)可以在视觉上将文档分隔成各个部分。在网页中常常看到一些水平线将段落与段落之间隔开,使得文档结构清晰,层次分明。 代码举例: ```html Document

    自古情深留不住


    总是套路得人心

    ``` 运行效果: ![](http://img.smyhvae.com/20200401_1930.png) 属性介绍: - `align="属性值"`:设定线条置放位置。属性值可选择:left right center。 - `size="2" `:设定线条粗细。以像素为单位,内定为2。 - `width="500"`或`width="70%"`:设定线条长度。可以是绝对值(单位是像素)或相对值。如果设置为相对值的话,内定为100%。 - `color="#0000FF"`:设置线条颜色。 - `noshade`:不要阴影,即设定线条为平面显示。若没有这个属性则表明线条具阴影或立体。 属性效果演示: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_05.png) ## 换行标签`
    ` 如果希望某段文本强制换行显示,就需要使用换行标签。 ```html This
    is a para
    graph with line breaks ``` 效果如下: ![](http://img.smyhvae.com/2015-10-01-cnblogs_html03.png) ## `
    `和``标签 div和span是非常重要的标签,div的语义是division“分割”; span的语义就是span“范围、跨度”。想必你应该听说过“div + css”布局。 ### div和span的介绍 - **div标签**:可以把标签中的内容分割为独立的区块。必须单独占据一行。 - **span标签**:和div的作用一致,但不换行。 代码举例: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_08.png) div标签的属性: - `align="属性值"`:设置块儿的位置。属性值可选择:left、right、 center。 ### div和span的区别 ``和`
    `唯一的区别在于:``是不换行的,而`
    `是换行的。 如果单独在网页中插入这两个元素,不会对页面产生任何的影响。这两个元素是专门为定义CSS样式而生的。或者说,DIV+CSS来实现各种样式。 div在浏览器中,默认是不会增加任何的效果的,但是语义变了,div中的所有元素是一个小区域。 div标签是一个**容器级**标签,里面什么都能放,甚至可以放div自己。 span也是表达“小区域、小跨度”的标签,但只是一个**文本级**的标签。 就是说,span里面只能放置文字、图片、表单元素。 span里面不能放p、h、ul、dl、ol、div。 span举例: ```html

    简介简介简介简介简介简介简介简介 详细信息 购买

    ``` div举例: ```html
    ``` 我们亲切地称这种模式叫做“**div+css**”:**div标签负责布局、结构、分块,css负责样式**。 ## 内容居中标签 `
    ` 此时center代表是一个标签,而不是一个属性值了。只要是在这个标签里面的内容,都会居于浏览器的中间。 效果演示: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_06.png) 到了HTML5里面,center标签不建议使用,建议使用css布局来实现。 ## 预定义(预格式化)标签`
    `
    
    含义:将保留标签内部所有的空白字符(空格、换行符),原封不动地输出结果(告诉浏览器不要忽略空格和空行)。
    
    说明:真正排网页过程中,`
    `标签几乎用不着。
    效果演示:
    
    ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_07.png)
    
    
    ## 我的公众号
    
    想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。
    
    扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:
    
    ![](https://img.smyhvae.com/20200102.png)
    
    
    ================================================
    FILE: 01-HTML/05-HTML标签:字体标签和超链接.md
    ================================================
    ---
    title: 05-HTML标签:字体标签和超链接
    publish: true
    ---
    
    
    
    
    ## 本文主要内容
    
    字体标签: ``、 ``、 `` 、`` 、``
    
    超链接 ``
    
    ## 字体标签
    
    ### 特殊字符(转义字符)
    
    - ` `:空格	(non-breaking spacing,不断打空格)
    - `<`:小于号`<`(less than)
    -  `>`:大于号`>`(greater than)
    - `&`:符号`&`
    - `"`:双引号
    - `'`:单引号
    - `©`:版权`©`
    - `™`:商标`™`
    -  `绐`:文字`绐`。其实,`#32464`是汉字`绐`的unicode编码。
    
    
    比如说,你想把`

    `作为一个文本在页面上显示,直接写`

    `是肯定不行的,因为这代表的是一个段落标签,所以这里需要用到**转义字符**。应该这么写: ```html 这是一个HTML语言的<p>标签 ``` 正确的效果如下: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_11.png) 错误的效果如下: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_12.png) 其实我们只要记住前三个符号就行了,其他的在需要的时候查一下就行了。而且,EditPlus软件中是可以直接点击这些符号进行选择的: ![Paste_Image.png]( http://img.smyhvae.com/2015-10-01-cnblogs_html_13.png) 来一张表格,方便需要的时候查询: | 特殊字符 | 描述 |字符的代码 | |:-------------|:-------------|:-----| ||空格符|` `| |<|小于号|`<`| |> |大于号|`>`| |&|和号|`&`| |¥|人民币|`¥`| |©|版权|`©`| |®|注册商标|`®`| |°|摄氏度|`°`| |±|正负号|`±`| |×|乘号|`×`| |÷|除号|`÷`| |²|平方2(上标2)|`²`| |³|立方3(上标3)|`³`| ### 下划线、中划线、斜体 - ``:下划线标记 - ``或``:中划线标记(删除线) - ``或``:斜体标记 效果: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_15.png) 上面的这几个标签,常用于做一些小装饰、小图标。比如: ![](http://img.smyhvae.com/20180118_2340.png) 这张图中,我们通过查看京东网站的代码发现,箭头处的小图标都是用的标签``。 ### 粗体标签``或``(已废弃) 效果: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_14.png) ### 字体标签``(已废弃) 属性: - `color="红色"`或`color="#ff00cc"`或`color="new rgb(0,0,255)"`:设置字体颜色。 设置方式:单词 \ #ff00cc \ rgb(0,0,255) - `size`:设置字体大小。 取值范围只能是:1至7。取值时,如果取值大于7那就按照7来算,如果取值小于1那就按照1来算。如果想要更大的字体,那就只能通过css样式来解决。 - `face="微软雅黑"`:设置字体类型。 举例: ```html vae ``` 效果: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_10.png) ### 上标`` 下标`` 上小标这两个标签容易混淆,怎么记呢?这样记:`b`的意思是`bottom:底部` 举例: ```html O2 53 ``` 效果: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_16.png) ## 三、超链接 超链接有三种形式,下面分别讲讲。 ### 1、外部链接:链接到外部文件 举例: ```html 点击进入另外一个文件 ``` a是英语`anchor`“锚”的意思,就好像这个页面往另一个页面扔出了一个锚。是一个文本级的标签。 href(hypertext reference):超文本地址。读作“喝瑞夫”,不要读作“喝夫”。 效果: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_17.png) 当然,我们也可以直接点进链接,访问一个网址。代码举例如下: ```html 点我点我 ``` ### 2、锚链接 **锚链接**:给超链接起一个名字,作用是**在本页面或者其他页面的的不同位置进行跳转**。比如说,在网页底部有一个向上箭头,点击箭头后回到顶部,这个就可以利用锚链接。 首先我们要创建一个**锚点**,也就是说,使用`name`属性或者`id`属性给那个特定的位置起个名字。效果如下: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_18.png) 上图中解释: 第11行代码表示,顶部这个锚的名字叫做name1。 然后在底部设置超链接,点击时将回到顶部(此时,网页中的url的末尾也出现了`#name1`)。注意**上图中红框部分的`#`号不要忘记了**,表示跳到名为name1的特定位置,这是规定。如果少了`#`号,点击之后,就会跳到name1这个文件或者name1这个文件夹中去。 如果我们将上图中的第28行代码写成: ```html 回到顶部 ``` 那就表示,点击之后,跳转到`a.html`页面的`name1锚点`中去。 说明:name属性是HTML4.0以前使用的,id属性是HTML4.0后才开始使用。为了向前兼容,因此,name和id这两个属性都要写上,并且值是一样的。 ### 3、邮件链接 代码举例: ```html 点击进入我的邮箱 ``` 效果:点击之后,会弹出outlook,作用不大。 ### 超链接的属性 - `href`:目标URL - `title`:悬停文本。 - `name`:主要用于设置一个锚点的名称。 - `target`:告诉浏览器用什么方式来打开目标页面。`target`属性有以下几个值: - `_self`:在同一个网页中显示(默认值) - `_blank`:**在新的窗口中打开**。 - `_parent`:在父窗口中显示 - `_top`:在顶级窗口中显示 `title`属性举例: ```html 结婚照 ``` 效果如下: ![](http://img.smyhvae.com/20170630_1415.png) `target`属性举例: ```html 链接的内容 ``` blank就是“空白”的意思,就表示新建一个空白窗口。为啥有一个_ ,就是规定,无需解释。 也就是说,如果不写`target=”_blank”`那么就是在相同的标签页打开,如果写了`target=”_blank”`,就是在新的空白标签页中打开。 #### 备注1:分清楚img和a标签的各自的属性 区别如下: ```html ``` #### 备注2:a是一个文本级的标签 比如一个段落中的所有文字都能够被点击,那么应该是p包裹a: ```html

    段落段落段落段落段落段落

    ``` 而不是a包裹p: ```html

    段落段落段落段落段落段落

    ``` a的语义要小于p,a就是可以当做文本来处理,所以p里面相当于放的就是纯文字。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 01-HTML/06-HTML标签:图片标签.md ================================================ --- title: 06-HTML标签:图片标签 publish: true --- ## img标签介绍 ### 介绍 img: 英文全称 image(图像),代表的是一张图片。 如果要想在网页中显示图像,就可以使用img 标签,它是一个单标签。语法如下: ```html ``` ### 能插入的图片类型 - 能够插入的图片类型是:jpg(jpeg)、gif、png、bmp等。 - 不能往网页中插入的图片格式是:psd、ai等。 HTML页面不是直接插入图片,而是插入图片的引用地址,所以要先把图片上传到服务器上。 ## img标签的`src`属性 这里涉及到图片的一个属性: - `src`属性:指图片的路径。英文名称 source。 在写**图片的路径**时,有两种写法:相对路径、绝对路径 ### 写法一:图片的相对路径 相对当前页面所在的路径。两个标记 `.` 和 `..` 分表代表当前目录和上一层目录。 举例1: ```html ``` 相对路径不会出现这种情况: ```html aaa/../bbb/1.jpg ``` `../`要么不写,要么就写在开头。 举例2: ```html ``` 上方代码的意思是说,当前html页面有一个并列的文件夹`images`,在文件夹`images`中存放了一张图片`1.jpg` 效果: ![Paste_Image.png](http://img.smyhvae.com/20151001_19.jpg) 相对路径的面试题。现有如下文件层级图: ![](http://img.smyhvae.com/20170630_1133.png) 问题:如果想在index.html中插入1.png,那么对应的img语句是? 分析: 现在document是最大的文件夹,里面有两个文件夹work和photo。work中又有一个文件夹叫做myweb。myweb文件夹里面有index.html。 所以index.html在myweb文件夹里面,上一级就是work文件夹,上两级就是document文件夹。通过document文件夹当做一个中转站,进入photo文件夹,看到了1.png。 答案: ```html ``` ### 写法二:图片的绝对路径 绝对路径包括以下两种: (1)以盘符开始的绝对路径。举例: ```html ``` (2)网络路径。举例: ```html ``` 大家打开上面的img中的链接,可能有彩蛋哦。 ### 相对路径和绝对路径的总结 相对路径的好处:站点不管拷贝到哪里,文件和图片的相对路径关系都是不变的。相对路径使用有一个前提,就是网页文件和你的图片,必须在一个服务器上。 **总结一下**: 无论是在 a 标签还是 img 标签上,如果要用路径。只有两种路径能用,就是相对路径和绝对路径: - 相对路径从自己出发,找到别人。 - 绝对路径,就是`http://`或者`https://`开头的路径。 ## img标签的其他属性 ### width、height 属性 - `width`:图像的宽度。 - `height`:图像的高度。 width和height,在 HTML5 中的单位是 CSS 像素,在 HTML 4 中既可以是像素,也可以是百分比。可以只指定 width 和 height 中的一个值,浏览器会根据原始图像进行缩放。 **重要提示**:如果要想保证图片等比例缩放,请只设置width和height中其中一个。 ### Alt 属性 - `alt`:当图片不可用(无法显示)的时候,代替图片显示的内容。alt是英语 alternate “替代”的意思,代表替换资源。 `Alt`属性效果演示: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_21.png) 如上图所示:当图片 src 不可用的时候,显示文字。这样做,至少能让用户知道,这个图片大概是什么内容。 ### title 属性 - `title`:**提示性文本**。鼠标悬停时出现的文本。 title 属性不该被用作一幅图片在 alt 之外的补充说明信息。如果一幅图片需要小标题,使用 figure 或 figcaption 元素。 title 元素的值一般作为提示条(tooltip)呈现给用户,在光标于图片上停下后显示出来。尽管这确实能给用户提供更多的信息,您不该假定用户真的能看到:用户可能只有键盘或触摸屏。如果要把特别重要的信息提供给用户,可以选择上面提供的一种方法将其内联显示,而不是使用 title。 举例: ```html ``` 效果: ![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_20.png) ### align 属性 - 图片的`align`属性:**图片和周围文字的相对位置**。属性取值可以是:bottom(默认)、center、top、left、right。 如果想实现图文混排的效果,请使用align属性,取值为left或right。 我们来分别看一下这`align`属性的这几个属性值的区别。 1、`align=""`,图片和文字底端对齐。即默认情况下的显示效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_19.png) 2、`align="center"`:图片和文字水平方向上居中对齐。显示效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_21.png) 3、`align="top"`:图片与文字顶端对齐。显示效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_22.png) 4、`align="left"`:图片在文字的左边。显示效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_23.png) 5、`align="right"`:图片在文字的右边。显示效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_24.png) ### 其他已废弃的属性 - `Align`(已废弃):指图片的水平对齐方式,属性值可以是:top、middle、bottom、left、center、right。该属性已废弃,替换为 `vertical-align`这个CSS属性。 - `border`(已废弃):给图片加边框,单位是像素,边框的颜色默认黑色。该属性已废弃,替换为 `border`这个CSS属性。 - `Hspace`(已废弃):指图片左右的边距。 - `Vspace`(已废弃):指图片上下的边距。 最后,送上妹子的近照一张。楼主已经仁至义尽了:http://img.smyhvae.com/2015-10-01-cnblogs_html_20150219214912_11994.jpg ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 01-HTML/07-html标签图文详解(二).md ================================================ --- title: 07-HTML标签图文详解(二) --- ## 本文主要内容 - 列表标签:`
      `、`
        `、`
        ` - 表格标签:`` - 框架标签及内嵌框架`
        嘿嘿 ``` 效果演示: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_GIF.gif) ## 表单标签 表单标签用``表示,用于与服务器的交互。表单就是收集用户信息的,就是让用户填写的、选择的。 **属性:** - `name`:表单的名称,用于JS来操作或控制表单时使用; - `id`:表单的名称,用于JS来操作或控制表单时使用; - `action`:指定表单数据的处理程序,一般是PHP,如:action=“login.php” - `method`:表单数据的提交方式,一般取值:get(默认)和post 注意:表单和表格嵌套时,是在``标记中套`
        `标记。 form标签里面的action属性和method属性,在后续的 ajax文章上再讲。这里简单说一下:action属性就是表示,表单将提交到哪里。 method属性表示用什么HTTP方法提交,有get、post两种。 **get提交和post提交的区别:** GET方式: 将表单数据,以"name=value"形式追加到action指定的处理程序的后面,两者间用"?"隔开,每一个表单的"name=value"间用"&"号隔开。 特点:只适合提交少量信息,并且不太安全(不要提交敏感数据)、提交的数据类型只限于ASCII字符。 POST方式: 将表单数据直接发送(隐藏)到action指定的处理程序。POST发送的数据不可见。Action指定的处理程序可以获取到表单数据。 特点:可以提交海量信息,相对来说安全一些,提交的数据格式是多样的(Word、Excel、rar、img)。 **Enctype:** 表单数据的编码方式(加密方式),取值可以是:application/x-www-form-urlencoded、multipart/form-data。Enctype只能在POST方式下使用。 - Application/x-www-form-urlencoded:**默认**加密方式,除了上传文件之外的数据都可以 - Multipart/form-data:**上传附件时,必须使用这种编码方式**。 ### ``:输入标签(文本框) 用于接收用户输入。 ```html ``` **属性:** - **`type="属性值"`**:文本类型。属性值可以是: - `text`(默认) - `password`:密码类型 - `radio`:单选按钮,名字相同的按钮作为一组进行单选(单选按钮,天生是不能互斥的,如果想互斥,必须要有相同的name属性。name就是“名字”。 )。非常像以前的收音机,按下去一个按钮,其他的就抬起来了。所以叫做radio。 - `checkbox`:多选按钮,**name 属性值相同的按钮**作为一组进行选择。 - `checked`:将单选按钮或多选按钮默认处于选中状态。当``标签设置为`type="radio"`或者`type=checkbox`时,可以用这个属性。属性值也是checked,可以省略。 - `hidden`:隐藏框,在表单中包含不希望用户看见的信息 - `button`:普通按钮,结合js代码进行使用。 - `submit`:提交按钮,传送当前表单的数据给服务器或其他程序处理。这个按钮不需要写value自动就会有“提交”文字。这个按钮真的有提交功能。点击按钮后,这个表单就会被提交到form标签的action属性中指定的那个页面中去。 - `reset`:重置按钮,清空当前表单的内容,并设置为最初的默认值 - `image`:图片按钮,和提交按钮的功能完全一致,只不过图片按钮可以显示图片。 - `file`:文件选择框。 提示:如果要限制上传文件的类型,需要配合JS来实现验证。对上传文件的安全检查:一是扩展名的检查,二是文件数据内容的检查。 - **`value="内容"`**:文本框里的默认内容(已经被填好了的) - `size="50"`:表示文本框内可以显示**五十个字符**。一个英文或一个中文都算一个字符。 注意**size属性值的单位不是像素哦**。 - `readonly`:文本框只读,不能编辑。因为它的属性值也是readonly,所以属性值可以不写。 用了这个属性之后,在google浏览器中,光标点不进去;在IE浏览器中,光标可以点进去,但是文字不能编辑。 - `disabled`:文本框只读,不能编辑,光标点不进去。属性值可以不写。 > 备注:HTML5中,input的类型又增加了很多(比如date、color,我们会在 html5 中讲到)。 **举例**: ```html 姓名:逗比
        昵称:
        名字:
        密码:
        性别:
        爱好:吃饭 睡觉 打豆豆 ``` 效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_33.png) 注意,多个单选框的input标签中,name 的属性值可以相同,但是 **id 的属性值必须是唯一的**。我们知道,html的标签中,id的属性值是唯一的。 **四种按钮的举例**: ```html




        ``` **前端开发工程师,重点关心页面的美、样式、板式、交互。至于数据的提供和比较重的业务逻辑,都是后台工程师做的事情。** 效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_35.png) ### ``标签里面的每一项用`








        ``` 效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_32.png) ### ` ``` 效果: ![](http://img.smyhvae.com/2015-10-02-cnblogs_html_34.png) 上图的红框部分表示,我在文本区域进行了换行,所以显示的效果也出现了空白。 ### 表单的语义化 比如,我们在注册一个网站的信息的时候,有一部分是必填信息,有一部分是选填信息,这个时候可以利用表单的语义化。 举例: ```html
        账号信息 姓名:逗比
        密码:
        其他信息 性别:
        爱好:吃饭 睡觉 打豆豆
        ``` 效果: ![](http://img.smyhvae.com/20151002_36.png) ### `
        /*滤镜:设置图片为灰白效果*/
        原始图片 图片加入黑白效果
        ``` 效果如下:(IE有效果,google浏览器无效果) ![](http://img.smyhvae.com/2015-10-03-css-36.png) **延伸:** 滤镜本身是平面设计中的知识。如果你懂一点PS的话···打开PS看看吧: ![](http://img.smyhvae.com/2015-10-03-css-38.png) 爆料一下,表示博主有两年多的平面设计经验,我做设计的时间其实比写代码的时间要长,嘿嘿··· ## 导航栏的制作(本段内容请忽略) 现在,我们利用float浮动属性来把无序列表做成一个简单的导航栏吧,效果如下: ![](http://img.smyhvae.com/2015-10-03-css-34.png) 代码: ```html Document ``` 实现效果如下: ![](http://img.smyhvae.com/2015-10-03-css-35.png) 国庆这四天,连续写了四天的博客,白天和黑夜,从未停歇,只交替没交换,为的就是这每日一发。以后会不断更新的。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/02-CSS属性:背景属性.md ================================================ --- title: 02-CSS属性:背景属性 publish: true --- ## background 的常见背景属性 **css2.1** 中,常见的背景属性有以下几种:(经常用到,要记住) - `background-color:#ff99ff;` 设置元素的背景颜色。 - `background-image:url(images/2.gif);` 将图像设置为背景。 - `background-repeat: no-repeat;` 设置背景图片是否重复及如何重复,默认平铺满。(重要) - `no-repeat`不要平铺; - `repeat-x`横向平铺; - `repeat-y`纵向平铺。 - `background-position:center top;` 设置背景图片在当前容器中的位置。 - `background-attachment:scroll;` 设置背景图片是否跟着滚动条一起移动。 属性值可以是:`scroll`(与fixed属性相反,默认属性)、`fixed`(背景就会被固定住,不会被滚动条滚走)。 - 另外还有一个综合属性叫做`background`,它的作用是:将上面的多个属性写在一个声明中。 **CSS3** 中,新增了一些background属性: - background-origin - background-clip 背景裁切 - background-size 调整尺寸 - 多重背景 上面这几个属性经常用到,需要记住。现在我们逐个进行讲解。 ## background-color:背景颜色的表示方法 css2.1 中,颜色的表示方法有三种:单词、rgb表示法、十六进制表示法。 比如红色可以有下面的三种表示方法: ```css background-color: red; background-color: rgb(255,0,0); background-color: #ff0000; ``` CSS3 中,有一种新的表示颜色的方式:RGBA或者HSLA。 RGBA、HSLA可应用于**所有**使用颜色的地方。 下面分别介绍。 ### 用英语单词表示 能够用英语单词来表述的颜色,都是简单颜色,比如red、green、blue、orange、gray等。代码举例: ```css background-color: red; ``` ### RGB 表示法 RGB 表示三原色“红”red、“绿”green、“蓝”blue。 光学显示器中,每个像素都是由三原色的发光原件组成的,靠明亮度不同调成不同的颜色的。r、g、b的值,每个值的取值范围0~255,一共256个值。 比如红色: ```css background-color: rgb(255,0,0); ``` 黑色: ```css background-color: rgb(0,0,0); ``` 颜色可以叠加,比如黄色就是红色和绿色的叠加: ```css background-color: rgb(255,255,0); ``` ### RGBA 表示法 ```javascript background-color: rgba(0, 0, 255, 0.3); border: 30px solid rgba(0, 255, 0, 0.3); ``` **代码解释**: - RGBA 即:Red 红、Green 绿、Blue 蓝、Alpha 透明度。 - R、G、B 的取值范围是:0~255;透明度的取值范围是 0~1。 **RGB色彩模式:** - 自然界中绝大部分颜色都可以用红、绿、蓝(RGB)这三种颜色波长的不同强度组合而得,这就是人们常说的三原色原理。 - RGB三原色也叫加色模式,这是因为当我们把不同光的波长加到一起的时候,可以得到不同的混合色。例:红+绿=黄色,红+蓝=紫色,绿+蓝=青。 - RGB各有256级(0-255)亮度,256级的RGB色彩总共能组合出约1678万种色彩,即256×256×256=16777216。 在数字视频中,对RGB三基色各进行8位编码就构成了大约1678万种颜色,这就是我们常说的真彩色。所有显示设备都采用的是RGB色彩模式。 ### 十六进制表示法 比如红色: ``` background-color: #ff0000; ``` 所有用`#`开头的色值,都是16进制的。 这里,我们就要学会16进制与10进制之间的转换。下面举几个例子。 问:16进制中的28等于10进制的多少? 答:2*16+8 = 40。 16进制中的af等于10进制的多少? 答:10 * 16 + 15 = 175 以此类推: - #ff0000等于rgb(255,0,0)。 - `background-color: #123456;`等价于`background-color: rgb(18,52,86);` **十六进制可以简化为3位,所有#aabbcc的形式,能够简化为#abc**。举例如下: 比如: ``` background-color:#ff0000; ``` 等价于: ``` background-color:#f00; ``` 比如: ``` background-color:#112233; ``` 等价于: ``` background-color:#123; ``` 但是,比如下面这个是无法简化的: ``` background-color:#222333; ``` 再比如,下面这个也是无法简化的: ``` background-color:#123123; ``` 几种常见的颜色简写可以记住。如下: ``` #000 黑 #fff 白 #f00 红 #222 深灰 #333 灰 #ccc 浅灰 ``` ### HSLA 表示法 举例: ```javascript background-color: hsla(240,50%,50%,0.4); ``` 解释: - `H` 色调,取值范围 0~360。0或360表示红色、120表示绿色、240表示蓝色。 - `S` 饱和度,取值范围 0%~100%。值越大,越鲜艳。 - `L` 亮度,取值范围 0%~100%。亮度最大时为白色,最小时为黑色。 - `A` 透明度,取值范围 0~1。 如果不知道 H 的值该设置多少,我们不妨来看一下**色盘**: ![](http://img.smyhvae.com/20180207_1545.png) 推荐链接:[配色宝典](http://www.uisdc.com/how-to-create-color-palettes) **关于设置透明度的其他方式:** (1)`opacity: 0.3;` 会将整个盒子及子盒子设置透明度。也就是说,当盒子设置半透明的时候,会影响里面的子盒子。 (2)`background: transparent;` 可以单独设置透明度,但设置的是完全透明(不可调节透明度)。 ## `background-repeat`属性 `background-repeat:no-repeat;`设置背景图片是否重复及如何重复,默认平铺满。属性值可以是: - `no-repeat`(不要平铺) - `repeat-x`(横向平铺) - `repeat-y`(纵向平铺) 这个属性在开发的时候也是经常用到的。我们通过设置不同的属性值来看一下效果吧: (1)不加这个属性时:(即默认时)(背景图片会被平铺满) ![](http://img.smyhvae.com/2015-10-03-css-19.png) PS:padding的区域也是有背景图的。 (2)属性值为`no-repeat`(不要平铺)时: ![](http://img.smyhvae.com/2015-10-03-css-20.png) (3)属性值为`repeat-x`(横向平铺)时: ![](http://img.smyhvae.com/2015-10-03-css-21.png) 其实这种属性的作用还是很广的。举个例子,设计师设计一张宽度只有1px、颜色纵向渐变的图片,然后我们通过这个属性将其进行水平方向的平铺,就可以看到整个页面都是渐变的了。 在搜索引擎上搜“**平铺背景**”,就可以发现,**周期性的图片**可以采用此种方法进行平铺。 (4)属性值为`repeat-y`(纵向平铺)时: ![](http://img.smyhvae.com/2015-10-03-css-22.png) ## `background-position`属性 `background-position`属性指的是**背景定位**属性。公式如下: 在描述属性值的时候,有两种方式:用像素描述、用单词描述。下面分别介绍。 **1、用像素值描述属性值:** 格式如下: ``` background-position:向右偏移量 向下偏移量; ``` 属性值可以是正数,也可以是负数。比如:`100px 200px`、`-50px -120px`。 举例如下: ![](http://img.smyhvae.com/20170812_1643.png) ![](http://img.smyhvae.com/20170812_1645.png) **2、用单词描述属性值:** 格式如下: ``` background-position: 描述左右的词 描述上下的词; ``` - 描述左右的词:left、center、right - 描述上下的词:top 、center、bottom 比如说,`right center`表示将图片放到右边的中间;`center center`表示将图片放到正中间。 比如说,`bottom`表示图片的底边和父亲**底边贴齐**(好好理解)。 位置属性有很多使用场景的。我们来举两个例子。 场景1:(大背景图) 打开“暗黑3 台湾”的官网,可以看到官网的效果是比较炫的: ![](http://img.smyhvae.com/20170812_1945.jpg) 检查网页后,找到网站背景图片的url:。背景图如下: ![](http://img.smyhvae.com/20170812_1950.jpg) 实际上,我们是通过把这张图片作为网站的背景图来达到显示效果的。只需要给body标签加如下属性即可: ``` body{ background-image: url(/Users/smyhvae/Dropbox/img/20170812_1950.jpg); background-repeat: no-repeat; background-position: center top; } ``` 上方代码中,如果没加`background-position`这个属性,背景图会默认处于浏览器的左上角(显得很丑);加了此属性之后,图片在水平方向就位于浏览器的中间了。 场景2:(通栏banner) 很多网站的首页都会有banner图(网站最上方的全屏大图叫做「**通栏banner**」),这种图要求横向的宽度特别大。比如说,设计师给你一张1920*465的超大banner图,如果我们把这个banner图作为img标签直接插入网页中,会有问题的:首先,图片不在网页的中间;其次,肯定会出现横向滚动条。如下图所示: ![](http://img.smyhvae.com/20170813_1102.gif) 正确的做法是,将banner图作为div的背景图,这样的话,背景图超出div的部分,会自动移溢出。需要给div设置的属性如下: ```css div{ height: 465px; background-image: url(http://img.smyhvae.com/20170813_1053.jpg); background-position: center top; background-repeat: no-repeat; } ``` 上方代码中,我们给div设置height(高度为banner图的高度),不需要设置宽度(因为宽度会自动霸占整行)。效果如下: ![](http://img.smyhvae.com/20170813_1119.gif) 上图可以看出,将banner图作为div的背景后,banner图会永远处于网页的正中间(水平方向来看)。 ## background-attachment 属性 - `background-attachment:scroll;` 设置背景图片是否固定。属性值可以是: - `fixed`(背景就会被固定住,不会被滚动条滚走)。 - `scroll`(与fixed属性相反,默认属性) `background-attachment:fixed;`的效果如下: ![](http://img.smyhvae.com/20170813_1158.gif) ### background 综合属性 background属性和border一样,是一个综合属性,可以将多个属性写在一起。(在[盒子模型](http://www.cnblogs.com/smyhvae/p/7256371.html)这篇文章中专门讲到border) 举例1: ```css background:red url(1.jpg) no-repeat 100px 100px fixed; ``` 等价于: ```css background-color:red; background-image:url(1.jpg); background-repeat:no-repeat; background-position:100px 100px; background-attachment:fixed; ``` 以后,我们可以用小属性层叠掉大属性。 上面的属性中,可以任意省略其中的一部分。 比如说,对于下面这样的属性: ```css background: blue url(images/wuyifan.jpg) no-repeat 100px 100px; ``` 效果如下: ![](http://img.smyhvae.com/20170813_1515.png) ## `background-size`属性:背景尺寸 `background-size`属性:设置背景图片的尺寸。 格式举例: ```javascript /* 宽、高的具体数值 */ background-size: 500px 500px; /* 宽高的百分比(相对于容器的大小) */ background-size: 50% 50%; // 如果两个属性值相同,可以简写成:background-size: 50%; background-size: 100% auto; //这个属性可以自己试验一下。 /* cover:图片始终填充满容器,且保证长宽比不变。图片如果有超出部分,则超出部分会被隐藏。 */ background-size: cover; /* contain:将图片完整地显示在容器中,且保证长宽比不变。可能会导致容器的部分区域为空白。 */ background-size: contain; ``` 这里我们对属性值 `cover` 和 `contain` 进行再次强调: - `cover`:图片始终**填充满**容器,且保证**长宽比不变**。图片如果有超出部分,则超出部分会被隐藏。 - `contain`:将图片**完整地**显示在容器中,且保证**长宽比不变**。可能会导致容器的部分区域留白。 代码举例:(这张图片本身的尺寸是 1080 * 1350) ```html Document
        ``` 效果如下: ![](http://img.smyhvae.com/20191006_1350.png) 在上方代码的基础之上,再加一个 `background-position: center`属性之后,图片就会在容器里**居中显示**: ![](http://img.smyhvae.com/20191006_1520.png) ## 背景原点:`background-origin` 属性 `background-origin` 属性:控制背景从什么地方开始显示。 格式举例: ```javascript /* 从 padding-box 内边距开始显示背景图 */ background-origin: padding-box; //默认值 /* 从 border-box 边框开始显示背景图 */ background-origin: border-box; /* 从 content-box 内容区域开始显示背景图 */ background-origin: content-box; ``` 如果属性值设置成了`border-box`,那边框部分也会显示图片哦。 如下图所示: ![](http://img.smyhvae.com/20180207_2115.png) ## `background-clip`属性:设置元素的背景(背景图片或颜色)是否延伸到边框下面 格式举例: `background-clip: content-box;` 超出的部分,将裁剪掉。属性值可以是: - `border-box` 超出 border-box 的部分,将裁剪掉 - `padding-box` 超出 padding-box 的部分,将裁剪掉 - `content-box` 超出 content-box 的部分,将裁剪掉 假设现在有这样的属性设置: ```javascript background-origin: border-box; background-clip: content-box; ``` 上方代码的意思是,背景图片从**边框部分**开始加载,但是呢,超出**内容区域**的部分将被裁减掉。 ## 同时设置多个背景 我们可以给一个盒子同时设置多个背景,用以逗号隔开即可。可用于自适应局。 代码举例: ```html
        ``` 实现效果如下: ![](http://img.smyhvae.com/20180207_2140.gif) 上方代码中,我们其实给盒子设置了五张小图,拼成的一张大图。当改变浏览器窗口大小时,可以自适应布局。 ## 渐变:background-image 渐变是CSS3当中比较丰富多彩的一个特性,通过渐变我们可以实现许多炫丽的效果,有效的减少图片的使用数量,并且具有很强的适应性和可扩展性。 渐变分为: - 线性渐变:沿着某条直线朝一个方向产生渐变效果。 - 径向渐变:从一个**中心点**开始沿着**四周**产生渐变效果。 - 重复渐变。 见下图: ![](http://img.smyhvae.com/20180208_1140.png) ### 线性渐变 格式: ```javascript background-image: linear-gradient(方向, 起始颜色, 终止颜色); background-image: linear-gradient(to right, yellow, green); ``` 参数解释: - 方向可以是:`to left`、`to right`、`to top`、`to bottom`、角度`30deg`(指的是顺时针方向30°)。 格式举例: ```html
        ``` 效果如下: ![](http://img.smyhvae.com/20180207_2222.png) **举例**:按钮 ```html CSS3 渐变 ``` 效果: ![](http://img.smyhvae.com/20180207_2301.png) ### 径向渐变 格式: ``` background-image: radial-gradient(辐射的半径大小, 中心的位置, 起始颜色, 终止颜色); background-image: radial-gradient(100px at center,yellow ,green); ``` 解释:围绕中心点做渐变,半径是150px,从黄色到绿色做渐变。 中心点的位置可以是:at left right center bottom top。如果以像素为单位,则中心点参照的是盒子的左上角。 当然,还有其他的各种参数。格式举例: ```html
        ``` 效果如下: ![](http://img.smyhvae.com/20180207_2256.png) **举例:**利用径向渐变和边框圆角的属性,生成按钮。代码如下: ```html CSS3 渐变
        ``` 效果如下: ![](http://img.smyhvae.com/20180208_1133.png) 上图中,给第二个div设置的透明度是从0到0.5。如果设置的透明度是从0到0,则样式无变化,和第一个div一样。如果设置的透明度是从1到1,则盒子是全黑的。 ## clip-path:裁剪出元素的部分区域做展示 `clip-path`属性可以创建一个只有元素的部分区域可以显示的剪切区域。区域内的部分显示,区域外的隐藏。 虽然`clip-path`不是背景属性,但这个属性非常强大,但往往会结合背景属性一起使用,达到一些效果。 举例:(鼠标悬停时,放大裁剪的区域) ```css .div1 { width: 320px; height: 320px; border: 1px solid red; background: url(http://img.smyhvae.com/20191006_1410.png) no-repeat; background-size: cover; /* 裁剪出圆形区域 */ clip-path: circle(50px at 100px 100px); transition: clip-path .4s; } .div1:hover{ /* 鼠标悬停时,裁剪出更大的圆形 */ clip-path: circle(80px at 100px 100px); } ``` `clip-path`属性的好处是,即使做了任何裁剪,**容器的占位大小是不变的**。比如上方代码中,容器的占位大小一直都是 320px * 320px。这样的话,也方便我们做一些动画效果。 `clip-path: polygon()`举例: ![](http://img.smyhvae.com/20191006_1430.png) 另外,通过 `clip-path: (svg)` 可以导入svg矢量图,实现 iOS图标的圆角。这里就不详细展开了。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/03-CSS样式表和选择器.md ================================================ --- title: 03-CSS样式表和选择器 publish: true --- ## 本文主要内容 - CSS 概述 - CSS 和 HTML 结合的三种方式:`行内样式表`、`内嵌样式表`、`外部样式表` - CSS 四种基本选择器:`标签选择器`、`类选择器`、`ID选择器`、`通用选择器` - CSS 几种扩展选择器:`后代选择器`、`交集选择器`、`并集选择器` - CSS 样式优先级 ## 前言 ## CSS 概述 CSS:Cascading Style Sheet,层叠样式表。CSS 的作用就是给 HTML 页面标签添加各种样式,**定义网页的显示效果**。简单一句话:CSS 将网页**内容和显示样式进行分离**,提高了显示功能。 css 的最新版本是 css3,**我们目前学习的是 css2.1**。 因为 css3 和 css2.1 不矛盾,必须先学 2.1 然后学 3。 接下来我们要讲一下为什么要使用 CSS。 **HTML 的缺陷:** 1. 不能够适应多种设备 2. 要求浏览器必须智能化足够庞大 3. 数据和显示没有分开 4. 功能不够强大 **CSS 优点:** 1. 使数据和显示分开 2. 降低网络流量 3. 使整个网站视觉效果一致 4. 使开发效率提高了(耦合性降低,一个人负责写 html,一个人负责写 css) 比如说,有一个样式需要在一百个页面上显示,如果是 html 来实现,那要写一百遍,现在有了 css,只要写一遍。现在,html 只提供数据和一些控件,完全交给 css 提供各种各样的样式。 ### CSS 的重点知识点 盒子模型、浮动、定位 ### CSS 整体感知 我们先来看一段简单的 css 代码: ```html Document

        我是大标题

        我是内容

        ``` 解释如下: ![](http://img.smyhvae.com/20170710_1605.png) 我们写 css 的地方是 style 标签,就是“样式”的意思,写在 head 里面。后面的课程中我们将知道,css 也可以写在单独的文件里面,现在我们先写在 style 标签里面。 如果在 sublime 中输入` ``` type 表示“类型”,text 就是“纯文本”,css 也是纯文本。 但是,如果在 sublime 中输入`st`或者`style`然后按 tab 键,可以自动生成的格式如下:(不建议) ```html ``` css 对换行不敏感,对空格也不敏感。但是一定要有标准的语法。冒号,分号都不能省略。 ## CSS 语法 **语法格式:**(其实就是键值对) ```html 选择器{ 属性名: 属性值; 属性名: 属性值; } ``` 或者可以写成: ```css 选择器 { k: v; k: v; k: v; k: v; } 选择器 { k: v; k: v; k: v; k: v; } ``` **解释:** - 选择器代表页面上的某类元素,选择器后一定是大括号。 - 属性名后必须用冒号隔开,属性值后用分号(最后一个属性可以不用分号,但最好还是加上分号)。 - 冒号和属性值之间可以留一个空格(编程习惯的经验)。 - 如果一个属性有多个值的话,那么多个值用**空格**隔开。 **举例:** ```css p { color: red; } ``` **完整版代码举例:** ```html

        洗白白

        你懂得

        我不会就这样轻易的狗带

        ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-01.png) ### css 代码的注释 **格式:** ```html ``` 注意:CSS 只有`/* */`这种注释,没有`//`这种注释。而且注释要写在`

        洗白白

        你懂得

        ``` ![](http://img.smyhvae.com/2015-10-03-css-03.png) ### 3、CSS 和 HTML 结合方式三:引入外部样式表 css 文件 **引入样式表文件**的方式又分为两种: - (1)**采用``标签**。例如:`` - (2)**采用 import**,必须写在`

        千古壹号学完了安卓,继续学前端

        ``` 【总结】需要注意的是: (1)所有的标签,都可以是选择器。比如 ul、li、label、dt、dl、input。 (2)无论这个标签藏的多深,一定能够被选择上。 (3)选择的所有,而不是一个。 ### 2、ID 选择器:规定用`#`来定义 针对某一个特定的标签来使用,只能使用一次。css 中的 ID 选择器以”#”来定义。 举例: ```html #mytitle{ border:3px dashed green; } ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-08.png) id 选择器的选择符是“#”。 任何的 HTML 标签都可以有 id 属性。表示这个标签的名字。这个标签的名字,可以任取,但是: - (1)只能有字母、数字、下划线。 - (2)必须以字母开头。 - (3)不能和标签同名。比如 id 不能叫做 body、img、a。 另外,特别强调的是:**HTML 页面,不能出现相同的 id,哪怕他们不是一个类型**。比如页面上有一个 id 为 pp 的 p,一个 id 为 pp 的 div,是非法的! **一个标签可以被多个 css 选择器选择:** 比如,我们可以同时让标签选择器和 id 选择器作用于同一个标签。如下: ![](http://img.smyhvae.com/20170710_1737.png) 然后我们通过网页的审查元素看一下效果: ![](http://img.smyhvae.com/20170711_1540.png) 现在,假设选择器冲突了,比如 id 选择器说这个文字是红色的,标签选择器说这个文字是绿色的。那么听谁的? 实际上,css 有着非常严格的计算公式,能够处理冲突. 一个标签可以被多个 css 选择器选择,共同作用,这就是“**层叠式**”的第一层含义(第一层含义和第二层含义,放到 css 基础的第三篇文章里讲)。 ### 3、类选择器:规定用圆点`.`来定义 、针对**你想要的所有**标签使用。优点:灵活。 css 中用`.`来表示类。举例如下: ```html .one{ width:800px; } ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-07.png) 和 id 非常相似,任何的标签都可以携带 id 属性和 class 属性。class 属性的特点: - 特性 1:类选择器可以被多种标签使用。 - 特性 2:同一个标签可以使用多个类选择器。用**空格**隔开。举例如下:(正确) ```html

        我是一个h3啊

        ``` 初学者常见的错误,就是写成了两个 class。举例如下:(错误) ```html

        我是一个h3啊

        ``` **类选择器使用的举例:** 类选择器的使用,能够决定一个人的 css 水平。 比如,我们现在要做下面这样一个页面: ![](http://img.smyhvae.com/20170711_1639.png) 正确的思路,就是用所谓“公共类”的思路,就是我们类就是提供“公共服务”,比如有绿、大、线,一旦携带这个类名,就有相应的样式变化。对应 css 里的代码如下: ```html ``` 然后让每个标签去选取自己想要用的类选择器: ```html

        段落1

        段落2

        段落3

        ``` 也就是说: (1)不要去试图用一个类名,把某个标签的所有样式写完。这个标签要多携带几个类,共同完成这个标签的样式。 (2)每一个类要尽可能小,有“公共”的概念,能够让更多的标签使用。 问题:到底用 id 还是用 class? 答案:尽可能的用 class,除非极特殊的情况可以用 id。 原因:id 是 js 用的。也就是说,js 要通过 id 属性得到标签,所以 css 层面尽量不用 id,要不然 js 就很别扭。另一层面,我们会认为一个有 id 的元素,有动态效果。 举例如下: ![](http://img.smyhvae.com/20170711_1706.png) 上图所示,css 和 js 都在用同一个 id,会出现不好沟通的情况。 我们记住这句话:**类上样式,id 上行为**。意思是说,`class`属性交给 css 使用,`id`属性交给 js 使用。 **上面这三种选择器的区别:** - 标签选择器针对的是页面上的一类标签。 - ID 选择器是只针对特定的标签(一个),ID 是此标签在此页面上的唯一标识。 - 类选择器可以被多种标签使用。 ### 4、通配符`*`:匹配任何标签 通用选择器,将匹配任何标签。不建议使用,IE 有些版本不支持,大网站增加客户端负担。 效率不高,如果页面上的标签越多,效率越低,所以页面上不能出现这个选择器。 举例: ```css * { margin-left: 0px; margin-top: 0px; } ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-09.png) ## CSS 的几种高级选择器 **高级选择器:** - 后代选择器:用空格隔开 - 交集选择器:选择器之间紧密相连 - 并集选择器(分组选择器):用逗号隔开 - 伪类选择器 下面详细讲一下这几种高级(扩展)选择器。 ### 1、后代选择器: 定义的时候用空格隔开 对于`E F`这种格式,表示**所有属于 E 元素后代的 F 元素**,有这个样式。空格就表示后代。 后代选择器,就是一种平衡:共性、特性的平衡。当要把**某一个部分的所有的什么**,进行样式改变,就要想到后代选择器。 后代选择器,描述的是祖先结构。 看定义可能有点难理解,我们来看例子吧。 举例 1: ```html ``` 空格就表示后代。`.div1 p` 表示`.div1`的后代所有的`p`。 这里强调一下:这两个标签不一定是连续紧挨着的,只要保持一个后代的关联即可。也就是说,选择的是后代,不一定是儿子。 举例: ```html ``` 上方代码的意思是说:定义了`

        `标签中的``标签中的``标签的样式。 同理:h3 和 b 和 i 标签不一定是连续紧挨着的,只要保持一个后代的关联即可。 效果: ![](http://img.smyhvae.com/2015-10-03-css-11.png) 或者还有下面这种写法: ![](http://img.smyhvae.com/2015-10-03-css-12.png) 上面的这种写法,`

        `标签和``标签并不是紧挨着的,但他们保持着一种后代关系。 还有下面这种写法:(含类选择器、id 选择器都是可以的) ![](http://img.smyhvae.com/2015-10-03-css-13.png) 我们在开头说了:**后代选择器,描述的是一种祖先结构**。我们举个例子来说明这句话: ```html Document

        我是什么颜色?

        ``` 上面 css 中的`div div p`,也能使文字的颜色变红。通过浏览器的审查元素,我们可以看到 p 元素的祖先列表: ![](http://img.smyhvae.com/20170711_1836.png) 讲到这里,我们再提一个 VS Code 的快捷键: 在 VS Code 中输入`p#haha`,按 tab 键后,会生成`

        `。 在 VS Code 中输入`p.haha`,按 tab 键后,会生成`

        `。 ### 2、交集选择器:定义的时候紧密相连 定义交集选择器的时候,两个选择器之间紧密相连。一般是以标签名开头,比如`div.haha`,再比如`p.special`。 如果后一个选择器是类选择器,则写为`div.special`;如果后一个选择器 id 选择器,则写为`div#special`。 来看下面这张图就明白了: ![](http://img.smyhvae.com/20170711_1851.png) ```css h3.special { color: red; } ``` 选择的元素要求同时满足两个条件:必须是 h3 标签,然后必须是 special 标签。 举例: ```html 交集选择器测试

        标题1

        我也是标题

        我是段落

        ``` 效果如下: ![](http://img.smyhvae.com/20170711_1852.png) 注意,交集选择器没有空格。所以,没有空格的`div.red`(交集选择器)和有空格的`div .red`(后代选择器)不是一个意思。 交集选择器可以连续交:(一般不要这么写) ```css h3.special.zhongyao { color: red; } ``` 上面这种写法,是 IE7 开始兼容的,IE6 不兼容。 ### 3、并集选择器:定义的时候用逗号隔开 三种基本选择器都可以放进来。 举例: ```css p,h1,.title1,#one { color: red; } ``` 效果: ![](https://img.smyhvae.com/20211116_1055.png) ## 一些 CSS3 选择器 > 所有的 CSS3 选择器,我们放在 CSS3 的内容里介绍。本文暂时先接触一部分。 ### 浏览器的兼容性问题 > 我们可以用`IETester`这个软件测一下 CSS 在各个版本 IE 浏览器上的显示效果。 IE: 微软的浏览器,随着操作系统安装的。所以每个 windows 都有 IE 浏览器。各版本如下: - windows xp 操作系统安装的 IE6 - windows vista 操作系统安装的 IE7 - windows 7 操作系统安装的 IE8 - windows 8 操作系统安装的 IE9 - windows10 操作系统安装的 edge 浏览器兼容问题,要出,就基本上就是出在 IE6、7 身上,这两个浏览器是非常低级的浏览器。 为了测试浏览器 CSS 3 的兼容性,我们可以在网上搜"css3 机器猫"关键字,然后在不同的浏览器中打开如下链接: - 测试结果如下: ![](http://img.smyhvae.com/20170711_1939.png) 我们可以在[百度统计](http://tongji.baidu.com/data/)里查看浏览器的市场占有率: - IE9 5.94% - IE8 21.19% - IE7 4.79% - IE6 4.11% 我们可以在中查看 ![](http://img.smyhvae.com/20170711_1948.png) 我们要知道典型的 IE6 兼容问题(面试要问),但是做项目我们兼容到 IE8 即可。不解决 IE8 以下的兼容问题,目的在于:培养更高的兴趣和眼光,别天天的跟 IE6 较劲。 我们可以用「IETester」软件看看 css 在各个浏览器中的显示效果。 ### 1.子代选择器,用符号`>`表示 > IE7 开始兼容,IE6 不兼容。 ```css div > p { color: red; } ``` div 的儿子 p。和 div 的后代 p 的截然不同。 能够选择: ```html

        我是div的儿子

        ``` 不能选择: ```html
        • 我是div的重孙子

        ``` ### 2.序选择器 > IE8 开始兼容;IE6、7 都不兼容 设置无序列表`
          `中的第一个`
        • `为红色: ```html ``` 设置无序列表`
            `中的最后一个`
          • `为红色: ```css ul li:last-child { color: blue; } ``` 序选择器还有更复杂的用法,以后再讲。 由于浏览器的更新需要过程,所以现在如果公司还要求兼容 IE6、7,那么就要自己写类名: ```html
            • 项目
            • 项目
            • 项目
            • 项目
            • 项目
            • 项目
            • 项目
            • 项目
            • 项目
            • 项目
            ``` 用类选择器来选择第一个或者最后一个: ```html ul li.first{ color:red; } ul li.last{ color:blue; } ``` ### 3.下一个兄弟选择器 > IE7 开始兼容,IE6 不兼容。 `+`表示选择下一个兄弟 ```html ``` 上方的选择器意思是:选择的是 h3 元素后面紧挨着的第一个兄弟。 ```html

            我是一个标题

            我是一个段落

            我是一个段落

            我是一个段落

            我是一个标题

            我是一个段落

            我是一个段落

            我是一个段落

            我是一个标题

            我是一个段落

            我是一个段落

            我是一个段落

            我是一个标题

            ``` 效果如下: ![](http://img.smyhvae.com/20170711_1950.png) 这种选择器作用不大。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/04-CSS选择器:伪类.md ================================================ --- title: 04-CSS选择器:伪类 publish: true --- ## 伪类(伪类选择器) **伪类**:同一个标签,根据其**不同的种状态,有不同的样式**。这就叫做“伪类”。伪类用冒号来表示。 比如div是属于box类,这一点很明确,就是属于box类。但是a属于什么类?不明确。因为需要看用户点击前是什么状态,点击后是什么状态。所以,就叫做“伪类”。 ### 静态伪类选择器、动态伪类选择器 伪类选择器分为两种。 (1)**静态伪类**:只能用于**超链接**的样式。如下: - `:link` 超链接点击之前 - `:visited` 链接被访问过之后 PS:以上两种样式,只能用于超链接。 (2)**动态伪类**:针对**所有标签**都适用的样式。如下: - `:hover` “悬停”:鼠标放到标签上的时候 - `:active` “激活”: 鼠标点击标签,但是不松手时。 - `:focus` 是某个标签获得焦点时的样式(比如某个输入框获得焦点) ## 超链接a标签 ### 超链接的四种状态 a标签有4种伪类(即对应四种状态),要求背诵。如下: - `:link` “链接”:超链接点击之前 - `:visited` “访问过的”:链接被访问过之后 - `:hover` “悬停”:鼠标放到标签上的时候 - `:active` “激活”: 鼠标点击标签,但是不松手时。 对应的代码如下: ```html ``` 记住,在css中,这四种状态**必须按照固定的顺序写**: > a:link 、a:visited 、a:hover 、a:active 如果不按照顺序,那么将失效。“爱恨准则”:love hate。必须先爱,后恨。 看一下这四种状态的动图效果: ![](http://img.smyhvae.com/20180113_2239.gif) ### 超链接的美化 问:既然`a{}`定义了超链的属性,和`a:link{}`定义了超链点击之前的属性,那这两个有啥区别呢? 答: **`a{}`和`a:link{}`的区别:** - `a{}`定义的样式针对所有的超链接(包括锚点) - `a:link{}`定义的样式针对所有写了href属性的超链接(不包括锚点) 超链接a标签在使用的时候,比较难。因为不仅仅要控制a这个盒子,也要控制它的伪类。 我们一定要将a标签写在前面,将`:link、:visited、:hover、:active`这些伪类写在后面。 针对超链接,我们来举个例子: ![](http://img.smyhvae.com/20170810_2235.gif) 为了实现上面这个效果,完整版代码如下: ```html Document ``` 上方代码中,我们发现,当我们在定义`a:link`和 `a:visited`这两个伪类的时候,如果它们的属性相同,我们其实可以写在一起,用逗号隔开就好,摘抄如下: ```css .nav ul li a{ display: block; width: 120px; height: 50px; } /*两个伪类的属性,可以用逗号隔开*/ .nav ul li a:link , .nav ul li a:visited{ text-decoration: none; background-color: purple; color:white; } .nav ul li a:hover{ background-color: orange; } ``` 如上方代码所示,最标准的写法,就是把link、visited、hover这三个伪类都要写。但是前端开发工程师在大量的实践中,发现不写link、visited也挺兼容。写法是: > a:link、a:visited都是可以省略的,简写在a标签里面。也就是说,a标签涵盖了link、visited的状态(前提是都具有了相同的属性)。写法如下: ```css .nav ul li a{ display: block; width: 120px; height: 50px; text-decoration: none; background-color: purple; color:white; } .nav ul li a:hover{ background-color: orange; } ``` 当然了,在写`a:link`、`a:visited`这两个伪类的时候,要么同时写,要么同时不写。如果只写`a`属性和`a:link`属性,不规范。 ## 动态伪类举例 我们在第一段中描述过,下面这三种动态伪类,针对所有标签都适用。 - `:hover` “悬停”:鼠标放到标签上的时候 - `:active` “激活”: 鼠标点击标签,但是不松手时。 - `:focus` 是某个标签获得焦点时的样式(比如某个输入框获得焦点) 我们不妨来举下例子。 举例1: ```html ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-02.gif) 利用这个`hover`属性,我们同样对表格做一个样式的设置: 表格举例: ```html Document
            ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-04.gif) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/05-CSS样式表的继承性和层叠性.md ================================================ --- title: 05-CSS样式表的继承性和层叠性 publish: true --- ## 本文重点 - CSS的继承性 - CSS的层叠性 - 计算权重 - 权重问题大总结 - CSS样式表的冲突的总结 - 权重问题深入 - 同一个标签,携带了多个类名 - !important标记 ## CSS的继承性 我们来看下面这样的代码,来引入继承性: ![](http://img.smyhvae.com/20170724_2359.png) 上方代码中,我们给div标签增加红色属性,却发现,div里的每一个子标签`

            `也增加了红色属性。于是我们得到这样的结论: > 有一些属性,当给自己设置的时候,自己的后代都继承上了,这个就是**继承性。** 继承性是从自己开始,直到最小的元素。 但是呢,如果再给上方的代码加一条属性: ![](http://img.smyhvae.com/20170725_2122.jpg) 上图中,我们给div加了一个border,但是发现只有div具备了border属性,而p标签却没有border属性。于是我们可以得出结论: - 关于文字样式的属性,都具有继承性。这些属性包括:color、 text-开头的、line-开头的、font-开头的。 - 关于盒子、定位、布局的属性,都不能继承。 以后当我们谈到css有哪些特性的时候,我们要首先想到继承性。而且,要知道哪些属性具有继承性、哪些属性没有继承性。 ## CSS的层叠性 很多公司如果要笔试,那么一定会考层叠性。 ### 层叠性:计算权重 **层叠性:就是css处理冲突的能力。** 所有的权重计算,没有任何兼容问题! CSS像艺术家一样优雅,像工程师一样严谨。 我们来看一个例子,就知道什么叫层叠性了。 ![](http://img.smyhvae.com/20170725_2132.png) 上图中,三种选择器同时给P标签增加颜色的属性,但是,文字最终显示的是蓝色,这个时候,就出现了层叠性的情况。 当多个选择器,选择上了某个元素的时候,要按照如下顺序统计权重: - id 选择器 - 类选择器、属性选择器、伪类选择器 - 标签选择器、伪元素选择器 因为对于相同方式的样式表,其选择器排序的优先级为:ID选择器 > 类选择器 > 标签选择器 针对上面这句话,我们接下来举一些复杂一点的例子。 ### 层叠性举例 #### 举例1:计算权重 ![](http://img.smyhvae.com/20170725_2138.png) 如上图所示,统计各个选择器的数量,优先级高的胜出。文字的颜色为红色。 PS:不进位,实际上能进位(奇淫知识点:255个标签,等于1个类名)但是没有实战意义! #### 举例2:权重相同时 ![](http://img.smyhvae.com/20170725_2250.png) 上图可以看到,第一个样式和第二个样式的权重相同。但第二个样式的书写顺序靠后,因此以第二个样式为准(就近原则)。 #### 举例3:具有实战性的例子 ![](http://img.smyhvae.com/20170726_2221.png) 现在我要让一个列表实现上面的这种样式:第一个li为红色,剩下的li全部为蓝色。 如果写成下面这种代码是无法实现的: ![](http://img.smyhvae.com/20170726_2225.png) 无法实现的原因很简单,计算一下三个选择器的权重就清楚了,显然第二个样式被第一个样式表覆盖了。 正确的做法是:(**非常重要**) ![](http://img.smyhvae.com/20170726_2229.png) 上图中,第二个样式比第一个样式的权重要大。因此在实战中可以实现这种效果:**所有人当中,让某一个人为红,让其他所有人为蓝。** 这种方式好用是好用,但用好很难。 就拿上方代码来举例,为了达到这种效果,即为了防止权重不够,比较稳妥的做法是:**把第二个样式表照着第一个样式表来写,在此基础上,给第二个样式表再加一个权重。** 上面这个例子很具有实战性。 #### 举例4:继承性造成的影响 这里需要声明一点: >如果不能直接选中某个元素,通过继承性影响的话,那么权重是0。 为了验证上面这句话,我们来看看下面这样的例子: ![](http://img.smyhvae.com/20170727_0843.png) 另外:**如果大家的权重相同,那么就采用就近原则:谁描述的近,听谁的**。举例如下:(box3 描述得最近,所以采用 box3 的属性) ![](http://img.smyhvae.com/20190122_1530.png) 上方代码的文字版如下: ```html Document

            猜猜我是什么颜色

            ``` ### 层叠性:权重计算的问题大总结(非常重要) 层叠性。层叠性是一种能力,就是处理冲突的能力。当不同选择器,对一个标签的同一个样式,有不同的值,听谁的?这就是冲突。css有着严格的处理冲突的机制。 通过列举上面几个例子,我们对权重问题做一个总结。 ![](http://img.smyhvae.com/20170727_2050.png) 上面这个图非常重要,我们针对这个图做一个文字描述: - 选择上了,数权重,(id的数量,类的数量,标签的数量)。如果权重一样,谁写在后面听谁的。 - 没有选择上,通过继承影响的,就近原则,谁描述的近听谁的。如果描述的一样近,比如选择器权重,如果权重再一样重,谁写在后面听谁的。 ### CSS样式表的冲突的总结 - 1、对于相同的选择器(比如同样都是类选择器),其样式表排序:行级样式 > 内嵌样式表 > 外部样式表(就近原则) - 2、对于相同类型的样式表(比如同样都是内部样式表),其选择器排序:ID选择器 > 类选择器 > 标签选择器 - 3、外部样式表的ID选择器 > 内嵌样式表的标签选择器 > 总结:就近原则。ID选择器优先级最大。 举例:如果都是内嵌样式表,优先级的顺序如下:(ID 选择器 > 类选择器 > 标签选择器) ![](http://img.smyhvae.com/2015-10-03-css-14.png) 另外还有两个冲突的情况: - 1、对同一个标签,如果用到的都是内嵌样式表,且权重一致,那它的优先级:**定义**的CSS样式表中,谁最近,就用谁。 - 2、对于同一个标签,如果用到的都是外部样式表,且权重一致,那它的优先级:html文件中,引用样式表的位置越近,就用谁。 例如: ![](http://img.smyhvae.com/2015-10-03-css-16.png) ### 题目演示 CSS的层叠性讲完了,我们来做几个题目吧。 #### 题目1 ![](http://img.smyhvae.com/20170727_0851.png) #### 题目2 ![](http://img.smyhvae.com/20170727_0853.png) #### 题目3 ![](http://img.smyhvae.com/20170727_0855.png) #### 题目4 ![](http://img.smyhvae.com/20170727_0857.png) ## 权重问题深入 ### 同一个标签,携带了多个类名,有冲突: 这里需要补充两种冲突的情况: - 1、对同一个标签,如果用到了了多个相同的内嵌样式表,它的优先级:**定义**的样式表中,谁最近,就用谁。 - 2、对于同一个标签,如果引用了多个相同的外部样式表,它的优先级:html文件中,引用样式表的位置越近,就用谁。 例如:(就近原则) ![](http://img.smyhvae.com/20170727_2021.png) 上图中,**文字显示的颜色均为红色**。因为这和在标签中的挂类名的书序无关,只和css的顺序有关。 ### !important标记:优先级最高 来看个很简单的例子: ![](http://img.smyhvae.com/20170727_2029.png) 上图中,显然id选择器的权重最大,所以文字的颜色是红色。 如果我们想让文字的颜色显示为绿色,只需要给标签选择器的加一个`!important`标记,此时其权重为无穷大。如下: ![](http://img.smyhvae.com/20170727_2035_2.png) important是英语里面的“重要的”的意思。我们可以通过如下语法: ``` k:v !important; ``` 来给一个属性提高权重。这个属性的权重就是**无穷大**。 注意,一定要注意语法的正确性。 正确的语法: ``` font-size:60px !important; ``` 错误的语法: ``` font-size:60px; !important; 不能把!important写在外面 font-size:60px important; 不能忘记感叹号 ``` `!important`标记需要强调如下3点: **(1)!important提升的是一个属性,而不是一个选择器** ```css p{ color:red !important; 只写了这一个!important,所以只有字体颜色属性提升了权重 font-size: 100px ; 这条属性没有写!important,所以没有提升权重 } #para1{ color:blue; font-size: 50px; } .spec{ color:green; font-size: 20px; } ``` 所以,综合来看,字体颜色是red(听important的);字号是50px(听id的)。 **(2)!important无法提升继承的权重,该是0还是0** 比如HTML结构: ```html

            哈哈哈哈哈哈哈哈

            ``` 有CSS样式: ```css div{ color:red !important; } p{ color:blue; } ``` 由于div是通过继承性来影响文字颜色的,所以!important无法提升它的权重,权重依然是0。 干不过p标签,因为p标签是实实在在选中了,所以字是蓝色的(以p为准)。 **(3)!important不影响就近原则** 如果大家都是继承来的,按理说应该按照“就近原则”,那么important能否影响就近原则呢? 答案是:不影响。远的,永远是远的。不能给远的写一个important,干掉近的。 为了验证这个问题,我们可以搞两层具有继承性的标签,然后给外层标签加一个!important,最终看看就近原则有没有被打破。举例如下: ![](http://img.smyhvae.com/20170727_2046.png) PS:做网站的时候,!important 尽量不要用。否则会让css写的很乱。 ## 知识回顾 > 我们把以上内容和上一篇文章做一个简单的知识回顾。 ### CSS属性 > css属性,面试的时候会有笔试,笔试没有智能提示的。 加粗,倾斜,下划线: ``` font-weight:bold; font-style:italic; text-decoration:underline; ``` 背景颜色、前景色: ``` background-color:red; color:red; ``` ### class和id的区别 class用于css的,id用于js的。 1)class页面上可以重复。id页面上唯一,不能重复。 2)一个标签可以有多个class,用空格隔开。但是id只能有id。 ### 各种选择器(浏览器兼容性) IE6层面兼容的选择器: 标签选择器、id选择器、类选择器、后代、交集选择器、并集选择器、通配符。如下: ``` p #box .spec div p div.spec div,p * ``` IE7能够兼容的:儿子选择器、下一个兄弟选择器。如下: ``` div>p h3+p ``` IE8能够兼容的: ``` ul li:first-child ul li:last-child ``` ### css两个性质 - 继承性:好的事儿。继承从上到下,哪些能?哪些不能? - 层叠性:冲突,多个选择器描述了同一个属性,听谁的? 再看几个题目: ![](http://img.smyhvae.com/20170727_0900.png) ![](http://img.smyhvae.com/20170727_0901.png) ![](http://img.smyhvae.com/20170727_0902.png) ![](http://img.smyhvae.com/20170727_0903.png) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/06-CSS盒模型详解.md ================================================ --- title: 06-CSS盒模型详解 publish: true --- ## 盒子模型 ### 前言 盒子模型,英文即box model。无论是div、span、还是a都是盒子。 但是,图片、表单元素一律看作是文本,它们并不是盒子。这个很好理解,比如说,一张图片里并不能放东西,它自己就是自己的内容。 ### 盒子中的区域 一个盒子中主要的属性就5个:width、height、padding、border、margin。如下: - width和height:**内容**的宽度、高度(不是盒子的宽度、高度)。 - padding:内边距。 - border:边框。 - margin:外边距。 盒子模型的示意图: ![](http://img.smyhvae.com/20170727_2128.png) 代码演示: ![](http://img.smyhvae.com/20170727_2326.png) 上面这个盒子,width:200px; height:200px; 但是真实占有的宽高是302*302。 这是因为还要加上padding、border。 注意:**宽度和真实占有宽度,不是一个概念!**来看下面这例子。 ### 标准盒模型和IE盒模型 > 我们目前所学习的知识中,以标准盒子模型为准。 标准盒子模型: ![](http://img.smyhvae.com/2015-10-03-css-27.jpg) IE盒子模型: ![](http://img.smyhvae.com/2015-10-03-css-30.jpg) 上图显示: 在 CSS 盒子模型 (Box Model) 规定了元素处理元素的几种方式: - width和height:**内容**的宽度、高度(不是盒子的宽度、高度)。 - padding:内边距。 - border:边框。 - margin:外边距。 CSS盒模型和IE盒模型的区别: - 在 **标准盒子模型**中,**width 和 height 指的是内容区域**的宽度和高度。增加内边距、边框和外边距不会影响内容区域的尺寸,但是会增加元素框的总尺寸。 - **IE盒子模型**中,**width 和 height 指的是内容区域+border+padding**的宽度和高度。 注:Android中也有margin和padding的概念,意思是差不多的,如果你会一点Android,应该比较好理解吧。区别在于,Android中没有border这个东西,而且在Android中,margin并不是控件的一部分。 ### ``标签也有margin ``标签有必要强调一下。很多人以为``标签占据的是整个页面的全部区域,其实是错误的,正确的理解是这样的:整个网页最大的盒子是``,即浏览器。而``是``的儿子。浏览器给``默认的margin大小是8个像素,此时``占据了整个页面的一大部分区域,而不是全部区域。来看一段代码。 ```html Document
            有生之年
            狭路相逢
            ``` 上面的代码中,我们对div标签设置了边距等信息。打开google浏览器,按住F12,显示效果如下: ![](http://img.smyhvae.com/20151003_27.png) ## 认识width、height 一定要知道,在前端开发工程师眼中,世界中的一切都是不同的。 比如说,丈量稿纸,前端开发工程师只会丈量内容宽度: ![](http://img.smyhvae.com/20170727_2329.png) 下面这两个盒子,真实占有宽高,都是302*302: 盒子1: ```css .box1{ width: 100px; height: 100px; padding: 100px; border: 1px solid red; } ``` 盒子2: ```css .box2{ width: 250px; height: 250px; padding: 25px; border: 1px solid red; } ``` 真实占有宽度 = 左border + 左padding + width + 右padding + 右border 上面这两个盒子的盒模型图如下: ![](https://img.smyhvae.com/20170728_0925.png) **如果想保持一个盒子的真实占有宽度不变,那么加width的时候就要减padding。加padding的时候就要减width**。因为盒子变胖了是灾难性的,这会把别的盒子挤下去。 ## 认识padding ### padding区域也有颜色 padding就是内边距。padding的区域有背景颜色,css2.1前提下,并且背景颜色一定和内容区域的相同。也就是说,background-color将填充**所有border以内的区域。** 效果如下: ![](http://img.smyhvae.com/20170728_1005.png) ### padding有四个方向 padding是4个方向的,所以我们能够分别描述4个方向的padding。 方法有两种,第一种写小属性;第二种写综合属性,用空格隔开。 小属性的写法: ```css padding-top: 30px; padding-right: 20px; padding-bottom: 40px; padding-left: 100px; ``` 综合属性的写法:(上、右、下、左)(顺时针方向,用空格隔开。margin的道理也是一样的) ```css padding:30px 20px 40px 100px; ``` 如果写了四个值,则顺序为:上、右、下、左。 如果只写了三个值,则顺序为:上、右和左、下。 如果只写了两个值,则顺序为:上和下、左和右。 比如说: ``` padding: 30px 40px; ``` 则顺序等价于:30px 40px 30px 40px; 要懂得,**用小属性层叠大属性**。比如: ``` padding: 20px; padding-left: 30px; ``` 上面的padding对应盒子模型为: ![](http://img.smyhvae.com/20170728_1039.png) 下面的写法: ``` padding-left: 30px; padding: 20px; ``` 第一行的小属性无效,因为被第二行的大属性层叠掉了。 下面的题,会做了,说明你明白了。 ### 一些题目 **题目1**:说出下面盒子真实占有宽高,并画出盒模型图。 ```css div{ width: 200px; height: 200px; padding: 10px 20px 30px; padding-right: 40px; border: 1px solid #000; } ``` 答案: ![](http://img.smyhvae.com/20170728_1048.png) **题目2**:说出下面盒子真实占有宽高,并画出盒模型图。 ```css div{ width: 200px; height: 200px; padding-left: 10px; padding-right: 20px; padding:40px 50px 60px; padding-bottom: 30px; border: 1px solid #000; } ``` 答案: `padding-left:10px;` 和`padding-right:20px;` 没用,因为后面的padding大属性,层叠掉了他们。 盒子模型如下: ![](http://img.smyhvae.com/20170728_1100.png) **题目3**:现在给你一个盒子模型图,请写出代码,试着用最最简单的方法写。 ![](http://img.smyhvae.com/20170728_1401.png) 答案: ```css width:123px; height:123px; padding:20px 40px; border:1px solid red; ``` **题目4**:现在给你一个盒子模型图,请写出代码,试着用最最简单的方法写。 ![](http://img.smyhvae.com/20170728_1402.png) 答案: ```css width:123px; height:123px; padding:20px; padding-right:40px; border:1px solid red; ``` ### 一些元素,默认带有padding 一些元素,默认带有`padding`,比如ul标签。如下: ![](http://img.smyhvae.com/20170728_1413.png) 上图显示,不加任何样式的ul,也是有40px的padding-left。 所以,我们做站的时候,为了便于控制,总是喜欢清除这个默认的padding。 可以使用`*`进行清除: ```css *{ margin: 0; padding: 0; } ``` 但是,`*`的效率不高,所以我们使用并集选择器,罗列所有的标签(不用背,有专业的清除默认样式的样式表,今后学习): ``` body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{ margin:0; padding:0; } ``` ## 认识border border就是边框。边框有三个要素:像素(粗细)、线型、颜色。 比如: ```css .div1{ width: 10px; height: 10px; border: 2px solid red; } ``` 颜色如果不写,默认是黑色。另外两个属性如果不写,则无法显示边框。 ### border-style border的所有的线型如下:(我们可以通过查看`CSS参考手册`得到) ![](http://img.smyhvae.com/20170728_1435.png) 比如`border:10px ridge red;`这个属性,在chrome和firefox、IE中有细微差别:(因为可以显示出效果,因此并不是兼容性问题,只是有细微差别而已) ![](http://img.smyhvae.com/20170728_1619.png) 如果公司里面的设计师是处女座的,追求极高的**页面还原度**,那么不能使用css来制作边框。就要用到图片,就要切图了。 所以,比较稳定的border-style就几个:solid、dashed、dotted。 ### border拆分 border是一个大综合属性。比如说: ```css border:1px solid red; ``` 就是把上下左右这四个方向的边框,都设置为 1px 宽度、线型实线、red颜色。 PS:小技巧:在sublime text中,为了快速输入`border:1px solid red;`这个属性,可以直接输入`bd`,然后选第二个后回车。 border属性是能够被拆开的,有两大种拆开的方式: - (1)按三要素拆开:border-width、border-style、border-color。(一个border属性是由三个小属性综合而成的) - (2)按方向拆开:border-top、border-right、border-bottom、border-left。 现在我们明白了:**一个border属性,是由三个小属性综合而成的**。如果某一个小属性后面是空格隔开的多个值,那么就是上右下左的顺序。举例如下: ``` border-width:10px 20px; border-style:solid dashed dotted; border-color:red green blue yellow; ``` 效果如下: ![](http://img.smyhvae.com/20170728_1516.png) (1)按三要素拆: ```css border-width:10px; //边框宽度 border-style:solid; //线型 border-color:red; //颜色。 ``` 等价于: ``` border:10px solid red; ``` (2)按方向来拆: ```css border-top:10px solid red; border-right:10px solid red; border-bottom:10px solid red; border-left:10px solid red; ``` 等价于: ```css border:10px solid red; ``` (3)按三要素和方向来拆:(就是把每个方向的,每个要素拆开。3*4 = 12) ```css border-top-width:10px; border-top-style:solid; border-top-color:red; border-right-width:10px; border-right-style:solid; border-right-color:red; border-bottom-width:10px; border-bottom-style:solid; border-bottom-color:red; border-left-width:10px; border-left-style:solid; border-left-color:red; ``` 等价于: ```css border:10px solid red; ``` 工作中到底用什么?很简答:什么简单用什么。但要懂得,用小属性层叠大属性。举例如下: ![](http://img.smyhvae.com/20170728_1606.png) 为了实现上方效果,写法如下: ```css border:10px solid red; border-right-color:blue; ``` ![](http://img.smyhvae.com/20170728_1608.png) 为了实现上方效果,写法如下: ```css border:10px solid red; border-style:solid dashed; ``` border可以没有: ```css border:none; ``` 可以某一条边没有: ```css border-left: none; ``` 也可以调整左边边框的宽度为0: ```css border-left-width: 0; ``` ### border-image 属性 比如: ```css border-image: url(.img.png) 30 round; ``` 这个属性在实际开发中用得不多,暂时忽略。 ### 举例1:利用 border 属性画一个三角形(小技巧) 完整代码如下: ```css div{ width: 0; height: 0; border: 50px solid transparent; border-top-color: red; border-bottom: none; } ``` 步骤如下: (1)当我们设置盒子的width和height为0时,此时效果如下: ![](http://img.smyhvae.com/20170728_1639.png) (2)然后将border的底部取消: ![](http://img.smyhvae.com/20170728_1645.png) (3)最后设置border的左边和右边为白色或者**透明**: ![](http://img.smyhvae.com/20170728_1649.png) 这样,一个三角形就画好了。 ### 举例2:利用 border 属性画一个三角形(更推荐的技巧) 上面的例子1中,画出来的是直角三角形,可如果我想画等边三角形,要怎么做呢? 完整代码如下:(用 css 画等边三角形) ```css .div1{ width: 0; height: 0; border-top: 30px solid red; /* 通过改变 border-left 和 border-right 中的像素值,来改变三角形的形状 */ border-left: 20px solid transparent; border-right: 20px solid transparent; } ``` 效果如下: ![](http://img.smyhvae.com/20191004_1830.png) 另外,我们在上方代码的基础之上,再加一个 `border-radus: 20px;` 就能画出一个扇形。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/07-浮动.md ================================================ --- title: 07-浮动 publish: true --- ## 文本主要内容 - 标准文档流 - 标准文档流的特性 - 行内元素和块级元素 - 行内元素和块级元素的相互转换 - 浮动的性质 - 浮动的清除 - 浏览器的兼容性问题 - 浮动中margin相关 - 关于margin的IE6兼容问题 ## 标准文档流 宏观地讲,我们的web页面和photoshop等设计软件有本质的区别:web页面的制作,是个“流”,必须从上而下,像“织毛衣”。而设计软件,想往哪里画个东西,都能画。 ### 标准文档流的特性 **(1)空白折叠现象:** 无论多少个空格、换行、tab,都会折叠为一个空格。 比如,如果我们想让img标签之间没有空隙,必须紧密连接: ``` ``` **(2)高矮不齐,底边对齐:** 举例如下: ![](http://img.smyhvae.com/20170729_1508_2.png) **(3)自动换行,一行写不满,换行写。** ### 行内元素和块级元素 学习的初期,我们就要知道,标准文档流等级森严。标签分为两种等级: - 行内元素 - 块级元素 我们可以举一个例子,看看块级元素和行内元素的区别: ![](http://img.smyhvae.com/20170729_1529_2.png) 上图中可以看到,`h1`标签是块级元素,占据了整行,`span`标签是行内元素,只占据内容这一部分。 现在我们尝试给两个标签设置宽高。效果如下: ![](http://img.smyhvae.com/20170729_1532_2.png) 上图中,我们尝试给两个标签设置宽高,但发现,宽高属性只对块级元素`h1`生效。于是我们可以做出如下总结。 **行内元素和块级元素的区别:**(非常重要) 行内元素: - 与其他行内元素并排; - 不能设置宽、高。默认的宽度,就是文字的宽度。 块级元素: - 霸占一行,不能与其他任何元素并列; - 能接受宽、高。如果不设置宽度,那么宽度将默认变为父亲的100%。 **行内元素和块级元素的分类:** 在以前的HTML知识中,我们已经将标签分过类,当时分为了:文本级、容器级。 从HTML的角度来讲,标签分为: - 文本级标签:p、span、a、b、i、u、em。 - 容器级标签:div、h系列、li、dt、dd。 > PS:为甚么说p是文本级标签呢?因为p里面只能放文字&图片&表单元素,p里面不能放h和ul,p里面也不能放p。 现在,从CSS的角度讲,CSS的分类和上面的很像,就p不一样: - 行内元素:除了p之外,所有的文本级标签,都是行内元素。p是个文本级,但是是个块级元素。 - 块级元素:所有的容器级标签都是块级元素,还有p标签。 我们把上面的分类画一个图,即可一目了然: ![](http://img.smyhvae.com/20170729_1545.png) ### 行内元素和块级元素的相互转换 我们可以通过`display`属性将块级元素和行内元素进行相互转换。display即“显示模式”。 #### 块级元素可以转换为行内元素: 一旦,给一个块级元素(比如div)设置: ``` display: inline; ``` 那么,这个标签将立即变为行内元素,此时它和一个span无异。inline就是“行内”。也就是说: - 此时这个div不能设置宽度、高度; - 此时这个div可以和别人并排了。 举例如下: ![](http://img.smyhvae.com/20170729_1629.png) #### 行内元素转换为块级元素: 同样的道理,一旦给一个行内元素(比如span)设置: ``` display: block; ``` 那么,这个标签将立即变为块级元素,此时它和一个div无异。block”是“块”的意思。也就是说: - 此时这个span能够设置宽度、高度 - 此时这个span必须霸占一行了,别人无法和他并排 - 如果不设置宽度,将撑满父亲 举例如下: ![](http://img.smyhvae.com/20170729_1638.png) 标准流里面的限制非常多,导致很多页面效果无法实现。如果我们现在就要并排、并且就要设置宽高,那该怎么办呢?办法是:移民!**脱离标准流**! css中一共有三种手段,使一个元素脱离标准文档流: - (1)浮动 - (2)绝对定位 - (3)固定定位 这便引出我们今天要讲的内容:浮动。 ## 浮动的性质 > 浮动是css里面布局用的最多的属性。 现在有两个div,分别设置宽高。我们知道,它们的效果如下: ![](http://img.smyhvae.com/20170729_1722.png) 此时,如果给这两个div增加一个浮动属性,比如`float: left;`,效果如下: ![](http://img.smyhvae.com/20170729_1723.png) 这就达到了浮动的效果。此时,两个元素并排了,并且两个元素都能够设置宽度、高度了(这在上一段的标准流中,不能实现)。 浮动想学好,一定要知道三个性质。接下来讲一讲。 ### 性质1:浮动的元素脱标 脱标即脱离标准流。我们来看几个例子。 证明1: ![](http://img.smyhvae.com/20170729_2028.png) 上图中,在默认情况下,两个div标签是上下进行排列的。现在由于float属性让上图中的第一个`
            `标签出现了浮动,于是这个标签在另外一个层面上进行排列。而第二个`
            `还在自己的层面上遵从标准流进行排列。 证明2: ![](http://img.smyhvae.com/20180111_2320.png) 上图中,span标签在标准流中,是不能设置宽高的(因为是行内元素)。但是,一旦设置为浮动之后,即使不转成块级元素,也能够设置宽高了。 所以能够证明一件事:**一旦一个元素浮动了,那么,将能够并排了,并且能够设置宽高了。无论它原来是个div还是个span。**所有标签,浮动之后,已经不区分行内、块级了。 ### 性质2:浮动的元素互相贴靠 我们来看一个例子就明白了。 我们给三个div均设置了`float: left;`属性之后,然后设置宽高。当改变浏览器窗口大小时,可以看到div的贴靠效果: ![](http://img.smyhvae.com/20170730_1910.gif) 上图显示,3号如果有足够空间,那么就会靠着2号。如果没有足够的空间,那么会靠着1号大哥。 如果没有足够的空间靠着1号大哥,3号自己去贴左墙。 不过3号自己去贴墙的时候,注意: ![](http://img.smyhvae.com/20170730_1928.gif) 上图显示,3号贴左墙的时候,并不会往1号里面挤。 同样,float还有一个属性值是`right`,这个和属性值`left`是对称的。 ### 性质3:浮动的元素有“字围”效果 来看一张图就明白了。我们让div浮动,p不浮动。 ![](http://img.smyhvae.com/20170730_2005.png) 上图中,我们发现:**div挡住了p,但不会挡住p中的文字**,形成“字围”效果。 总结:**标准流中的文字不会被浮动的盒子遮挡住**。(文字就像水一样) 关于浮动我们要强调一点,浮动这个东西,为避免混乱,我们在初期一定要遵循一个原则:**永远不是一个东西单独浮动,浮动都是一起浮动,要浮动,大家都浮动。** ### 性质4:收缩 收缩:一个浮动的元素,如果没有设置width,那么将自动收缩为内容的宽度(这点非常像行内元素)。 举例如下: ![](http://img.smyhvae.com/20170801_1720.png) 上图中,div本身是块级元素,如果不设置width,它会单独霸占整行;但是,设置div浮动后,它会收缩 ### 浮动的补充(做网站时注意) ![](http://img.smyhvae.com/20170731_2248.png) 上图所示,将para1和para2设置为浮动,它们是div的儿子。此时para1+para2的宽度小于div的宽度。效果如上图所示。可如果设置para1+para2的宽度大于div的宽度,我们会发现,para2掉下来了: ![](http://img.smyhvae.com/20170731_2252_2.png) ### 布置一个作业 布置一个作业,要求实现下面的效果: ![](http://img.smyhvae.com/20170801_0858.png) 为实现上方效果,代码如下: ```html Document
            语言选择
            ``` 其实,这个页面的布局是下面这个网站: ![](http://img.smyhvae.com/20170801_0900.png) ## 浮动的清除 > 这里所说的清除浮动,指的是清除浮动与浮动之间的影响。 ### 前言 通过上面这个例子,我们发现,此例中的网页就是通过浮动实现并排的。 比如说一个网页有header、content、footer这三部分。就拿content部分来举例,如果设置content的儿子为浮动,但是,这个儿子又是一个全新的标准流,于是儿子的儿子仍然在标准流里。 从学习浮动的第一天起,我们就要明白,浮动有开始,就要有清除。我们先来做个实验。 下面这个例子,有两个块级元素div,div没有任何属性,每个div里有li,效果如下: ![](http://img.smyhvae.com/20170801_2122.png) 上面这个例子很简单。可如果我们给里面的`
          • `标签加浮动。效果却成了下面这个样子: 代码如下: ```html Document
            • 生命壹号1
            • 生命壹号2
            • 生命壹号3
            • 生命壹号4
            • 许嵩1
            • 许嵩2
            • 许嵩3
            • 许嵩4
            ``` 效果如下: ![](http://img.smyhvae.com/20170801_2137.png) 上图中,我们发现:第二组中的第1个li,去贴靠第一组中的最后一个li了(我们本以为这些li会分成两排)。 这便引出我们要讲的:清除浮动的第一种方式。 那该怎么解决呢? ### 方法1:给浮动元素的祖先元素加高度 造成前言中这个现象的根本原因是:li的**父亲div没有设置高度**,导致这两个div的高度均为0px(我们可以通过网页的审查元素进行查看)。div的高度为零,导致不能给自己浮动的孩子,撑起一个容器。 撑不起一个容器,导致自己的孩子没办法在自己的内部进行正确的浮动。 好,现在就算给这个div设置高度,可如果div自己的高度小于孩子的高度,也会出现不正常的现象: ![](http://img.smyhvae.com/20170801_2152.png) 给div设置一个正确的合适的高度(至少保证高度大于儿子的高度),就可以看到正确的现象: ![](http://img.smyhvae.com/20170801_2153.png) **总结:** **如果一个元素要浮动,那么它的祖先元素一定要有高度。** **有高度的盒子,才能关住浮动**。(记住这句过来人的经验之语) 只要浮动在一个有高度的盒子中,那么这个浮动就不会影响后面的浮动元素。所以就是清除浮动带来的影响了。 ![](http://img.smyhvae.com/20170801_2200.png) ![](http://img.smyhvae.com/20170801_2201.png) ### 方法2:clear:both; 网页制作中,高度height其实很少出现。为什么?因为能被内容撑高!也就是说,刚刚我们讲解的方法1,工作中用得很少。 那么,能不能不写height,也把浮动清除了呢?也让浮动之间,互不影响呢? 这个时候,我们可以使用`clear:both;`这个属性。如下: ![](http://img.smyhvae.com/20170801_2233.png) ``` clear:both; ``` clear就是清除,both指的是左浮动、右浮动都要清除。`clear:both`的意思就是:**不允许左侧和右侧有浮动对象。** 这种方法有一个非常大的、致命的问题,**它所在的标签,margin属性失效了**。读者可以试试看。 margin失效的本质原因是:上图中的box1和box2,高度为零。 ### 方法3:隔墙法 上面这个例子中,为了防止第二个div贴靠到第二个div,我们可以在这两个div中间用一个新的div隔开,然后给这个新的div设置`clear: both;`属性;同时,既然这个新的div无法设置margin属性,我们可以给它设置height,以达到margin的效果(曲线救国)。这便是隔墙法。 我们看看例子效果就知道了: ![](http://img.smyhvae.com/20170802_1109.png) 上图这个例子就是隔墙法。 **内墙法:** 近些年,有演化出了“内墙法”: ![](http://img.smyhvae.com/20170802_1123.png) 上面这个图非常重要,当作内墙法的公式,先记下来。 为了讲内墙法,我们先记住一句重要的话:**一个父亲是不能被浮动的儿子撑出高度的**。举例如下: (1)我们在一个div里放一个有宽高的p,效果如下:(很简单) ![](http://img.smyhvae.com/20170802_1716.png) (2)可如果在此基础之上,给p设置浮动,却发现父亲div没有高度了: ![](http://img.smyhvae.com/20170802_1730.png) (3)此时,我么可以在div的里面放一个div(作为内墙),就可以让父亲div恢复高度: ![](http://img.smyhvae.com/20170802_1812.png) 于是,我们采用内墙法解决前言中的问题: ![](http://img.smyhvae.com/20170802_1834.png) 与外墙法相比,内墙法的优势(本质区别)在于:内墙法可以给它所在的家撑出宽度(让box1有高)。即:box1的高度可以自适应内容。 而外墙法,虽然一道墙可以把两个div隔开,但是这两个div没有高,也就是说,无法wrap_content。 ### 清除浮动方法4:overflow:hidden; 我们可以使用如下属性: ``` overflow:hidden; ``` overflow即“溢出”, hidden即“隐藏”。这个属性的意思是“溢出隐藏”。顾名思义:所有溢出边框的内容,都要隐藏掉。如下: ![](http://img.smyhvae.com/20170804_1449.png) 上图显示,`overflow:hidden;`的本意是清除溢出到盒子外面的文字。但是,前端开发工程师发现了,它能做偏方。如下: 一个父亲不能被自己浮动的儿子,撑出高度。但是,只要给父亲加上`overflow:hidden`; 那么,父亲就能被儿子撑出高了。这是一个偏方。 举个例子: ![](http://img.smyhvae.com/20170804_1920.png) 那么对于前言中的例子,我们同样可以使用这一属性: ![](http://img.smyhvae.com/20170804_1934.png) 这一招,实际上生成了BFC。关于BFC的解释,详见本项目的另外一篇文章《前端面试/CSS盒模型及BFC.md》。 ## 浮动清除的总结 > 我们在上一段讲了四种清除浮动的方法,本段来进行一个总结。 浮动的元素,只能被有高度的盒子关住。 也就是说,如果盒子内部有浮动,这个盒子有高,那么妥妥的,浮动不会互相影响。 ### 1、加高法 工作上,我们绝对不会给所有的盒子加高度,这是因为麻烦,并且不能适应页面的快速变化。 ```html
            //设置height

            //设置height

            ``` ### 2、`clear:both;`法 最简单的清除浮动的方法,就是给盒子增加clear:both;表示自己的内部元素,不受其他盒子的影响。 ```html

            //clear:both;

            ``` 浮动确实被清除了,不会互相影响了。但是有一个问题,就是margin失效。两个div之间,没有任何的间隙了。 ### 3、隔墙法 在两部分浮动元素中间,建一个墙。隔开两部分浮动,让后面的浮动元素,不去追前面的浮动元素。 墙用自己的身体当做了间隙。 ```html

            ``` 我们发现,隔墙法好用,但是第一个div,还是没有高度。如果我们现在想让第一个div,自动根据自己的儿子撑出高度,我们就要想一些“小伎俩”。 内墙法: ```html

            ``` 内墙法的优点就是,不仅仅能够让后部分的p不去追前部分的p了,并且能把第一个div撑出高度。这样,这个div的背景、边框就能够根据p的高度来撑开了。 ### 4、`overflow:hidden;` 这个属性的本意,就是将所有溢出盒子的内容,隐藏掉。但是,我们发现这个东西能够用于浮动的清除。 我们知道,一个父亲,不能被自己浮动的儿子撑出高度,但是,如果这个父亲加上了overflow:hidden;那么这个父亲就能够被浮动的儿子撑出高度了。这个现象,不能解释,就是浏览器的偏方。 并且,overflow:hidden;能够让margin生效。 **清除浮动的例子:** 我们现在举个例子,要求实现下图中无序列表部分的效果: ![](http://img.smyhvae.com/20170804_2321.png) 对比一下我们讲的四种清除浮动的方法。如果用外墙法,ul中不能插入div标签,因为ul中只能插入li,如果插入li的墙,会浪费语义。如果用内墙法,不美观。综合对比,还是用第四种方法来实现吧,这会让标签显得极其干净整洁: ![](http://img.smyhvae.com/20170805_1615.png) 上方代码中,如果没有加`overflow:hidden;`,那么第二行的li会紧跟着第一行li的后面。 ## 浏览器的兼容性问题 > 讲一下上述知识点涉及到的浏览器兼容问题。 ### 兼容性1(微型盒子) **兼容性的第一条**:IE6不支持小于12px的盒子,任何小于12px的盒子,在IE6中看都大。即:IE 6不支持微型盒子。 举个例子。我们设置一个height为 5px 、宽度为 200px的盒子,看下在IE 8和 IE 6中的显示效果: ![](http://img.smyhvae.com/20170805_1726.png) 解决办法很简单,就是将盒子的字号大小,设置为**小于盒子的高**,比如,如果盒子的高为5px,那就把font-size设置为0px(0px < 5px)。如下: ``` height: 5px; _font-size: 0px; ``` 我们现在介绍一下浏览器hack。hack就是“黑客”,就是使用浏览器提供的后门,针对某一种浏览器做兼容。 IE6留了一个**后门**:只要给css属性之前,加上**下划线**,这个属性就是IE6的专有属性。 比如说,我们给背景颜色这个属性加上下划线,就变成了`_background-color: green;`。效果如下: ![](http://img.smyhvae.com/20170805_2026.png) 于是乎,为了解决微型盒子(即height小于12px)的问题,正确写法:(注意不要忘记下划线) ``` height: 10px; _font-size:0; ``` ### 兼容性2 **兼容性的第二条:**IE6不支持用`overflow:hidden;`来清除浮动。 解决办法,以毒攻毒。追加一条: ``` _zoom:1; ``` 完整写法: ``` overflow: hidden; _zoom:1; ``` 实际上,`_zoom:1;`能够触发浏览器hasLayout机制。这个机制,不要深究了,因为只有IE6有。我们只需要让IE6好用,具体的实现机制,可以自行查阅。 需要强调的是,`overflow:hidden;`的本意,就是让溢出盒子的border的内容隐藏,这个功能是IE6兼容的。不兼容的是`overflow:hidden;`清除浮动的时候。 **总结:** 我们刚才学习的两个IE6的兼容问题,都是通过多写一条hack来解决的,这个我们称为伴生属性,即两个属性,要写一起写。 属性1: ``` height:6px; _font-size:0; ``` 属性2: ``` overflow:hidden; _zoom:1; ``` ## margin相关 > 我们来讲一下浮动中和margin相关的知识。 ### margin塌陷/margin重叠 **标准文档流中,竖直方向的margin不叠加,取**较大的值**作为margin(水平方向的margin是可以叠加的,即水平方向没有塌陷现象)。如下图所示: ![](http://img.smyhvae.com/20170805_0904.png) 如果不在标准流,比如盒子都浮动了,那么两个盒子之间是没有塌陷现象的。 ### 盒子居中`margin:0 auto;` margin的值可以为auto,表示自动。当left、right两个方向都是auto的时候,盒子居中了: ``` margin-left: auto; margin-right: auto; ``` 盒子居中的简写为: ``` margin:0 auto; ``` 对上方代码的理解:上下的margin为0,左右的margin都尽可能的大,于是就居中了。 注意: - (1)只有标准流的盒子,才能使用`margin:0 auto;`居中。也就是说,当一个盒子浮动了、绝对定位了、固定定位了,都不能使用margin:0 auto; - (2)使用`margin:0 auto;`的盒子,必须有width,有明确的width。(可以这样理解,如果没有明确的width,那么它的width就是霸占整行,没有意义) - (3)`margin:0 auto;`是让盒子居中,不是让盒子里的文本居中。文本的居中,要使用`text-align:center;` 对上面的第三条总结一下:(非常重要) ``` margin:0 auto; //让这个div自己在大容器中的水平方向上居中。 text-align: center; //让这个div内部的文本居中。 ``` 顺便普及一下知识,text-align还有: ``` text-align:left; //没啥用,因为默认居左 text-align:right; //文本居右 ``` ### 善于使用父亲的padding,而不是儿子的margin 我们来看一个奇怪的现象。现在有下面这样一个结构:(div中放一个p) ```

            ``` 上面的结构中,我们尝试通过给儿子`p`一个`margin-top:50px;`的属性,让其与父亲保持50px的上边距。结果却看到了下面的奇怪的现象: ![](http://img.smyhvae.com/20170806_1537.png) 此时我们给父亲div加一个border属性,就正常了: ![](http://img.smyhvae.com/20170806_1544.png) 如果父亲没有border,那么儿子的margin实际上踹的是“流”,踹的是这“行”。所以,父亲整体也掉下来了。 **margin这个属性,本质上描述的是兄弟和兄弟之间的距离; 最好不要用这个marign表达父子之间的距离。** 所以,如果要表达父子之间的距离,我们一定要善于使用父亲的padding,而不是儿子的margin。 ## 关于margin的IE6兼容问题 ### IE6的双倍margin的bug: 当出现连续浮动的元素,携带与浮动方向相同的margin时,队首的元素,会双倍marign。 ```
            ``` ![](http://img.smyhvae.com/20170806_1558.png) 解决方案: (1)使浮动的方向和margin的方向,相反。 所以,你就会发现,我们特别喜欢,浮动的方向和margin的方向相反。并且,前端开发工程师,把这个当做习惯了。 ``` float: left; margin-right: 40px; ``` (2)使用hack:(没必要,别惯着这个IE6) 单独给队首的元素,写一个一半的margin: ```
          • ``` ``` ul li.no1{ _margin-left:20px; } ``` PS:双倍margin的问题,面试经常问哦。 ### IE6的3px bug ![](http://img.smyhvae.com/20170806_1616.png) 解决办法:不用管,因为根本就不允许用儿子踹父亲(即描述父子之间的距离,请用padding,而不是margin)。所以,如果你出现了3px bug,说明你的代码不标准。 IE6,千万不要跟他死坑、较劲,它不配。 格调要高,我们讲IE6的兼容性问题,就是为了增加面试的成功率,不是为了成为IE6的专家。 ## Fireworks和others ### Fireworks fireworks是Adobe公司的一个设计软件。功能非常多,我们以后用啥讲啥。Fireworks的默认文件格式是png。 标尺的快捷键:Ctrl + Alt+ R ### others 首行缩进两个汉字: ``` text-indent: 2em; ``` 上方属性中,单位比较奇怪,叫做em,em就是汉字的一个宽度。indent的意思是缩进。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/08-CSS属性:定位属性.md ================================================ --- title: 08-CSS属性:定位属性 publish: true --- CSS的定位属性有三种,分别是绝对定位、相对定位、固定定位。 ``` position: absolute; position: relative; position: fixed; ``` 下面逐一介绍。 ## 相对定位 **相对定位**:让元素相对于自己原来的位置,进行位置调整(可用于盒子的位置微调)。 我们之前学习的背景属性中,是通过如下格式: ``` background-position:向右偏移量 向下偏移量; ``` 但这回的定位属性,是通过如下格式: ``` position: relative; left: 50px; top: 50px; ``` 相对定位的举例: ```html Document
            有生之年
            狭路相逢
            ``` 效果: ![](http://img.smyhvae.com/2015-10-03-css-28.png) ### 相对定位不脱标 **相对定位**:不脱标,老家留坑,**别人不会把它的位置挤走**。 也就是说,相对定位的真实位置还在老家,只不过影子出去了,可以到处飘。 ### 相对定位的用途 如果想做“压盖”效果(把一个div放到另一个div之上),我们一般**不用**相对定位来做。相对定位,就两个作用: - (1)微调元素 - (2)做绝对定位的参考,子绝父相 ### 相对定位的定位值 - left:盒子右移 - right:盒子左移 - top:盒子下移 - bottom:盒子上移 PS:负数表示相反的方向。 ↘: ``` position: relative; left: 40px; top: 10px; ``` ↙: ``` position: relative; right: 100px; top: 100px; ``` ↖: ``` position: relative; right: 100px; bottom: 100px; ``` ↗: ``` position: relative; left: 200px; bottom: 200px; ``` ![](http://img.smyhvae.com/20180115_1716.png) 如果要描述上面这张图的方向,我们可以首先可以这样描述: ``` position: relative; left: 200px; top: 100px; ``` 因为`left: 200px`等价于`right: -200px`,所以这张图其实有四种写法。 ## 绝对定位 **绝对定位**:定义横纵坐标。原点在父容器的左上角或左下角。横坐标用left表示,纵坐标用top或者bottom表示。 格式举例如下: ``` position: absolute; /*绝对定位*/ left: 10px; /*横坐标*/ top/bottom: 20px; /*纵坐标*/ ``` ### 绝对定位脱标 **绝对定位的盒子脱离了标准文档流。** 所以,所有的标准文档流的性质,绝对定位之后都不遵守了。 绝对定位之后,标签就不区分所谓的行内元素、块级元素了,不需要`display:block`就可以设置宽、高了。 ### 绝对定位的参考点(重要) (1)如果用**top描述**,那么参考点就是**页面的左上角**,而不是浏览器的左上角: ![](http://img.smyhvae.com/20180115_2120.png) (2)如果用**bottom描述**,那么参考点就是**浏览器首屏窗口尺寸**(好好理解“首屏”二字),对应的页面的左下角: ![](http://img.smyhvae.com/20180115_2121.png) 为了理解“**首屏**”二字的含义,我们来看一下动态图: ![](https://img.smyhvae.com/20180115_2200.gif) 问题: ![](http://img.smyhvae.com/20180115_2131.png) 答案: 用bottom的定位的时候,参考的是浏览器首屏大小对应的页面左下角。 ![](http://img.smyhvae.com/20180115_2132.png) ### 以盒子为参考点 一个绝对定位的元素,如果父辈元素中也出现了已定位(无论是绝对定位、相对定位,还是固定定位)的元素,那么将以父辈这个元素,为参考点。 如下:(子绝父相) ![](http://img.smyhvae.com/20180115_2210.png) 以下几点需要注意。 (1) 要听最近的已经定位的祖先元素的,不一定是父亲,可能是爷爷: ```
            相对定位
            没有定位

            绝对定位,将以box1为参考,因为box2没有定位,box1就是最近的父辈元素
            ``` 再比如: ```
            相对定位
            相对定位

            绝对定位,将以box2为参考,因为box2是自己最近的父辈元素
            ``` (2)不一定是相对定位,任何定位,都可以作为儿子的参考点: 子绝父绝、**子绝父相**、子绝父固,都是可以给儿子定位的。但是在工程上,如果子绝、父绝,没有一个盒子在标准流里面了,所以页面就不稳固,没有任何实战用途。 **工程应用:** “**子绝父相**”有意义:这样可以保证父亲没有脱标,儿子脱标在父亲的范围里面移动。于是,工程上经常这样做: > 父亲浮动,设置相对定位(零偏移),然后让儿子绝对定位一定的距离。 (3)绝对定位的儿子,无视参考的那个盒子的padding: 下图中,绿色部分是父亲div的padding,蓝色部分p是div的内容区域。此时,如果div相对定位,p绝对定位,那么, p将无视父亲的padding,在border内侧为参考点,进行定位: ![](http://img.smyhvae.com/20180116_0812.png) **工程应用:** 绝对定位非常适合用来做“压盖”效果。我们来举个lagou.com上的例子。 现在有如下两张图片素材: ![](http://img.smyhvae.com/20180116_1115.png) ![](http://img.smyhvae.com/20180116_1116.jpg) 要求作出如下效果: ![](http://img.smyhvae.com/20180116_1117.png) 代码实现如下: ```html Document

            广东深圳宝安区建安一路海雅缤纷城4楼

            ``` 代码解释如下: - 为了显示“多套餐”那个小图,我们需要用到精灵图。 - “多套餐”下方黑色背景的文字都是通过“子绝父相”的方式的盖在大海报image的上方的。 代码的效果如下: ![](http://img.smyhvae.com/20180116_1335.png) ### 让绝对定位中的盒子在父亲里居中 我们知道,如果想让一个**标准流中的盒子在父亲里居中**(水平方向看),可以将其设置`margin: 0 auto`属性。 可如果盒子是绝对定位的,此时已经脱标了,如果还想让其居中(位于父亲的正中间),可以这样做: ``` div { width: 600px; height: 60px; position: absolute; 绝对定位的盒子 left: 50%; 首先,让左边线居中 top: 0; margin-left: -300px; 然后,向左移动宽度(600px)的一半 } ``` 如上方代码所示,我们先让这个宽度为600px的盒子,左边线居中,然后向左移动宽度(600px)的一半,就达到效果了。 ![](http://img.smyhvae.com/20180116_1356.png) 我们可以总结成一个公式: > left:50%; margin-left:负的宽度的一半 ## 固定定位 **固定定位**:就是相对浏览器窗口进行定位。无论页面如何滚动,这个盒子显示的位置不变。 备注:IE6不兼容。 **用途1**:网页右下角的“返回到顶部” 比如我们经常看到的网页右下角显示的“返回到顶部”,就可以固定定位。 ```html ``` **用途2:**顶部导航条 我们经常能看到固定在网页顶端的导航条,可以用固定定位来做。 需要注意的是,假设顶部导航条的高度是60px,那么,为了防止其他的内容被导航条覆盖,我们要给body标签设置60px的padding-top。 顶部导航条的实现如下: ```html Document ``` ### 5、z-index属性: **z-index**属性:表示谁压着谁。数值大的压盖住数值小的。 有如下特性: (1)属性值大的位于上层,属性值小的位于下层。 (2)z-index值没有单位,就是一个正整数。默认的z-index值是0。 (3)如果大家都没有z-index值,或者z-index值一样,那么在HTML代码里写在后面,谁就在上面能压住别人。定位了的元素,永远能够压住没有定位的元素。 (4)只有定位了的元素,才能有z-index值。也就是说,不管相对定位、绝对定位、固定定位,都可以使用z-index值。**而浮动的元素不能用**。 (5)从父现象:父亲怂了,儿子再牛逼也没用。意思是,如果父亲1比父亲2大,那么,即使儿子1比儿子2小,儿子1也能在最上层。 针对(1)(2)(3)条,举例如下: 这是默认情况下的例子:(div2在上层,div1在下层) ![](http://img.smyhvae.com/2015-10-03-css-32.png) 现在加一个`z-index`属性,要求效果如下: ![](http://img.smyhvae.com/2015-10-03-css-33.png) 第五条分析: ![](http://img.smyhvae.com/20180116_1445.png) z-index属性的应用还是很广泛的。当好几个已定位的标签出现覆盖的现象时,我们可以用这个z-index属性决定,谁处于最上方。也就是**层级**的应用。 **层级:** (1)必须有定位(除去static) (2)用`z-index`来控制层级数。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/09-CSS案例讲解:博雅互动.md ================================================ --- title: 09-CSS案例讲解:博雅互动 publish: true --- ## 前言 > CSS已经学了一些基础内容了,我们来讲解一个小案例吧。以[博雅互动](http://www.boyaa.com/)的官网首页举例。 ### 版心 首页的**版心**如下: ![](http://img.smyhvae.com/20170813_1535.png) 这里我们要普及一个概念,叫“[版心](https://baike.baidu.com/item/%E7%89%88%E5%BF%83)”。**版心是页面中主要内容所在的区域。** 比如说,网站左上角的logo,设计图给出的左边距是143像素,此时,我们千万不要以为,logo的左边距真的是143像素。因为设计图只是一个版心;而整个页面是处于浏览器的中间,浏览器的宽度是可以随时调整的。 我们量一下中间四个方形图的width,是1000px,所以,网页版心的宽度是1000px。 ### 网页的结构 从结构上来看,网页分为头部(导航栏)、banner区、内容区、底部。 ## 导航栏的制作 在此我们只讲基础知识的使用,不涉及浏览器的优化。 `class==header`这个div是顶部的通栏,我们在里面放一个1000px宽的div,作为通栏的版心,我一般把这个版心称为`class=inner_c`,c指的是center。 `class=inner_c`不需要给高,因为它可以被内容撑高。 现在我们需要在`class=inner_c`里放三个东西:左侧的logo、中间的导航栏、右侧的“加入我们”。 接下来我们开始做右侧的「加入我们」,「加入我们」的背景是带圆角的矩形,这个圆角,实现的方式有两种:要么切图,要么用CSS3实现(IE 7、IE 8不兼容)。我们暂时使用切图来实现。 我们最好把「加入我们」这个超链接``放到`div`里,然后设置div的margin和padding,而不是直接设置``的边距。 我们起个名字叫`class=jrwm`是没有问题的,这在工作当中很常见,如果写成`class=join_us`反倒很别扭。 暂时我们的做法是: - (1)给`class=jrwm_box`这个div里放一个`class=jrwm`的div。`class=jrwm`用来放绿色的背景图片。 - (2)在`class=jrwm`里放一个超链接,并将超链接转为块级元素。 最终,导航栏的代码如下: ```html Document ``` 导航栏的效果如下: ![](http://img.smyhvae.com/20180114_1332.gif) ## banenr图 > 因为涉及到 js 的内容,这里先不讲内容区域**轮播图**的效果。 我们首先在导航条和banner图之间加一道墙,即`class=cl`,然后采用隔墙法对其设置`clear: both;`的属性。 然后设置banner的背景图片属性,添加banner图。 ## 内容区域的制作 导航栏+banner+内容区域的完整代码如下: ```html Document
            ``` 代码解释: (1)导航栏,左侧的logo: **错误的写法:** 可能会有人直接将img标签作为logo的布局: ```html ``` 然后将img的样式设置为: ```css .header .logo{ float: left; margin-right: 40px; } ``` 这样写虽然视觉效果上达到了,但是搜索引擎是搜不到图片的,不利于SEO。 **正确的写法:** 正确的写法是将超链接作为logo的布局,里面放入文字(文字可以被SEO): ```html

            博雅互动-世界上最好的游戏公司

            ``` 然后将**logo设置为背景图**: ```css .header .logo{ float: left; padding-left: 12px; margin-right: 39px; width: 174px; height: 58px; } .header .logo a{ display: block; width: 174px; height: 58px; background:url(images/logo.png) no-repeat; text-indent: -999em; } ``` 由于搜索引擎是搜不到图片的,所以一定要把“博雅互动”这几个文字加上去,**然后通过`text-indent`缩进的属性把文字赶走到视线以外的地方**。这是做搜索引擎优化的一个重要的技巧。 另外,背景要放在里层的a标签里,不要放在外层的h1标签里。假设背景图放在h1里,那么不管h1的padding有多大,背景图的位置都不会变。 (1)内容区域,“点击播放”右侧的小三角形: 我们在“点击播放”的右侧放了一个三角形。这个很有技巧。 ![](http://img.smyhvae.com/20180115_1356.png) 代码截取如下: ```css .content .product ul li p.djbf a{ font-size: 12px; color:#38B774; text-decoration: none; background:url(images/sanjiaoxing.png) no-repeat right center; padding-right: 12px; } ``` 上方代码中,我们在第6行给“点击播放”这个超链接加一个右padding(很关键),然后在第5行把小三角这个背景图放在右padding的位置,就能达到想要的视觉效果。 (2)导航栏+banner+内容区域的效果如下: ![](http://img.smyhvae.com/20180114_1405.png) 工程文件:[2018-03-20-boya.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-03-20-boya.rar) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/10-CSS3选择器详解.md ================================================ --- title: 10-CSS3选择器详解 publish: true --- ## CSS3介绍 CSS3在CSS2基础上,**增强**或**新增**了许多特性, 弥补了CSS2的众多不足之处,使得Web开发变得更为高效和便捷。 ### CSS3的现状 - 浏览器支持程度不够好,有些需要添加**私有前缀** - 移动端支持优于PC端 - 不断改进中 - 应用相对广泛 ### 应对的策略:渐进增强 - (1)坚持**渐进增强**的原则:**让低版本浏览器能正常访问页面,高版本的浏览器用户体验更好**。【重要】 比如说,同样是一个头像,可能在低版本的浏览器中,头像方的;在高版本的浏览器中,头像是圆的。 - (2)考虑用户群体。 - (3)遵照产品的方案。 参考链接: - [渐进增强 VS 优雅降级 | 简书](https://www.jianshu.com/p/d313f1108862) - [渐进增强和优雅降级之间的不同(面试题目)](https://www.cnblogs.com/iceflorence/archive/2017/03/27/6625466.html) ### 浏览器的版本问题 由于CSS3普遍存在兼容性问题,为了避免因兼容性带来的干扰,浏览器的建议版本为: - Chrome浏览器 version 46+ - Firefox浏览器 firefox 42+ ### 如何使用手册 CSS参考手册的网址: CSS参考手册的下载链接: 在查看[CSS参考手册](http://download.csdn.net/download/smyhvae/10243974)时,需要注意以下符号: ![](http://img.smyhvae.com/20180206_2150.png) 比如说,`{1,4}`表示可以设置一至四个参数。 下面讲CSS3的基础知识。本文讲一下 CSS3 选择器的内容。 ## CSS3 选择器 我们之前学过 CSS 的选择器,比如: ``` div 标签选择器 .box 类名选择器 #box id选择器 div p 后代选择器 div.box 交集选择器 div,p,span 并集选择器 div>p 子代选择器 * : 通配符 div+p: 选中div后面相邻的第一个p div~p: 选中的div后面所有的p ``` CSS3新增了许多**灵活**查找元素的方法,极大的提高了查找元素的效率和**精准度**。CSS3选择器与 jQuery 中所提供的**绝大部分**选择器兼容。 ### 属性选择器 属性选择器的标志性符号是 `[]`。 匹配含义: ``` ^:开头 $:结尾 *:包含 ``` 格式: - `E[title]` 选中页面的E元素,并且E存在 title 属性即可。 - `E[title="abc"]`选中页面的E元素,并且E需要带有title属性,且属性值**完全等于**abc。 - `E[attr~=val]` 选择具有 att 属性且属性值为:用空格分隔的字词列表,其中一个等于 val 的E元素。 - `E[attr|=val]` 表示要么是一个单独的属性值,要么这个属性值是以“-”分隔的。 - `E[title^="abc"]` 选中页面的E元素,并且E需要带有 title 属性,属性值以 abc 开头。 - `E[title$="abc"]` 选中页面的E元素,并且E需要带有 title 属性,属性值以 abc 结尾。 - `E[title*="abc"]` 选中页面的E元素,并且E需要带有 title 属性,属性值任意位置包含abc。 比如说,我们用属性选择器去匹配标签的className,是非常方便的。 这里我们用class属性来举例。代码举例: ```html 选择器 - 属性
            CSS3-属性选择器
            简介
            • 姓名:
            • 密码:
            • 性别:
            • 兴趣: 写代码
            E[attr]
            E[attr~=attr]
            E[attr|=attr]
            E[attr*=val]
            E[attr^=val]
            E[attr$=val]
            ``` 最后来张表格: ![](http://img.smyhvae.com/20180207_1500.png) ### 结构伪类选择器 伪类选择器的标志性符号是 `:`。 CSS中有一些伪类选择器,比如`:link`、`:visited`、`:hover`、`:active`、`:focus`。 CSS3又新增了其它的伪类选择器。这一小段,我们来学习CSS3中的**结构伪类选择器**:即通过**结构**来进行筛选。 **1、格式:(第一部分)**(重要) - `E:first-child` 匹配父元素的第一个子元素E。 - `E:last-child` 匹配父元素的最后一个子元素E。 - `E:nth-child(n)` 匹配父元素的第n个子元素E。**注意**,盒子的编号是从`1`开始算起,不是从`0`开始算起。 - `E:nth-child(odd)` 匹配奇数 - `E:nth-child(even)` 匹配偶数 - `E:nth-last-child(n)` 匹配父元素的倒数第n个子元素E。 理解: (1)这里我们要好好理解**父元素**的含义,它指的是:以 E 元素的父元素为参考。 (2)注意:以上选择器中所选到的元素的类型,必须是指定的类型E,如果选不中,则无效。这个要好好理解,具体可以看CSS参考手册中的`E:nth-child(n)`的示例。我们可以理解成:**先根据选择器找到选中的全部位置,如果发现某个位置不是类型E,则该位置失效**。 (3)另外,`E:nth-child(n)`这个属性也很有意思。比如,针对下面这样一组标签: ```html
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            ``` 上方代码中: - 如果选择器写成`li:nth-child(2)`,则表示第2个 `li`。 - 如果选择器写成`li:nth-child(n)`,则表示**所有的**`li`。因为此时的 `n` 表示 0,1,2,3,4,5,6,7,8.....(当n小于1时无效,因为n = 0 也是不会选中的) - 如果选择器写成`li:nth-child(2n)`,则表示所有的**第偶数个** li。 - 如果选择器写成`li:nth-child(2n+1)`,则表示所有的**第奇数个** li。 - 如果选择器写成`li:nth-child(-n+5)`,则表示**前5个** li。 - 如果选择器写成`li:nth-last-child(-n+5)`,则表示**最后5个** li。 - 如果选择器写成`li:nth-child(7n)`,则表示选中7的倍数。。 上面列举的选择器中,我们只要记住: `n` 表示 0,1,2,3,4,5,6,7,8.....就很容易明白了。 **2、格式:(第二部分)** - `E:first-of-type` 匹配同类型中的第一个同级兄弟元素E。 - `E:last-of-type` 匹配同类型中的最后一个同级兄弟元素E。 - `E:nth-of-type(n)` 匹配同类型中的第n个同级兄弟元素E。 - `E:nth-last-of-type(n)` 匹配同类型中的倒数第n个同级兄弟元素E。 既然上面这几个选择器带有`type`,我们可以这样理解:先在同级里找到所有的E类型,然后根据 n 进行匹配。 **3、格式:(第三部分)** - `E:empty` 匹配没有任何子节点(包括空格等text节点)的元素E。 - `E:target` 匹配相关URL指向的E元素。要配合锚点使用。 **举例:** 我们可以把多个伪类选择器结合起来使用,比如: ![](http://img.smyhvae.com/20180207_1340.png) 如果想把上图中,第一行的前三个 span 标红,我们可以这样使用结构伪类选择器: ```css dt:first-child span:nth-of-type(-n+3) { color: red; } ``` 最后来张表格: ![](http://img.smyhvae.com/20180207_1502.png) ### 伪元素选择器 伪元素选择器的标志性符号是 `::`。 **1、格式:(第一部分)** - `E::before` 设置在 元素E 前面(依据对象树的逻辑结构)的内容,配合content属性一起使用。 - `E::after` 设置在 元素E 后面(依据对象树的逻辑结构)的内容,配合content属性一起使用。 `E:after`、`E:before `在旧版本里是伪类,在 CSS3 这个新版本里是伪元素。新版本里,`E:after`、`E:before`会被自动识别为`E::after`、`E::before`,按伪元素来对待,这样做的目的是用来做兼容处理。 举例: ```html 生命壹号 ``` 效果如下: ![](http://img.smyhvae.com/20180207_1409.png) **上图可以看出**: - 通过伪元素选择器,就可以添加出类似于span标签的效果(记得要结合 content 属性使用)。 - 通过这两个属性添加的伪元素,是**行内元素**,需要转换成块元素才能设置宽高。 **2、格式:(第二部分)** - `E::first-letter` 设置元素 E 里面的**第一个字符**的样式。 - `E::first-line` 设置元素 E 里面的**第一行**的样式。 - `E::selection` 设置元素 E 里面被鼠标选中的区域的样式(一般设置颜色和背景色)。 `E::first-letter` 的举例: ![](http://img.smyhvae.com/20180207_1430.png) `E::first-line`的举例: ![](http://img.smyhvae.com/20180207_1433.png) 最后来张表格: ![](http://img.smyhvae.com/20180207_1503.png) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/11-CSS3属性详解(一).md ================================================ --- title: 11-CSS3属性详解(一) publish: true --- ## 前言 我们在上一篇文章中学习了[CSS3的选择器](http://www.cnblogs.com/smyhvae/p/8426799.html),本文来学一下CSS3的一些属性。 本文主要内容: - 文本 - 盒模型中的 box-sizing 属性 - 处理兼容性问题:私有前缀 - 边框 - 背景属性 - 渐变 ## 文本 ### text-shadow:设置文本的阴影 格式举例: ```javascript text-shadow: 20px 27px 22px pink; ``` 参数解释:水平位移 垂直位移 模糊程度 阴影颜色。 效果举例: ![](http://img.smyhvae.com/20180207_1600.png) ### 举例:凹凸文字效果 text-shadow 可以设置多个阴影,每个阴影之间使用逗号隔开。我们来看个例子。 代码如下: ```html
            生命壹号
            生命壹号
            ``` 效果如下: ![](http://img.smyhvae.com/20180207_1617.png) 上图中,实现凹凸文字效果的方式比较简单,给左上角放黑色的阴影,右下角放白色的阴影,就达到了凹下去的效果。 ## 盒模型中的 box-sizing 属性 我们在**[之前的文章](http://www.cnblogs.com/smyhvae/p/7256371.html)**中专门讲过盒子模型。 CSS3 对盒模型做出了新的定义,即允许开发人员**指定盒子宽度和高度的计算方式**。 这就需要用到 `box-sizing`属性。它的属性值可以是:`content-box`、`border-box`。解释如下。 **外加模式:**(css的默认方式) ```javascript box-sizing: content-box; ``` 解释:此时设置的 width 和 height 是**内容区域**的宽高。`盒子的实际宽度 = 设置的 width + padding + border`。此时改变 padding 和 border 的大小,也不会改变内容的宽高,而是盒子的总宽高发生变化。 **内减模式:**【需要注意】 ```javascript box-sizing: border-box; ``` 解释:此时设置的 width 和 height 是**盒子**的总宽高。`盒子的实际宽度 = 设置的 width`。此时改变 padding 和 border 的大小,会改变内容的宽高,盒子的总宽高不变。 ## 处理兼容性问题:私有前缀 通过网址 可以查询CSS3各特性的支持程度。 处理兼容性问题的常见方法:为属性添加**私有前缀**。 如此方法不能解决,应尽量避免使用,无需刻意去处理CSS3的兼容性问题。 **私有前缀的举例**: 比如说,我想给指定的div设置下面这样一个属性: ```css background: linear-gradient(left, green, yellow); ``` 上面这个属性的作用是:添加从左到右的线性渐变,颜色从绿色变为黄色。 如果直接这样写属性,是看不到效果的: ![](http://img.smyhvae.com/20180207_1700.png) 此时,我们可以**为浏览器添加不同的私有前缀**,属性就可以生效了。 格式如下: ```html -webkit-: 谷歌 苹果 -moz-:火狐 -ms-:IE -o-:欧朋 ``` 格式举例如下: ```css background: -webkit-linear-gradient(left, green, yellow); background: -moz-linear-gradient(left, green, yellow); background: -ms-linear-gradient(left, green, yellow); background: -o-linear-gradient(left, green, yellow); background: linear-gradient(left, green, yellow); ``` 效果如下: ![](http://img.smyhvae.com/20180207_1710.png) ## 边框 边框的属性很多,其中**边框圆角**和**边框阴影**这两个属性,应用十分广泛,兼容性也相对较好,且符合**渐进增强**的原则,需要重点熟悉。 ### 边框圆角:`border-radius` 属性 边框的每个圆角,本质上是一个圆,圆有**水平半径**和**垂直半径**:如果二者相等,就是圆;如果二者不等, 就是椭圆。 单个属性的写法: ```css border-top-left-radius: 60px 120px; //参数解释:水平半径 垂直半径 border-top-right-radius: 60px 120px; border-bottom-left-radius: 60px 120px; border-bottom-right-radius: 60px 120px; ``` 复合写法: ``` border-radius: 60px/120px; //参数:水平半径/垂直半径 border-radius: 20px 60px 100px 140px; //从左上开始,顺时针赋值。如果当前角没有值,取对角的值 border-radius: 20px 60px; ``` 最简洁的写法:(四个角的半径都相同时) ``` border-radius: 60px; ``` 举例: ```html
            1
            2
            3
            4
            5
            6
            ``` 效果如下: ![](http://img.smyhvae.com/20180207_1750.png) ### 边框阴影:`box-shadow` 属性 格式举例: ```javascript box-shadow: 水平偏移 垂直偏移 模糊程度 阴影大小 阴影颜色 box-shadow: 15px 21px 48px -2px #666; ``` 参数解释: - 水平偏移:正值向右 负值向左。 - 垂直偏移:正值向下 负值向上。 - 模糊程度:不能为负值。 效果如下: ![](http://img.smyhvae.com/20180207_2027.png) 另外,后面还可以再加一个inset属性,表示内阴影。如果不写,则默认表示外阴影。例如: ```javascript box-shadow:3px 3px 3px 3px #666 inset; ``` 效果如下: ![](http://img.smyhvae.com/20180207_2028.png) **注意**:设置边框阴影不会改变盒子的大小,即不会影响其兄弟元素的布局。 我们还可以设置多重边框阴影,实现更好的效果,增强立体感。 ### 边框图片 边框图片有以下属性: ```javascript /* 边框图片的路径*/ border-image-source: url("images/border.png"); /* 图片边框的裁剪*/ border-image-slice: 27; /*图片边框的宽度*/ border-image-width: 27px; /*边框图片的平铺*/ /* repeat :正常平铺 但是可能会显示不完整*/ /*round: 平铺 但是保证 图片完整*/ /*stretch: 拉伸显示*/ border-image-repeat: stretch; ``` 我们也可以写成一个综合属性: ```javascript border-image: url("images/border.png") 27/20px round; ``` 这个属性要好好理解,我们假设拿下面这张图来作为边框图片: ![](http://img.smyhvae.com/20180207_2045.png) ![](http://img.smyhvae.com/20180207_2046.png) 这张图片将会被“切割”成**九宫格**形式,然后进行平铺。四个角位置、形状保持不变,中心位置和水平垂直向两个方向平铺: ![](http://img.smyhvae.com/20180207_2050.png) 再具体一点: ![](http://img.smyhvae.com/20180207_2051.png) ### 常见的边框图片汇总 ```html ``` CSS3的更多属性,且看下文继续。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/12-CSS3属性详解:动画详解.md ================================================ --- title: 12-CSS3属性详解:动画详解 publish: true --- ## 前言 本文主要内容: - 过渡:transition - 2D 转换 transform - 3D 转换 transform - 动画:animation ## 过渡:transition `transition`的中文含义是**过渡**。过渡是CSS3中具有颠覆性的一个特征,可以实现元素**不同状态间的平滑过渡**(补间动画),经常用来制作动画效果。 - 补间动画:自动完成从起始状态到终止状态的的过渡。不用管中间的状态。 - 帧动画:通过一帧一帧的画面按照固定顺序和速度播放。如电影胶片。 参考链接:[补间动画基础](http://mux.alimama.com/posts/1009) transition 包括以下属性: - `transition-property: all;` 如果希望所有的属性都发生过渡,就使用all。 - `transition-duration: 1s;` 过渡的持续时间。 - `transition-timing-function: linear;` 运动曲线。属性值可以是: - `linear` 线性 - `ease` 减速 - `ease-in` 加速 - `ease-out` 减速 - `ease-in-out` 先加速后减速 - `transition-delay: 1s;` 过渡延迟。多长时间后再执行这个过渡动画。 上面的四个属性也可以写成**综合属性**: ```javascript transition: 让哪些属性进行过度 过渡的持续时间 运动曲线 延迟时间; transition: all 3s linear 0s; ``` 其中,`transition-property`这个属性是尤其需要注意的,不同的属性值有不同的现象。我们来示范一下。 如果设置 `transition-property: width`,意思是只让盒子的宽度在变化时进行过渡。效果如下: ![](http://img.smyhvae.com/20180208_1440.gif) 如果设置 `transition-property: all`,意思是让盒子的所有属性(包括宽度、背景色等)在变化时都进行过渡。效果如下: ![](http://img.smyhvae.com/20180208_1445.gif) ### 案例:小米商品详情 代码: ```html CSS 过渡
            ``` 效果如下: ![](http://img.smyhvae.com/20180208_1500.gif) 动画效果录制的比较差,但真实体验还是可以的。 工程文件:[2018-02-08-小米商品详情过渡](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-08-%E5%B0%8F%E7%B1%B3%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E8%BF%87%E6%B8%A1.rar) ## 2D 转换 **转换**是 CSS3 中具有颠覆性的一个特征,可以实现元素的**位移、旋转、变形、缩放**,甚至支持矩阵方式。 转换再配合过渡和动画,可以取代大量早期只能靠 Flash 才可以实现的效果。 在 CSS3 当中,通过 `transform` 转换来实现 2D 转换或者 3D 转换。 - 2D转换包括:缩放、移动、旋转。 我们依次来讲解。 ### 1、缩放:`scale` 格式: ```javascript transform: scale(x, y); transform: scale(2, 0.5); ``` 参数解释: x:表示水平方向的缩放倍数。y:表示垂直方向的缩放倍数。如果只写一个值就是等比例缩放。 取值:大于1表示放大,小于1表示缩小。不能为百分比。 格式举例: ```html
            1
            2
            3
            ``` 效果: ![](http://img.smyhvae.com/20180208_1551.gif) 上图可以看到,给 box1 设置 2D 转换,并不会把兄弟元素挤走。 ### 2、位移:translate 格式: ```javascript transform: translate(水平位移, 垂直位移); transform: translate(-50%, -50%); ``` 参数解释: - 参数为百分比,相对于自身移动。 - 正值:向右和向下。 负值:向左和向上。如果只写一个值,则表示水平移动。 格式举例: ```html
            1
            2
            3
            ``` 效果: ![](http://img.smyhvae.com/20180208_1600.gif) 上图中,因为我在操作的时候,鼠标悬停后,立即进行了略微的移动,所以产生了两次动画。正确的效果应该是下面这样的: ![](http://img.smyhvae.com/20180208_1605.gif) **应用:**让绝对定位中的盒子在父亲里居中 我们知道,如果想让一个**标准流中的盒子在父亲里居中**(水平方向看),可以将其设置`margin: 0 auto`属性。 可如果盒子是绝对定位的,此时已经脱标了,如果还想让其居中(位于父亲的正中间),可以这样做: ``` div { width: 600px; height: 60px; position: absolute; 绝对定位的盒子 left: 50%; 首先,让左边线居中 top: 0; margin-left: -300px; 然后,向左移动宽度(600px)的一半 } ``` 如上方代码所示,我们先让这个宽度为600px的盒子,左边线居中,然后向左移动宽度(600px)的一半,就达到效果了。 ![](http://img.smyhvae.com/20180116_1356.png) 现在,我们还可以利用偏移 translate 来做,这也是比较推荐的写法: ```javascript div { width: 600px; height: 60px; background-color: red; position: absolute; 绝对定位的盒子 left: 50%; 首先,让左边线居中 top: 0; transform: translate(-50%); 然后,利用translate,往左走自己宽度的一半【推荐写法】 } ``` ### 3、旋转:rotate 格式: ```javascript transform: rotate(角度); transform: rotate(45deg); ``` 参数解释:正值 顺时针;负值:逆时针。 举例: ```html
            1

    ``` 效果: ![](http://img.smyhvae.com/20180208_1630.gif) 注意,上方代码中,我们给盒子设置了 transform 中的 rotate 旋转,但同时还要给盒子设置 transition 过渡。如果没有这行过渡的代码,旋转会直接一步到位,效果如下:(不是我们期望的效果) ![](http://img.smyhvae.com/20180208_1635.gif) **案例1:**小火箭 ```html ``` 上方代码中,我们将 transform 的两个小属性合并起来写了。 小火箭图片的url: **案例2:**扑克牌 rotate 旋转时,默认是以盒子的正中心为坐标原点的。如果想**改变旋转的坐标原点**,可以用`transform-origin`属性。格式如下: ```javascript transform-origin: 水平坐标 垂直坐标; transform-origin: 50px 50px; transform-origin: center bottom; //旋转时,以盒子底部的中心为坐标原点 ``` 我们来看一下 rotate 结合 transform-origin 的用法举例。 代码如下: ```html
    ``` 效果如下: ![](https://img.smyhvae.com/20180208_1650.gif) ### 4、倾斜 暂略。 ## 3D 转换 ### 1、旋转:rotateX、rotateY、rotateZ **3D坐标系(左手坐标系)** ![](http://img.smyhvae.com/20180208_2010.png) 如上图所示,伸出左手,让拇指和食指成“L”形,大拇指向右,食指向上,中指指向前方。拇指、食指和中指分别代表X、Y、Z轴的正方向,这样我们就建立了一个左手坐标系。 浏览器的这个平面,是X轴、Y轴;垂直于浏览器的平面,是Z轴。 **旋转的方向:(左手法则)** 左手握住旋转轴,竖起拇指指向旋转轴的**正方向**,正向就是**其余手指卷曲的方向**。 从上面这句话,我们也能看出:所有的3d旋转,对着正方向去看,都是顺时针旋转。 **格式:** ```javascript transform: rotateX(360deg); //绕 X 轴旋转360度 transform: rotateY(360deg); //绕 Y 轴旋转360度 transform: rotateZ(360deg); //绕 Z 轴旋转360度 ``` **格式举例:** (1)rotateX 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180208_2025.gif) 上方代码中,我们最好加个透视的属性,方能看到3D的效果;没有这个属性的话,图片旋转的时候,像是压瘪了一样。 而且,透视的是要加给图片的父元素 div,方能生效。我们在后面会讲解透视属性。 (2)rotateY 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180208_2030.gif) (3)rotateZ 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180208_2035.gif) **案例**:百度钱包的水平翻转效果 现在有下面这张图片素材: ![](http://img.smyhvae.com/20180208_2055.png) 要求做成下面这种效果: ![](http://img.smyhvae.com/20180208_2100.gif) 上面这张图片素材其实用的是精灵图。实现的代码如下: ```html
    ``` ### 2、移动:translateX、translateY、translateZ **格式:** ```javascript transform: translateX(100px); //沿着 X 轴移动 transform: translateY(360px); //沿着 Y 轴移动 transform: translateZ(360px); //沿着 Z 轴移动 ``` **格式举例:** (1)translateX 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180208_2036.gif) (2)translateY 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180208_2037.gif) (3)translateZ 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180208_2040.gif) 上方代码中,如果不加透视属性,是看不到translateZ的效果的。 ### 3、透视:perspective 电脑显示屏是一个 2D 平面,图像之所以具有立体感(3D效果),其实只是一种视觉呈现,通过透视可以实现此目的。 透视可以将一个2D平面,在转换的过程当中,呈现3D效果。但仅仅只是视觉呈现出 3d 效果,并不是正真的3d。 格式有两种写法: - 作为一个属性,设置给父元素,作用于所有3D转换的子元素 - 作为 transform 属性的一个值,做用于元素自身。 格式举例: ```css perspective: 500px; ``` ### 4、3D呈现(transform-style) 3D元素构建是指某个图形是由多个元素构成的,可以给这些元素的**父元素**设置`transform-style: preserve-3d`来使其变成一个真正的3D图形。属性值可以如下: ```css transform-style: preserve-3d; /* 让 子盒子 位于三维空间里 */ transform-style: flat; /* 让子盒子位于此元素所在的平面内(子盒子被扁平化) */ ``` **案例:**立方体 ```html
    ``` ## 动画 动画是CSS3中具有颠覆性的特征,可通过设置**多个节点** 来精确控制一个或一组动画,常用来实现**复杂**的动画效果。 ### 1、定义动画的步骤 (1)通过@keyframes定义动画; (2)将这段动画通过百分比,分割成多个节点;然后各节点中分别定义各属性; (3)在指定元素里,通过 `animation` 属性调用动画。 之前,我们在 js 中定义一个函数的时候,是先定义,再调用: ```javascript js 定义函数: function fun(){ 函数体 } 调用: fun(); ``` 同样,我们在 CSS3 中**定义动画**的时候,也是**先定义,再调用**: ```javascript 定义动画: @keyframes 动画名{ from{ 初始状态 } to{ 结束状态 } } 调用: animation: 动画名称 持续时间; ``` 其中,animation属性的格式如下: ```javascript animation: 定义的动画名称 持续时间 执行次数 是否反向 运动曲线 延迟执行。(infinite 表示无限次) animation: move1 1s alternate linear 3; animation: move2 4s; ``` **定义动画的格式举例:** ```html
    ``` 注意好好看代码中的注释。 效果如下: ![](http://img.smyhvae.com/20180209_1001.gif) ### 2、动画属性 我们刚刚在调用动画时,animation属性的格式如下: animation属性的格式如下: ```javascript animation: 定义的动画名称 持续时间 执行次数 是否反向 运动曲线 延迟执行。(infinite 表示无限次) animation: move1 1s alternate linear 3; animation: move2 4s; ``` 可以看出,这里的 animation 是综合属性,接下来,我们把这个综合属性拆分看看。 (1)动画名称: ```javascript animation-name: move; ``` (2)执行一次动画的持续时间: ```javascript animation-duration: 4s; ``` 备注:上面两个属性,是必选项,且顺序固定。 (3)动画的执行次数: ```javascript animation-iteration-count: 1; //iteration的含义表示迭代 ``` 属性值`infinite`表示无数次。 (3)动画的方向: ```javascript animation-direction: alternate; ``` 属性值:normal 正常,alternate 反向。 (4)动画延迟执行: ```javascript animation-delay: 1s; ``` (5)设置动画结束时,盒子的状态: ```javascript animation-fill-mode: forwards; ``` 属性值: forwards:保持动画结束后的状态(默认), backwards:动画结束后回到最初的状态。 (6)运动曲线: ``` animation-timing-function: ease-in; ``` 属性值可以是:linear ease-in-out steps()等。 注意,如果把属性值写成**` steps()`**,则表示动画**不是连续执行**,而是间断地分成几步执行。我们接下来专门讲一下属性值 `steps()`。 ### steps()的效果 我们还是拿上面的例子来举例,如果在调用动画时,我们写成: ```javascript animation: move2 4s steps(2); ``` 效果如下: ![](http://img.smyhvae.com/20180209_1020.gif) 有了属性值 `steps()`,我们就可以作出很多不连续地动画效果。比如时钟;再比如,通过多张静态的鱼,作出一张游动的鱼。 **step()举例:**时钟的简易模型 ```html
    ``` 上方代码,我们通过一个黑色的长条div,旋转360度,耗时60s,分成60步完成。即可实现。 效果如下: ![](http://img.smyhvae.com/20180209_1030.gif) ### 动画举例:摆动的鱼 现在,我们要做下面这种效果: ![](http://img.smyhvae.com/20180209_1245.gif) PS:图片的url是,图片较大,如无法观看,可在浏览器中单独打开。 为了作出上面这种效果,要分成两步。 **(1)第一步**:让鱼在原地摆动 鱼在原地摆动并不是一张 gif动图,她其实是由很多张静态图间隔地播放,一秒钟播放完毕,就可以了: ![](http://img.smyhvae.com/20180209_shark.png) 上面这张大图的尺寸是:宽 509 px、高 2160 px。 我们可以理解成,每一帧的尺寸是:宽 509 px、高 270 px。`270 * 8 = 2160`。让上面这张大图,在一秒内从 0px 的位置往上移动2160px,分成8步来移动。就可以实现了。 代码是: ```html
    ``` 效果如下: ![](http://img.smyhvae.com/20180209_1250.gif) 我们不妨把上面的动画的持续时间从`1s`改成 `8s`,就可以看到动画的慢镜头: ![](http://img.smyhvae.com/20180209_1330.gif) 这下,你应该恍然大悟了。 **(2)第二步**:让鱼所在的盒子向前移动。 实现的原理也很简单,我们在上一步中已经让`shark`这个盒子实现了原地摇摆,现在,让 shark 所在的父盒子 `sharkBox`向前移动,即可。完整版代码是: ```html
    ``` ![](http://img.smyhvae.com/20180209_1350.gif) 大功告成。 工程文件:[2018-02-09-fishes.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-09-fishes.rar) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/13-CSS3属性:Flex布局图文详解.md ================================================ --- title: 13-CSS3属性:Flex布局图文详解 publish: true --- ## 前言 CSS3中的 flex 属性,在布局方面做了非常大的改进,使得我们对**多个元素之间**的布局排列变得十分灵活,适应性非常强。其强大的伸缩性和自适应性,在网页开中可以发挥极大的作用。 ### flex 初体验 我们先来看看下面这个最简单的布局: ![](http://img.smyhvae.com/20191009_1555.png) 上面这张图中的布局是我们都熟悉的:默认文档流中,在一个父容器里放置多个块级的子元素,那么,这些子元素会默认从上往下排列。 在此基础之上,如果我给父容器仅仅加一个 `display: flex`属性,此时,这些子元素的布局会摇身一变: ![](http://img.smyhvae.com/20191009_1600.png) 没错,子元素们会**在水平方向上,从左至右排列**,就是这么神奇。到此为止,你已经掌握了关于 flex 的一半的知识。 ### flex 布局的优势 1、**flex 布局的子元素不会脱离文档流**,很好地遵从了“流的特性”。 但你如果用 float 来做布局,float 属性的元素会脱离文档流,而且会涉及到各种 BFC、清除浮动的问题。浮动相关的问题,比较麻烦,所以也成了面试必问的经典题目。但有了 flex 布局之后,这些问题都不存在的。 2、**flex 是一种现代的布局方式,是 W3C 第一次提供真正用于布局的 CSS 规范**。 flex 提供了非常丰富的属性,非常灵活,让布局的实现更佳多样化,且方便易用。 flex 唯一的缺点就在于,它不支持低版本的 IE 浏览器。 ### flex 的兼容性问题 ![](http://img.smyhvae.com/20191005_1200.png) 上图中可以看到, flex 布局不支持 IE9 及以下的版本;IE10及以上也只是部分支持。如果你的页面不需要处理 IE浏览器的兼容性问题,则可以放心大胆地使用 flex 布局。 但是,比如网易新闻、淘宝这样的大型网站,面对的是海量用户,即便使用低版本浏览器的用户比例很少,但绝对基数仍然是很庞大的。因此,这些网站为了兼容低版本的 IE 浏览器,暂时还不敢尝试使用 flex 布局。 ### 概念:弹性盒子、子元素 在讲 flex 的知识点之前,我们事先约定两个概念: - **弹性盒子**:指的是使用 `display:flex` 或 `display:inline-flex` 声明的**父容器**。 - **子元素/弹性元素**:指的是父容器里面的子元素们(父容器被声明为 flex 盒子的情况下)。 ### 概念:主轴和侧轴 在上面的“初体验”例子中,我们发现,弹性盒子里面的子元素们,默认是从左至右排列的,这个方向,代表的就是主轴的方向。 在此,我们引入主轴和侧轴的概念。 ![](http://img.smyhvae.com/20191009_1701.png) 如上图所示: - 主轴:flex容器的主轴,默认是水平方向,从左向右。 - 侧轴:与主轴垂直的轴称作侧轴,默认是垂直方向,从上往下。 PS:主轴和侧轴并不是固定不变的,可以通过 `flex-direction` 更换方向,我们在后面会讲到。 ## 弹性盒子 ### 声明定义 使用 `display:flex` 或 `display:inline-flex` 声明一个**父容器**为弹性盒子。此时,这个父容器里的子元素们,会遵循弹性布局。 备注:一般是用 `display:flex`这个属性。`display:inline-flex`用得较少。 ### flex-direction 属性 `flex-direction`:用于设置盒子中**子元素**的排列方向。属性值可以是: | 属性值 | 描述 | |:-------------|:-------------| | row | 从左到右水平排列子元素(默认值) | |column|从上到下垂直排列子元素| | row-reverse |从右向左排列子元素 | |column-reverse|从下到上垂直排列子元素| 备注:如果我们不给父容器写`flex-direction`这个属性,那么,子元素默认就是从左到右排列的。 代码演示: ```html

    传统布局

    • 1
    • 2
    • 3

    伸缩布局 display:flex

    • 1
    • 2
    • 3

    主轴方向 flex-direction:row

    • 1
    • 2
    • 3

    主轴方向 flex-direction:row-reverse

    • 1
    • 2
    • 3

    主轴方向 flex-direction:column

    • 1
    • 2
    • 3

    主轴方向 flex-direction:column-reverse

    • 1
    • 2
    • 3
    ``` ### flex-wrap 属性 `flex-wrap`:控制子元素溢出时的换行处理。 ### justify-content 属性 `justify-content`:控制子元素在主轴上的排列方式。 ## 弹性元素 ### justify-content 属性 - `justify-content: flex-start;` 设置子元素在**主轴上的对齐方式**。属性值可以是: - `flex-start` 从主轴的起点对齐(默认值) - `flex-end` 从主轴的终点对齐 - `center` 居中对齐 - `space-around` 在父盒子里平分 - `space-between` 两端对齐 平分 代码演示:(在浏览器中打开看效果) ```html

    主轴的对齐方式:justify-content:flex-start

    • 1
    • 2
    • 3

    主轴的对齐方式:justify-content:flex-end

    • 1
    • 2
    • 3

    主轴的对齐方式:justify-content:center

    • 1
    • 2
    • 3

    主轴的对齐方式:justify-content:space-round

    • 1
    • 2
    • 3

    主轴的对齐方式:justify-content:space-bettwen

    • 1
    • 2
    • 3
    • 4
    ``` ### align-items 属性 `align-items`:设置子元素在**侧轴上的对齐方式**。属性值可以是: - `flex-start` 从侧轴开始的方向对齐 - `flex-end` 从侧轴结束的方向对齐 - `baseline` 基线 默认同flex-start - `center` 中间对齐 - `stretch` 拉伸 代码演示: ```html

    侧轴的对齐方式:align-items :flex-start

    • 1
    • 2
    • 3

    侧轴的对齐方式:align-items:flex-end

    • 1
    • 2
    • 3

    侧轴的对齐方式:align-items:center

    • 1
    • 2
    • 3

    侧轴的对齐方式:align-itmes:baseline

    • 1
    • 2
    • 3

    侧轴的对齐方式:align-itmes: stretch

    • 1
    • 2
    • 3
    ``` ### `flex`属性:设置子盒子的权重 代码演示: ```html

    伸缩比例:flex

    • 1
    • 2
    • 3

    伸缩比例:flex

    • 1
    • 2
    • 3
    ``` ## 相关链接 ### CSS Flexbox 可视化手册 可视化的截图如下:(请点开链接,查看大图) 相关文章: - 【英文原版】 CSS Flexbox Fundamentals Visual Guide: - 【中文翻译】CSS Flexbox 可视化手册: ### flex 相关的推荐文章 - flex 效果在线演示: - A Complete Guide to Flexbox | 英文原版: - CSS3 Flexbox 布局完全指南 | 中文翻译: ### flex 相关的教程 - [后盾人 flex 教程](http://houdunren.gitee.io/note/css/10%20%E5%BC%B9%E6%80%A7%E5%B8%83%E5%B1%80.html) ## 技巧:使用 margin 自动撑满剩余空间 ================================================ FILE: 02-CSS基础/14-CSS3属性详解:Web字体.md ================================================ --- title: 14-CSS3属性详解:Web字体 publish: true --- ## 前言 开发人员可以为自已的网页指定特殊的字体(将指定字体提前下载到站点中),无需考虑用户电脑上是否安装了此特殊字体。从此,把特殊字体处理成图片的方式便成为了过去。 支持程度比较好,甚至 IE 低版本的浏览器也能支持。 ## 字体的常见格式 > 不同浏览器所支持的字体格式是不一样的,我们有必要了解一下字体格式的知识。 #### TureTpe格式:(**.ttf**) .ttf 字体是Windows和Mac的最常见的字体,是一种RAW格式。 支持这种字体的浏览器有IE9+、Firefox3.5+、Chrome4+、Safari3+、Opera10+、iOS Mobile、Safari4.2+。 #### OpenType格式:(**.otf**) .otf 字体被认为是一种原始的字体格式,其内置在TureType的基础上。 支持这种字体的浏览器有Firefox3.5+、Chrome4.0+、Safari3.1+、Opera10.0+、iOS Mobile、Safari4.2+。 #### Web Open Font Format格式:(**.woff**) woff字体是Web字体中最佳格式,他是一个开放的TrueType/OpenType的压缩版本,同时也支持元数据包的分离。 支持这种字体的浏览器有IE9+、Firefox3.5+、Chrome6+、Safari3.6+、Opera11.1+。 #### Embedded Open Type格式:(**.eot**) .eot字体是IE专用字体,可以从TrueType创建此格式字体,支持这种字体的浏览器有IE4+。 #### SVG格式:(**.svg**) .svg字体是基于SVG字体渲染的一种格式。 支持这种字体的浏览器有Chrome4+、Safari3.1+、Opera10.0+、iOS Mobile Safari3.2+。 **总结:** 了解了上面的知识后,**我们就需要为不同的浏览器准备不同格式的字体**。通常我们会通过字体生成工具帮我们生成各种格式的字体,因此无需过于在意字体格式之间的区别。 下载字体的网站推荐: - - ## WebFont 的使用步骤 打开网站,如下: ![](http://img.smyhvae.com/20180220_1328.png) 上图中,比如我想要「思源黑体-粗」这个字体,那我就点击红框中的「本地下载」。 下载完成后是一个压缩包,压缩包链接:http://download.csdn.net/download/smyhvae/10253561 解压后如下: ![](http://img.smyhvae.com/20180220_1336.png) 上图中, 我们把箭头处的html文件打开,里面告诉了我们 webfont 的**使用步骤**: ![](http://img.smyhvae.com/20180220_1338.png) (1)第一步:使用font-face声明字体 ```css @font-face {font-family: 'webfont'; src: url('webfont.eot'); /* IE9*/ src: url('webfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('webfont.woff') format('woff'), /* chrome、firefox */ url('webfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ url('webfont.svg#webfont') format('svg'); /* iOS 4.1- */ } ``` (2)第二步:定义使用webfont的样式 ```css .web-font{ font-family:"webfont" !important; font-size:16px;font-style:normal; -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale;} ``` (3)第三步:为文字加上对应的样式 ```html 这一分钟,你和我在一起,因为你,我会记得那一分钟。从现在开始,我们就是一分钟的朋友。这是事实,你改变不了,因为已经完成了。 ``` **举例:** 我们按照上图中的步骤来,引入这个字体。完整版代码如下: ```html

    生命壹号,永不止步

    ``` 代码解释: (1)`my-web-font`这个名字是随便起的,只要保证第一步和第二步中的名字一样就行。 (2)因为我把字体文件单独放在了font文件夹中,所以在src中引用字体资源时,写的路径是 `font/...` 工程文件:[2018-02-20-WebFont举例.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-20-WebFont%E4%B8%BE%E4%BE%8B.zip) ## 字体图标(阿里的 iconfont 网站举例) 我们其实可以把图片制作成字体。常见的做法是:把网页中一些小的图标,借助工具生成一个字体包,然后就可以像使用文字一样使用图标了。这样做的优点是: - 将所有图标打包成字体库,减少请求; - 具有矢量性,可保证清晰度; - 使用灵活,便于维护。 也就是说,我们可以把这些图标当作字体来看待,凡是字体拥有的属性(字体大小、颜色等),均适用于图标。 **使用步骤如下:**(和上一段的使用步骤是一样的) 打开网站,找到想要的图标,加入购物车。然后下载下来: ![](http://img.smyhvae.com/20180220_1750.png) 压缩包下载之后,解压,打开里面的demo.html,里面告诉了我们怎样引用这些图标。 ![](http://img.smyhvae.com/20180220_1755.png) **举例1**:(图标字体引用) ```html

    扫码付款

    ``` 效果如下: ![](http://img.smyhvae.com/20180220_1800.png) **举例2**:(伪元素的方式使用图标字体) 如果想要在文字的前面加图标字体,我们更习惯采用**伪元素**的方式进行添加。 代码如下: ```html

    扫码付款

    我是span
    divvvvvvvvvvv
    ``` 效果如下: ![](http://img.smyhvae.com/20180220_1815.png) ~工程文件~: - 2018-02-20-图标字体demo.zip - 下载链接暂无。 ## 其他相相关网站介绍 - Font Awesome 使用介绍: 定制自已的字体图标库: - - SVG素材: - ## 360浏览器网站案例 暂略。 这里涉及到:jQuery fullPage 全屏滚动插件。 - 中文网址:http://www.dowebok.com - 相关说明:http://www.dowebok.com/77.html ## 使用 Bootstrap 网站的图标字体 打开如下网站:。 ![](http://img.smyhvae.com/20180223_2100.png) 如上图所示,下载字体后,进行解压: ![](http://img.smyhvae.com/20180223_2105.png) 使用步骤如下: (1)如图只是想要字体的话,可以把`css`和`font`这两个文件夹拷贝到项目里。 (2)在html文档中的 标签里,引入 font-awesome.min.css 文件: ```html ``` (3)想在哪个标签里用这个图标,直接在这个标签里加className就行(className都在[网站](http://www.bootcss.com/p/font-awesome/)上列出来了)。 完整版代码如下: ```html Title 播放 ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 02-CSS基础/15-Sass入门.md ================================================ --- title: 15-Sass入门 publish: true --- ## Sass简介 大家都知道,js 中可以自定义变量,css 仅仅是一个标记语言,不是编程语言,因此不可以自定义变量、不可以引用等等。 面对这些问题,我们现在来引入 Sass,简单的说,他是 css 的升级版,可以自定义变量,可以有 if 语句,还可以嵌套等等,很神奇吧!那下面我们就来介绍返个神奇的 Sass。 Sass比Less的功能更强大,也更复杂。 ### Sass 的定义 Sass:英文是 Syntactically Awesome Stylesheets Sass。最早由 Hampton Catlin 开发和设计。 一种帮助你简化 CSS 工作流程的方式,帮助你更容易维护和开发 CSS 内容。 官网是: Sass 是这个世界上最成熟、稳定和强大的专业级 CSS 扩展语言。 Sass专注的是怎样创建优雅的样式表,而不是内容。 ### Sass、Compass与CSS **关系:** - Less/Sass是语法、Compass是框架、CSS是目标。 **Sass&Compass的好处**: - 写出更优秀的CSS。 - 解决CSS编写过程中的痛点问题,比如精灵图合图、属性的浏览器前缀处理等。 - 有效组织样式、图片、字体等项目元素。 **受众群体:** - 重构的同学,写很多CSS,不知如何自动化。 - 希望在项目周期内更好地组织项目内容。 ## Sass的安装 sass引擎是用Ruby语言开发的(但是两者的语法没有关系),因此在安装 Sass 前,需要先安装Ruby(mac下自带Ruby无需再安装Ruby)。 下面来讲一下 Windows 下的安装Sass的步骤。 ### 第一步:安装Ruby(windows环境) 下载地址: 貌似网络很慢,不一定能下载成功~ 安装时,记得勾选“环境变量”: ![](http://img.smyhvae.com/20180407_2022.png) 安装完ruby之后,在命令行中输入`ruby -v`,查看ruby的的版本: ![](http://img.smyhvae.com/20180407_2039.png) ### 关于Mac下的Ruby 刚刚说了,Mac下自带Ruby,但是版本肯定很老: ![](http://img.smyhvae.com/20180407_2145.png) 有的时候,我们可能需要使用特定版本的ruby,或者在不同的ruby版本之间进行切换,所以,大家可以尝试安装`rvm`,它是ruby的版本管理工具。官网是: ### 第二步:安装 Sass 安装完ruby之后,在开始菜单中,找到刚才我们安装的ruby,打开Start Command Prompt with Ruby。输入`gem install sass`安装Sass。 PS:Ruby 是使用 gem 来管理它的各种包(比如Sass)。我们安装好ruby之后,gem会自动安装上;类似于,我们安装完node之后,npm也自动安装好了。 但是,由于访问网络受限,我们可以先切换到淘宝的镜像,再安装Sass。步骤如下: (1)移除默认的镜像,添加淘宝的镜像: ``` gem sources --remove https://rubygems.org/ gem sources -a https://ruby.taobao.org/ //注意:如果你系统不支持https,请将淘宝源更换成:gem sources -a http://gems.ruby-china.org ``` PS:我测试了一下,Win 7 不支持https,Mac支持https。 (2)查看当前使用的是哪个镜像: ``` gem sources -l ``` ![](http://img.smyhvae.com/20180407_2050.png) (3)安装sass: 紧接着,输入如下命令安装Sass: ``` gem install sass // 如果mac下输入这个命令时没有权限,则需要在前面加上 sudo ``` 系统会自动安装上最新版本的Sass。 查看sass版本的命令为: ``` sass -v ``` 升级sass版本的命令为: ``` gem update sass ``` 你也可以运行帮助命令行来查看你需要的命令: ``` sass -h ``` ![](http://img.smyhvae.com/20180407_2100.png) 参考链接: ## Compass 简介和安装 安装完sass之后,我们在main.scss中写一些代码,然后输入如下命令,就可以将`scss文件`转化为`css文件`: ``` sass main.scss main.css ``` 然而,真正的项目开发中,我们不一定是直接使用 sass 命令,而是使用 Compass。 ### Compass 简介 官网是:。 Compass 是开源的CSS书写框架。 ### Compass 安装 输入如下命令安装 Compass: ``` gem isntall compass ``` 输入如下命令查看版本: ``` compass -v ``` ![](http://img.smyhvae.com/20180407_2208.png) compass可以直接用来搭建前端项目的样式部分,但并不是常用的方法。 ### Compass的简单使用 通过 Compass 创建工程目录: ``` cd workspace compass create CompassDemo ``` 文件结构如下: - /sass - ie.scss - print.scss - screen.scss - /stylesheets - ie.css - print.css - screen.css - config.rb 为了能够让文件实时编译,我们可以通过 copass watch 监听sass文件的变化: ``` cd CompassDemo compass watch ``` 当.scss文件改动时,会自动生成对应的.css文件。 ## Sass的语法 ### 两种后缀名(两种语法) sass 有两种后缀名文件: (1)`.sass`:对空格敏感。不使用大括号和分号,所以每个属性之间是通过换行来分隔。 比如: ``` h1 color: #000 background: #fff ``` 这种语法是类ruby的语法,和CSS的语法相比,相差较大。所以,在3.0版本中就引入了`.scss`的语法。 (2)`.scss`:是css语法的超集,可以使用大括号和分号。 比如: ``` h1 { color: #000; background: #fff; } ``` 注意:一个项目中可以混合使用两种语法,但是一个文件中不能同时使用两种语法。 **两种格式之间的转换:** 我们在工程目录下新建`main.scss`,输入如下代码: ``` *{ margin: 0; padding: 0; } ``` 然后输入如下命令,就可以将上面的`main.scss`转化为`main.sass`: ```bash sass-convert main.scss main.sass ``` 打开生成的`main.sass`,内容如下: ``` * margin: 0 padding: 0 ``` ### 变量语法 Sass 是通过`$`符号来声明变量。 (1)我们新建一个文件`_variables.scss`,这个文件专门用来存放变量,然后在其他的文件中引入`_variables.scss`即可。 因为这个文件只需要存储变量,并不需要它编译出对应的 css 文件,所以我们给文件名的前面加了**下划线**,意思是声明为**局部文件**。 我们在这个文件中,声明两个字体变量: ```css $font1: Braggadocio, Arial, Verdana, Helvetica, sans-serif; $font2: Arial, Verdana, Helvetica, sans-serif; ``` (2)新建文件main.scss,在里面引入步骤(1)中的变量文件: ``` @import "variables"; // 引入变量文件 .div1{ font-family: $font1; } .div2{ font-family: $font2; } ``` 基于 Sass 的既定规则: - 没有文件后缀名时,Sass 会自动添加 .scss 或者 .sass 的后缀(具体要看已经存在哪个后缀的文件)。 - 同一目录下,局部文件和非局部文件不能重名。 对应生成的main.css文件如下: main.css ```css /* line 9, ../sass/main.scss */ .div1 { font-family: Braggadocio, Arial, Verdana, Helvetica, sans-serif; } /* line 13, ../sass/main.scss */ .div2 { font-family: Arial, Verdana, Helvetica, sans-serif; } ``` ### 注释语法 单行注释: ``` //我是单行注释 ``` 块级注释: ``` /* 我是块级注释 哈哈 */ ``` 二者的区别是:单行注释不会出现在自动生成的css文件中。 ================================================ FILE: 02-CSS基础/16-浏览器的兼容性问题.md ================================================ --- title: 16-浏览器的兼容性问题 publish: false --- 我们在div里放一个img,发现: 在html和html5中,div的长宽是不同的,后者的高度要超过几个像素。 比如说,下面这个是html的。 ```html Document
    ``` ================================================ FILE: 02-CSS基础/17-CSS3的常见边框汇总.md ================================================ --- title: 17-CSS3的常见边框汇总 publish: true --- ## CSS3 常见边框汇总 ```html CSS3 边框

    CSS3 边框圆角

    ``` 效果如下: ![](http://img.smyhvae.com/20180208_1730.png) ## 爱心 ```html
    ``` 效果如下: ![](http://img.smyhvae.com/20180208_1737.png) 它其实是下面这两个盒子叠起来的: ![](http://img.smyhvae.com/20180208_1738.png) 改变 `.heart::after` 的 left值,即可叠起来。 ================================================ FILE: 02-CSS基础/others.md ================================================ --- publish: false --- ```html Document ``` ================================================ FILE: 03-CSS进阶/00-准备.md ================================================ --- title: 00-准备 publish: true --- ## 前言 css 进阶的主要内容如下。 ### 1、css 非布局样式 - html 元素的分类和特性 - css 选择器 - css 常见属性(非布局样式) ### 2、css 布局相关 - css 布局属性和组合解析 - 常见布局方案 - 三栏布局案例 ### 3、动画和效果 > 属于 css 中最出彩的内容。 - 多背景多投影特效 - 3D特效编写实践 - 过渡动画和关键帧动画实践 - 动画细节和原理深入解析 ### 4、框架集成和 css 工程化 - 预处理器作用和原理 - less/sass 代码实践 - Bootstrap 原理和用法 - css 工程化的的实践方式 - js 框架中的 css 集成实践 ## 常见问题 > 不会 css 的前端称之为伪前端。 ### Vue 中模拟Scoped CSS的方式 方案一:随机选择器。css modules。 方案二:**随机属性**。`
    `、`div[adcd]{}` ### 其他问题 - html 元素的嵌套关系是怎么确定的?哪些嵌套不可以发生? - 比如说,为什么 div 可以放在 a 标签里面? - css 选择器的权重是如何计算的?写代码时要注意什么? - 浮动布局是怎么回事?有什么优缺点?国内用的多吗? - css 可否做逐帧动画吗?性能如何? - Bootstrap 怎么做响应式布局? - 如何解决 css 模块化过程中的选择器互相干扰的问题? ## 总结 单独看 css 属性并不难,难的是需要把这些思路和思想,想到它的应用场景。 ================================================ FILE: 03-CSS进阶/01-CSS中的非布局样式.md ================================================ --- title: 01-CSS中的非布局样式 publish: true --- ## 前言 CSS中,有很多**非布局样式**,这些样式(属性)和与布局无关,包括: - 字体、字重、颜色、大小、行高 - 背景、边框 - 滚动、换行 - 装饰性属性(粗体、斜体、下划线)等。 这篇文章,我们来对上面的部分样式做一个回顾。 ## 边框 如何用边框画一个三角形?详见《02-CSS基础/06-CSS盒模型详解》中的最后一段。 ## 文字换行 - ovferflow-wrap:通用的属性。用来说明当一个不能被分开的字符串(单词)太长而不能填充其包裹盒时,为防止其溢出,浏览器是否允许这样的单词**中断换行**。 - word-break:指定了怎样在单词内断行。这里涉及到CJK(中文/日文/韩文)的文字换行。 - white-space:空白处是否换行。 上面这三个 css 属性进行组合,可以设置各种不同的属性。 当然,如果想让一段很长的文本不换行,可以直接设置`white-space: nowrap` 这一个属性即可。如果想换行,可以试试`white-space: pre-wrap`。 ## CSS Hack - CSS Hack 的方式:不合法但可以生效的写法。 - 可以用来解决一些浏览器的兼容性问题。 - 缺点:难理解、难维护、易失效(比如浏览器升级后,hack可能会失效)。 - 替代方案:特性检测。 - 替代方案:针对性加 class ## CSS 效果 我们可以利用 CSS 实现各种效果,常见的效果属性有: - box-shadow:盒子的阴影 - text-shadow:文本的阴影 - border-radius - background - clip-path ================================================ FILE: 03-CSS进阶/02-CSS布局.md ================================================ --- title: 02-CSS布局 publish: true --- ## 前言 ### 常见的布局属性 (1)`display` 确定元素的显示类型: - block:块级元素。 - inline:行内元素。 - inline-block:对外的表现是行内元素(不会独占一行),对内的表现是块级元素(可以设置宽高)。 (2)`position` 确定元素的位置: - static:默认属性值。 - relative:相对定位。相对于元素本身进行偏移,**不会改变它所占据的空间**。 - absolute:绝对定位。相对于父元素中最近的 relative/absolute 进行偏移,会脱离文档流。音标:[ˈæbsəluːt]。 - fixed:固定定位。相对于可视区域固定,会脱离文档流。 `relative`、`absolute`、`fixed`这三个属性,可以结合 z-index 来设置层级。 ### 常见的布局方法 1、**table 表格布局**:早期使用的布局,如今用得很少。 2、**float 浮动 + margin**:为了兼容低版本的IE浏览器,很多网站(比如腾讯新闻、网易新闻、淘宝等)都会采用 float 布局。 3、**inline-block 布局**:对外的表现是行内元素(不会独占一行),对内的表现是块级元素(可以设置宽高)。 4、**flex 布局**:为布局而生,非常灵活,是最为推荐的布局写法。 唯一的缺点是兼容性问题: ![](http://img.smyhvae.com/20191005_1200.png) 上图中可以看到, flex 布局不支持 IE9 及以下的版本。如果你的页面不需要处理 IE浏览器的兼容性问题,则可以放心大胆地使用 flex 布局。 flex 是一种现代的布局方式,是 W3C 第一次提供真正用于布局的 CSS 规范。 5、响应式布局。 ## float 布局 是 CSS 中一种比较麻烦的属性,涉及到 BFC 和清除浮动(面试的重点)。 ### float 属性的特点 - 元素浮动 - **脱离文档流,但不脱离文本流** 代码举例: 下面这两个并列的`div1`和`div2`,默认是在标准流中的: ![](http://img.smyhvae.com/20191005_2029.png) 在此基础之上,如果给`div1`增加`float: left`属性后,效果如下: ![](http://img.smyhvae.com/20191005_2037.png) 上图中,可以看到,`div1`设置为浮动后,会脱离文档流,不会对`div2`的布局造成影响;但是`div1`不会脱离文本流,它会影响`div2`中文字的排列。 其实,这正是 float 属性的作用。float 本身是用来做图文混排、文字环绕的效果。 ### float 所带来的影响 **1、对自身的影响**: - 形成“块”(BFC) - 位置尽量靠上 - 位置尽量靠左/右 下面这两个并列的`div1`和`div2`,设置为浮动之后的效果:(都是尽量靠左显示的) ![](http://img.smyhvae.com/20191005_2130.png) 在上方代码的基础之上,增加 `div2`的宽度之后,会发现,`div2`掉下来了: ![](http://img.smyhvae.com/20191005_2135.png) **2、对兄弟元素的影响**: - 不影响其他块级元素的位置 - 影响其他块级元素的内部文本 **3、对父级元素的影响**: - 从父级的布局中“消失” - 造成父级元素的高度塌陷:父级元素撑开 div1 之后(父级元素里没有其他元素的情况下),如果设置 div1 为 float 之后,,会让父级元素的高度变为0。 ## inline-block 布局 对外的表现是行内元素(不会独占一行),对内的表现是块级元素(可以设置宽高)。 **思路**:像文本一样去排列 block 元素,没有清除浮动等问题。 **存在的问题**:需要处理间隙。代码举例如下: ```html Document
    div1的inline-block 属性
    div2的inline-block 属性
    琴棋书画不会,洗衣做饭嫌累。
    ``` ![](http://img.smyhvae.com/20191005_2200.png) 上面的代码,存在两个问题。 **问题一**:如果设置`div2`的宽度为 200px 之后,`div2` 掉下来。 **问题二**:`div1`和`div2`设置为 inline-block之后,这两个盒子之间存在了间隙。这是因为,此时的 `div1`和`div2` 已经被当成文本了。文本和文本之间,本身就会存在间隙。 为了去掉这个间隙,可以有几种解决办法: 办法1:设置父元素`container`的字体大小为0,即`font-size: 0`,然后设置子元素 `div1`、`div2`的字体`font-size: 12px`。 办法2:在写法上,去掉`div1`和`div2`之间的换行。改为: ```html
    div1的inline-block 属性
    div2的inline-block 属性
    ``` ## 响应式布局 移动端用得较多,本文暂时先不讲。 ================================================ FILE: 03-CSS进阶/03-网页设计和开发中,关于字体的常识.md ================================================ --- title: 03-网页设计和开发中,关于字体的常识 publish: true --- ## 前言 我周围的码农当中,有很多是技术大神,却常常被字体这种简单的东西所困扰。 这篇文章,我们来讲一讲关于字体的常识。这些常识所涉及到的问题,有很强的可操作性,都是在实际业务中真实遇到的,都是需要开发同学和产品经理、设计师不断重复沟通的。 字体真的只是“**系统默认,无从解释**”这么简单吗?是产品被忽悠?还是开发在敷衍?二者之间的博弈究竟谁能胜出?学会本文,你就能胜出。 ## 字体分类 常见的字体可以分为两类:**衬线体、无衬线体**。 ![](https://img.smyhvae.com/20191004_1101.png) **1、serif(衬线体)**:在字的笔画开始、结束的地方有额外的装饰,而且笔画的粗细会有所不同。 常见的衬线体有: - 宋体、楷体 - Times New Roman **2、sans-serif(无衬线体)**:笔划粗细基本一致,只剩下主干,造型简明有力,起源也很晚。适用于标题、广告等,识别性高。 常见的无衬线体有: - 黑体 - Windows 平台默认的中文字体:微软雅黑(Microsoft Yahei) - Windows 平台默认的英文字体:Arial - Mac & iOS 平台默认的中文字体:苹方(PingFang SC) - Mac & iOS 平台默认的英文字体:San Francisco - Android 平台默认字体:Droid Sans **补充**: 衬线体如今已经很少使用了,你所熟悉的“宋体”,也基本只能在纸质出版物中见到。而**非衬线体更符合现代审美**。 所以,在这里温馨提示各位:**做PPT不要用宋体**。如果你不知道用什么字体,那就用系统的默认字体就好:Win 平台用微软雅黑、Mac 平台用苹方字体。 如果你发现一名设计师,在做海报设计、或者制图的时候,使用了宋体,说明她一定是个外行。 ## 字体族 CSS 中的字体族可以理解成是某一类字体。常见的字体族可以分为五类: - serif:衬线体。 - sans-serif:无衬线体。 - monospace:等宽字体。每一个字母所占的宽度是相同的。写代码的字体尽量用等宽字体。 - cursive:手写字体。比如徐静蕾手写体。 - fantasy:梦幻字体。比如一些艺术字。 这五类字体族不代表某一个具体的字体,而是当你在 CSS 中指定字体族的时候,系统就有可能在字体族中找出一种字体来显示。 ![](https://img.smyhvae.com/20191004_1130.png) 参考链接:[serif,sans-serif,monospace,cursive和fantasy](http://www.ayqy.net/blog/serif%EF%BC%8Csans-serif%EF%BC%8Cmonospace%EF%BC%8Ccursive%E5%92%8Cfantasy/) ## 多字体 fallback 机制 多字体 fallback 机制:当指定的字体找不到(或者某些文字不支持这个字体)时,那就接着往后找。比如: ```css .div1{ font-family: "PingFang SC", "Microsoft Yahei", monospace; } ``` 上方 CSS 代码的意思是:让指定标签元素中的文字,在 Mac & iOS 平台用苹方字体,在 Win 平台用微软雅黑字体,如果这两个字体都没有,就随便找一个等宽的字体进行渲染。 **注意**: (1)写 CSS 代码时,字体族不需要带引号。 (2)有些 Mac 用户会安装 Office 等软件,安装这些软件后,系统会顺带安装微软雅黑字体。此时,写 CSS 代码时,如果写成 `"Microsoft Yahei", "PingFang SC"`这种顺序,可能导致有些 Mac 用户用微软雅黑来显示字体。这么好看的苹方字体,你忍心割舍吗? ## font-weight:字体的加粗属性 font-weight 字体加粗属性,是让前端同学最迷茫的属性。当你把做好的网页拿给产品经理验收时,网页一打开,产品经理首先关注到的就是字体的加粗问题,你信不信?下面这些话都是产品经理的口头禅,想必早已让你产生了幻听: - “这个字体怎么没有加粗?” - “这个字体是不是太粗了点?” - “为什么 iPhone 和 Android 手机上的文字粗细不一致?” 想要明白这些疑惑,我们先来看看 `font-weight` 有哪些属性值。 **font-weight 属性**:在设置字体是否加粗时,属性值既可以直接填写 100 至 900 这样的数字,也可以填写`normal`、`bold`这样的单词。`normal`的值相当于 400,`bold`的值相当于 700。如下: ```css font-weight: 100; font-weight: 200; font-weight: 300; font-weight: 400; font-weight: 500; font-weight: 600; font-weight: 700; font-weight: 800; font-weight: 900; font-weight: normal; // 相当于 400 font-weight: bold; // 相当于 700 ``` 关键问题来了。很多人会发现,在 Windows 平台的浏览器中, font-weight 无论是设置300、400,还是500,文字的粗细都没有任何变化,只有到600的时候才会加粗一下,感觉浏览器好像不支持这些数值,那搞这么多档位不就是多余的吗? 这个时候,大家就开始吐槽 Windows 电脑太挫、Windows 浏览器太挫;同时还会感叹 Mac 真香,支持字体的各种粗细。 **实际上,所有这些数值关键字浏览器都是支持的,之所以没有看到任何粗细的变化,是因为你所使用的字体不支持**。 就拿“微软雅黑”来举例,它只支持 400 和 700 这两种粗细,所以当你在代码里写成500的时候,也会被认为是400。但是 Mac 上的“苹方”字体,就支持从100到900之间的各种粗细。 再比如,前段时间,阿里巴巴开源的普惠字体,也是支持多种粗细的: ![](https://img.smyhvae.com/20191013_1100.png) ## 各大平台的默认字体加粗效果 一张图,胜过千言万语。解释了这么多,我们来看看各大平台的字体加粗效果是什么样的。 以下截图,都是我亲测的结果,如果你打算让别人看效果,直接把下面的图丢给他即可。像我这样贴心的前端,不多见了。 **1、Mac 平台的默认字体加粗效果**:(苹方字体) ![](https://img.smyhvae.com/20191016_1205_mac.png) **2、Windows 平台的默认字体加粗效果**:(微软雅黑字体) ![](https://img.smyhvae.com/20191016_1205_windows2.png) **3、iOS 平台的默认字体加粗效果**:(苹方字体) ![](https://img.smyhvae.com/20191016_1205_ios.png) **4、Android 平台(华为 P30 Pro)的默认字体加粗效果**:(Droid Sans 字体) ![](https://img.smyhvae.com/20191016_1205_huawei_p30_pro.jpeg) ### 总结各大平台的默认字体加粗档位(字重) > 注意,系统默认的 normal 字重是400;加粗的 bold 字重是700。 1、Mac & iOS 平台的“苹方”字体的字重:(有6种粗细,`>=600`的加粗效果是相同的) - 极细体:100 - 纤细体:200 - 细体:300 - 常规体:400 - 中黑体:500 - 中粗体:600、700、800、900 2、Windows 平台的“微软雅黑”字体的字重:(只有两种粗细 ;`>=600` 才会加粗,而且加粗效果相同) - 不加粗的默认字体:100、200、300、400、500 - 加粗字体:600、700、800 3、Android 平台的 Droid Sans 字体的字重:(只有 `>=700`才会加粗;而且加粗效果相同) - 不加粗的默认字体:100、200、300、400、500、600 - 加粗字体:700、800 **实战中,系统默认字体的加粗总结**: - 如果你做的软件产品只有苹果系统(比如iOS或Mac),可以使用各种粗细和字重。 - 如果你做的软件产品包括了苹果系统(比如iOS或Mac)和非苹果系统(比如Android或Windows),建议直接使用normal(系统默认) 和 bold 这两种粗细。 ### 补充:“苹方”字体的粗细效果,大图预览 “苹方”字体包含了六种自重:常规体、中等、细体、特粗体、特细体、粗体。对应的CSS样式如下: ```css /* 苹方-简 极细体:100 */ font-family: PingFangSC-Ultralight; /* 苹方-简 纤细体:200*/ font-family: PingFangSC-Thin; /* 苹方-简 细体:300 */ font-family: PingFangSC-Light; /* 苹方-简 常规体:400 */ font-family: PingFangSC-Regular; /* 苹方-简 中黑体:500 */ font-family: PingFangSC-Medium; /* 苹方-简 中粗体:600、700、800、900 */ font-family: PingFangSC-Semibold; ``` 大图预览如下: ![](https://img.smyhvae.com/20201028-1400.png) 我还要多说一句:我实际测试发现,苹方字体的200字重和300字重,在iOS上的粗细是不同的(符合预期),但在 Mac 上的粗细效果是相同的(无论把字体放大多少倍,都是如此)。具体你可以看看我在上面的截图效果对比。我目测这应该是Mac系统的bug。 ## 大部分字体不是免费的 有一点你需要知道:你所见到的大部分字体,都不是免费的。换句话说,如果你想用第三方字体从事商业活动,要先交钱,获得授权后才可以使用。你在给公司做网页的时候,就是一种商业行为。 「微软雅黑」是免费字体吗?并不是。你可以将微软雅黑字体用在 office 软件中,但是一旦脱离了 office 软件或者脱离了 Windows 系统(比如说,你把做好的PPT打印出来拿去做商业宣传),你就不能使用该字体。 同理,苹果专属的「苹方字体」也只能在苹果系统的生态内使用。 免费字体当然有,比如[思源黑体](https://baike.baidu.com/item/%E6%80%9D%E6%BA%90%E9%BB%91%E4%BD%93/14919098)(Adobe 和 Google 在2014年7月联合推出的一款开源字体)、阿里巴巴普惠体等。但这些免费字体,我们平时基本用不到。 这也就是为什么,很多公司会专门购买一套商用字体库、甚至是自己开发一套字体出来,避免未来潜在的纠纷和麻烦。 给大家列举一个常见的场景。很多时候,前端同学拿到的视觉稿是 psd 稿,需要用 PS 软件打开源文件,才能看到里面的文字是什么字体。在 PS 软件里,当我们用光标选中字体的时候,出现了下面这种场景: ![](https://img.smyhvae.com/20191010_1121.png) 看到上面的`FZLTZCHK`,不要慌,马上去 Google 查一下,发现这个字体的全称是`方正兰亭`字体系列。恩,基本可以肯定, 这个字体也是要收费的。 这个时候,前端同学要马上告诉产品或者设计师,不要用这个字体,因为它是商用字体,是要收费的,小心吃官司。那我们该用什么字体呢?接着往下看。 ## 网页一般用什么字体 大多数情况下,网页使用系统默认的字体就足够了。如果要使用特殊字体,顶多只是让**阿拉伯数字**使用特殊字体。中文和英文,使用默认字体,完全足够。 如果确实要使用特殊字体,只有这几种办法: - 使用开源的免费字体(比如思源黑体、阿里巴巴普惠体等)。但这类字体,种类很少,而且大多不是很好看。网页开发中,基本没人用。 - 单独购买第三方的商用字体,获得授权。 - 自己公司(找人)开发一套字体,给自己人用。 关于第三种办法,下面,我将以「京东朗正体」来举例。 ## 自主研发的字体举例:京东朗正体 ### 使用举例 比如 JD 公司就自主开发了一套商用字体`京东朗正体`,支持三种粗细。只允许 JD 公司自己用,别家公司不允许用。如下: ```css /** * JD正黑体,提供三种字重,请严格按设计稿选择字体 */ @font-face { font-family: 'JDZH-Light'; src: url('xxx.com/data/ppms/others/JDZH_Light.ttf') format('truetype'); } @font-face { font-family: 'JDZH-Regular'; src: url('xxx.com/data/ppms/others/JDZH_Regular.ttf') format('truetype'); } @font-face { font-family: 'JDZH-Bold'; src: url('xxx.com/data/ppms/others/JDZH_Bold.ttf') format('truetype'); } @font-face { font-family: 'JDZhengHT-EN-Bold'; src: url('xxx.com/data/ppms/others/JDZhengHei_01_Bold.ttf') format('truetype'); } ``` 为了使用这个`京东朗正体`字体, JD公司在实际开发网页时,是这样做的: - 步骤1:在公共的 CSS 文件中引入上方的字体库代码。这样的话,当页面加载时,用户的终端就会去下载这个字体库。 - 步骤2:在业务代码中,针对目标样式,直接使用 `font-family: 'JDZH-Regular';`这样的代码,即可生效。 这个`JDZH`,我们一般也只使用在阿拉伯数字中;中文和英文,建议使用系统默认字体就行,否则会导致字体文件过大。 ### 使用规则 ![](https://img.smyhvae.com/20201224_1916.png) ![](https://img.smyhvae.com/20201224_1921.png) 从资料中可以看出,**京东朗正体**的版权属于方正公司,而京东具有永久使用权。 ## 最后一段 所谓「见微知著」,一个再不起眼的知识点,也是有很多学问的。光是“字体”这一点,就足够成为一门学科。 2005年,苹果公司创始人乔布斯(Steve Jobs)在斯坦福大学的毕业典礼演讲上,有过这样一段话: > 当时的我从来没有期盼过我所学的这些东西,能够在我的生活中有什么实际的用处。 > 但是到了十年之后,当我们在设计第一台 Macintosh 电脑时,这些所学都涌进了我的头脑。于是,我把这些设计融入到了 Mac 电脑之中,这也使这台 Mac 成为了第一台拥有漂亮字体的电脑。 > 可以说,如果我当时没有退学,就不会有机会去参加我感兴趣的美术字课程,Mac 也就不会拥有那些美妙的排版和字体。而当 Windows 系统借鉴了Mac之后,似乎所有的电脑都应该是这个样子。 > 当时的我没有办法把这些点点滴滴连接起来,但是,**当我十年后回顾的时候,一切都变得豁然开朗**。 ## 字体相关的网站推荐 - 360字体版权查询: - 2020年最全的免费可商用字体清单: - 免费可商用字体-效果预览(跟上线的链接属于同一个项目): - 常见的免费字体: 如果不花钱,免费可商用的字体挺少的,也才100多种。 ## 推荐阅读 - [Web 中文字体处理总结](https://aotu.io/notes/2020/02/28/webfont-processing/index.html) - 常见的免费字体: - 如何优雅的选择字体(font-family): - iconfont: - [乔布斯斯坦福大学演讲-翻译](https://zhuanlan.zhihu.com/p/24242767) - [乔布斯斯坦福大学演讲-翻译](https://www.douban.com/note/149058647/) - 阿里巴巴官方发布免费商用字体:阿里巴巴普惠体: - 得到 | 从甲骨文至得到今楷,造字的人都是神: - [APP设计必备字体:San Francisco Pro、苹方、思源黑体和Roboto等](https://www.shejidaren.com/app-she-ji-bi-bei-zi-ti.html) - [《独立宣言》使用了什么字体?](https://mp.weixin.qq.com/s/i1qgUaSHrQlvqT-u3qJySw) - [你的版权常识指南](https://mp.weixin.qq.com/s/4uEBoajTygSADslzem3yZA) - [免费商用 | 这几款字体你值得拥有](https://mp.weixin.qq.com/s/wPqd0H9125bK7775KGoFbg) ![](https://img.smyhvae.com/20191016_2030.png) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 03-CSS进阶/04-如何让一个元素水平垂直居中?.md ================================================ --- title: 04-如何让一个元素水平垂直居中? publish: true --- ![](http://img.smyhvae.com/20191108_2130.png) ## 前言 老板的手机收到一个红包,为什么红包没居中? 如何让一个子元素在父容器里**水平垂直居中**?这个问题必考,在实战开发中,也应用得非常多。 你也许能顺手写出好几种实现方法。但大部分人的写法不够规范,经不起千锤百炼。换句话说:这些人也就面试的时候夸夸其谈,但真的上战场的时候,他们不敢这么写,也不知道怎么写最靠谱。 这篇文章中,我们来列出几种常见的写法,最终你会明白,哪种写法是最优雅的。 当然,我还会拿出实际应用中的真实场景来举例,让你感受一下**标准垂直居中的魅力**。 ## 如何让一个行内元素(文字、图片等)水平垂直居中 > 行内元素的居中问题比较简单。 ### 行内元素水平居中 给父容器设置: ``` text-align: center; ``` ### 行内元素垂直居中 让**文字的行高** 等于 **盒子的高度**,可以让单行文本垂直居中。比如: ```css .father { height: 20px; line-height: 20px; } ``` ## 如何让一个块级元素水平垂直居中 > 这一段是本文的核心。如何让一个块级的子元素在父容器里水平垂直居中?有好几种写法。我们一起来看看。 ### margin: auto 的问题 在 CSS 中对元素进行水平居中是非常简单的:如果它是一个行内元素,就对它的父容器应用 `text-align: center`;如果它是一个块级元素,就对它自身应用 `margin: auto`或者 `margin: 0 auto`。 在这里,`margin: auto`相当于`margin: auto auto auto auto`。`margin: 0 auto`相当于`margin: 0 auto 0 auto`,四个值分别对应上右下左。其计算值取决于**剩余空间**。 但是,如果要对一个元素垂直居中,`margin: auto`就行不通了。 比如下面这段代码: ```html Document
    ``` 上面的代码中,父元素和子元素都是定宽高的,即便在这种情况下,我给子元素设置 `margin: auto`,子元素依然没有垂直居中。 那还有没有比较好的通用的做法呢? ### 方式1:绝对定位 + margin > 需要指定子元素的宽高,不推荐。 ```html Document
    子元素的内容
    ``` **代码解释**:我们先让子元素的左上角居中,然后向上移动宽度的一半(50px),就达到了垂直居中的效果;水平居中的原理类似。 **不足之处**:要求指定子元素的宽高,才能写出 `margin-top` 和 `margin-left` 的属性值。 但是,在通常情况下,对那些需要居中的元素来说,其宽高往往是由其内容来决定的,不建议固定宽高。 ### 方式2:绝对定位 + translate > 无需指定子元素的宽高,推荐。 ```html Document
    子元素的内容
    ``` 这种写法,在没有指定子元素宽高的情况下,也能让其在父容器中垂直居中。因为 translate() 函数中使用百分比值时,是以这个元素自身的宽度和高度为基准进行换算和移动的(**动态计算宽高**)。 ### 方式3:绝对定位 + top,left,bottom,right = 0 + margin: auto > 无需指定子元素的宽高,推荐。 ### 方式4:flex 布局(待改进) 将父容器设置为 Flex 布局,再给父容器加个属性`justify-content: center`,这样的话,子元素就能水平居中了;再给父容器加个属性 `align-items: center`,这样的话,子元素就能垂直居中了。 代码举例: ```html Document
    子元素的内容
    ``` 上面这种写法,不足之处在于:给父容器设置属性`justify-content: center`和`align-items: center`之后,导致父容器里的所有子元素们都垂直居中了(如果父容器里有多个子元素的话)。可我明明想让指定的**某个子元素**居中,要怎么改进呢? ### 方式5: flex 布局 + margin: auto(推荐) 我们只需写两行声明即可:先给父容器设置 `display: flex`,再给指定的子元素设置我们再熟悉不过的 `margin: auto`,即可让这个指定的子元素在**剩余空间**里,水平垂直居中。大功告成。 代码举例: ```html Document
    子元素的内容,想水平垂直居中
    这个元素不想水平垂直居中
    ``` 请注意,当我们给父容器使用 Flex 布局 时,子元素的`margin: auto`不仅能让其在水平方向上居中,**垂直方向上也是居中的**。 参考文章:[探秘 flex 上下文中神奇的自动 margin](https://www.cnblogs.com/coco1s/p/10910588.html) ## 垂直居中的典型应用场景:红包幕帘/弹窗 ### 问题引入 就拿“弹窗”这一点来说,现在大家的弹窗都是各种样式、各种布局满天飞。不过进公司后,新人在第一次写弹窗之前,都会问一个问题:“弹窗这么通用的东西,没有一个规范吗?”说完之后,又默默写自己的有个性的弹窗去了。 建议大家在写弹窗的时候,无论如何,一定要**严格采用**水平居中、垂直居中的写法。 千万不要用 `margin-top` 这种距离屏幕顶部的距离来计算弹窗的位置,太搓了。不要让领导觉得:“你们写了这么久的前端代码,连个弹窗都搞不定?” ### 移动端,红包幕帘/弹窗 居中的规范写法(非常标准) 移动端场景,这里提供一个 红包幕帘/弹窗 的居中写法。注意,是严格居中,非常标准。为什么是移动端?你有见过PC网页端给你送红包的么? 在实战开发中,下面的这段代码,可以直接拿去用。注释详细,贴心无比。 ```html Document
    默认文档流中的页面主体
    ``` 实现效果: ![](http://img.smyhvae.com/20191010_1510.png) **补充**: 1、如果你的页面中,有很多弹窗,建议将弹窗封装成一个抽象组件。 2、任何弹窗,都需要解决“滚动穿透”的问题,本文篇幅有限,请自行查阅。 ## 最后一段 有些实现方式虽然简单,但必须要经得起千锤百炼。我们要做到**敬畏每一行代码**,不能浮于表面。团队开发,要的不是个性,而是**标准和规范**。 ## 参考链接 - [为什么「margin:auto」可以让块级元素水平居中?](https://www.zhihu.com/question/21644198/answer/22392394) - [七种方式实现垂直居中](https://jscode.me/t/topic/1936) - [margin:auto实现绝对定位元素的水平垂直居中](http://www.zhangxinxu.com/wordpress/2013/11/margin-auto-absolute-%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D-%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 03-CSS进阶/CSS开发积累.md ================================================ --- title: 认识Web和Web标准 publish: false --- ### 让flex盒子中的子元素们,居中 flex布局常用的三行代码: ``` display: flex; justify-content: center; // 子元素在横轴的对齐方式 (左右居中) align-items: center; // 子元素在竖轴的对齐方式(上下居中) ``` ### 让文字只显示一行,超出显示省略号 ```css overflow: hidden; white-space: nowrap; text-overflow: ellipsis; ``` ### 让文字最多只显示两行,超出后显示省略号 ```css display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; text-overflow: ellipsis; ``` 上面的代码中,我们把 `-webkit-line-clamp`的值设为1,也能达到“让文字只显示一行,超出显示省略号”的效果。 参考链接: ### 让文字最多只显示三行,超过后显示省略号 > 如果当前元素处于flex布局等复杂的场景中,那么,样式可能比较难调,用上面两种写法未必能达到预期效果。此时可以试试下面这种写法(多加了一行 `white-space: normal`)。 ```css display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis; white-space: normal; ``` ### 问题描述:文本内容里自带了换行,但是在前端没有展示出来 解决办法:增加如下属性即可。 ``` white-space: pre-wrap; ``` 或者: ``` white-space: pre-line; ``` ### 2019-11-12-CSS的逗号和空格 CSS的逗号一般写在()里。**不同属性值之间,用的是空格**,不是逗号。比如: ```css transform: translate(-50%, -50%); /* 这种写逗号 */ transform: translate(-50%, -50%) scale(0.5); /* 不同属性值之间,用的是空格 */ background-size: 100% 100%; /* 这里是空格,不是逗号 */ ``` ### 2019-11-01 人民币价格中的羊角符号,有半角和全角之分: - ¥半角 - ¥全角 可以看出,半角的宽度更小。考虑到容器的空间一般比较紧张,所以推荐使用**半角**。 ### 2019-11-19-鼠标悬停时,弹出提示文字 参考链接:[css实现鼠标悬浮后的提示效果](https://www.cnblogs.com/zhaojian-08/p/10074660.html) ### 2019-11-27-图片的宽度固定,高度自适应 这个场景下,别用background。直接放img元素就好了,将图片的高度设置为`auto`。 ### 2020-03-26-通过CSS扩大点击热区 ```css .button { position: relative; /* [其余样式] */ } .button::before { content: ''; position: absolute; top: -10px; right: -10px; bottom: -10px; left: -10px; } ``` 注意,button 里面不要写 `overflow: hidden` 属性,否则扩大的热区无效。 参考链接: ### 2020-10-09-上下滚动,不显示滚动条 ```css .sku_list { margin-left: 25rpx; display: flex; flex-wrap: wrap; height: 617rpx; overflow: hidden; overflow-y: scroll; /* 去掉滚动条 */ &::-webkit-scrollbar { display: none; } } ``` 注意,去掉滚动条的那行代码里,建议用`display: none;`,不要用`width: 0;`。因为,当你需要设置横向滚动的效果时,`display: none;`这个属性的效果更好,不会改变容器的size;`width: 0;`可能会改变容器的size。 参考链接: - [html设置局部区域上下滚动,不显示滚动条](https://blog.csdn.net/weixin_42157001/article/details/90176510) ### 2021-05-06-设置页面的宽高,撑满屏幕 一般来说,我们在开发一个页面的时候,默认是希望这个页面的宽高能够撑满屏幕的。代码可以这样写: ```css .app { width: 100vw; min-height: 100vh; } ``` ### 2021-09-13-JS中插入CSS 代码举例: ```js new_element = document.createElement("style"); new_element.innerHTML =(".tucao-content p{font-size:18px;}"); document.body.appendChild(new_element); ``` 参考链接: - js 插入公共css的方法:https://blog.csdn.net/u013970232/article/details/90578937 ================================================ FILE: 03-CSS进阶/CSS文章推荐.md ================================================ --- title: 认识Web和Web标准 publish: false --- ### 2020-03-16 Bootstrap 中文文档: ### 2020-03-13 - [CSS实现鼠标悬停弹出微信二维码](https://www.hanost.com/637.html) ================================================ FILE: 03-CSS进阶/CSS的一些小知识.md ================================================ --- title: 认识Web和Web标准 publish: false --- ## 隐藏盒子的几种方式 隐藏盒子,有以下几种方式: (1)方式一: ``` overflow:hidden; //隐藏盒子超出的部分 ``` (2)**方式二**: ``` display: none; 隐藏盒子,而且不占位置(用的最多) ``` 比如,点击`X`,关闭京东首页上方的广告栏。 (3)方式三: ``` visibility: hidden; //隐藏盒子,占位置。 visibility: visible; //让盒子重新显示 ``` (4)方式四: ``` opacity: 0; //设置盒子的透明度(不建议,因为内容也会半透明),占位置 ``` (4)方式五: ``` Position/top/left/...-999px //把盒子移得远远的,占位置。 ``` (5)方式六: ``` margin-left: 1000px; ``` ### 设置盒子的半透明 方式一:`opacity: 0.4`。优点是方便。缺点是:里面的内容也会半透明。 方式二:css3的技术来解决半透明。如下: - background: rgba(0,0,0,0.3); - background: rgba(0,0,0,.3); 备注:`a`指的是alpha透明度。 ### 给标签的形状设置为圆角矩形 ``` border-radius: 50%; border-radius: 10px 0 0 10px; ``` ### 行高的问题:儿子把父亲撑开 比如对于下面这样的标签: ```
    ``` 前置条件:如果我们给父亲div的行高设为31px,然后给儿子a的行高也设置为31 结果:当我们给儿子a设置了字体属性之后,会发现,父亲被撑高为32px了。因为font字体自身会比较大,多撑出了一个像素。 解决办法:**行内元素尽量不要设置font属性**。对于行内元素而言,如果它和父亲都设置了行高,就不要去给自己设置font属性了。要么就,不要同时设置行高。 ### 背景图不能撑开盒子 高和行高都可以城开盒子,但背景图不能撑开盒子。 ## JS ### 超链接``的href跳转 一个空白的超链接如下: ``` ``` 当点击超链接时,由于 href 的属性值的不同,可以产生很多种情况: ```bash href="" //刷新页面 href="#" //跳转到当前页面的顶部(不会刷新) href="javascript:void(0)" // 什么都不做 href="javascript:;" // 什么都不做 ``` ================================================ FILE: 03-CSS进阶/CSS面试题.md ================================================ --- title: 认识Web和Web标准 publish: false --- ## 常见问题 ### 你是如何理解 HTML 语义化的? **语义化**:指对文本内容的结构化(内容语义化),选择合乎语义的标签(代码语义化)。 **举例**:段落用 p,边栏用 aside,主要内容用 main 标签。 **好处:** - 便于开发者阅读和维护 - 有利于SEO:让浏览器的爬虫和辅助技术更好的解析, **语义化标签介绍**: 在HTML5出来之前,我们习惯于用div来表示页面的章节或者不同模块,但是`div`本身是没有语义的。但是现在,HTML5中加入了一些语义化标签,来更清晰的表达文档结构。 20180322_1120.jpg 参考链接: - [初探 · HTML5语义化](https://zhuanlan.zhihu.com/p/32570423) ### meta viewport 是做什么用的,怎么写? ```html ``` 控制页面在移动端不要缩小显示。 ### canvas 元素是干什么的? 看 MDN 的 [canvas 入门手册](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API)。 ### 说一下CSS盒模型 可以参考本人的另外一篇文章:《02-CSS基础/06-CSS盒模型详解》。 ### css reset 和 Normalize.css 有什么区别 > 此题考英文。 二者都是用来**统一**浏览器的默认样式: - reset:重置。相对「暴力」,不管你有没有用,统统重置成一样的效果,且影响的范围很大,讲求跨浏览器的一致性。(一刀切) - `Normalize.css` :标准化。相对「平和」,注重通用的方案,重置掉该重置的样式,保留有用的 user agent 样式,同时进行一些 bug 的修复,这点是 reset 所缺乏的。(去伪存真) 参考链接: - [Normalize.css 与传统的 CSS Reset 有哪些区别?](https://p.baidu.com/question/ab496162636234613761335c00) - [CSS3初始化代码Normalize.css中文版](http://www.bbsxiaomi.com/html_css/html5_css3/177.html) - [谈谈一些有趣的 CSS 话题](https://github.com/chokcoco/iCSS) - [前端面试之CSS总结(上)](https://segmentfault.com/a/1190000006890725) ### 选择器的优先级如何确定 - 选择器越具体,优先级越高。 #xxx 大于 .yyy - 同样优先级,写在后面的覆盖前面的。 - color: red !important; 优先级最高。 ### BFC 是什么 overflow:hidden :取消父子 margin 合并。 (另一种推荐做法:`padding-top: 0.1px;`) ### 如何清除浮动 (1)overflow: hidden (2).clearfix 清除浮动写在爸爸身上 ```css .clearfix::after { content: ''; display: block; clear: both; } /* 兼容 IE */ .clearfix { zoom: 1; } ``` ### 伪类和伪元素的区别是什么? 概念上的区别: - 伪类表示一种状态 - 伪元素是真的有元素。比如 ::after 是真的有元素,可以在页面上显示内容。 使用上的区别: - 伪类:使用单冒号 - 伪元素:使用双冒号 ## 参考链接 - [互联网公司招聘启事的正确阅读方式](https://zhuanlan.zhihu.com/p/33998813) ================================================ FILE: 04-JavaScript基础/01-编程语言和JavaScript简介.md ================================================ --- title: 01-编程语言和JavaScript简介 --- ## 计算机语言 ### 概念 **计算机语言**:人与计算机之间通信的语言。它是人与计算机之间传递信息的媒介,它通过特定的语法规则和语义约定,将人类可理解的指令转化为计算机可以执行的机器指令。 **计算机程序**:就是计算机所执行的一系列的**指令集合**,程序全部都是用我们所掌握的语言来编写的,如果我们要控制计算机,就需要通过计算机语言向计算机发出指令。 ### 计算机语言的分类 计算机语言的种类非常多,总的来说可以分成三大类:**机器语言、汇编语言和高级语言**。他们之间的转换过程如下: ![](https://img.smyhvae.com/image-20230820183113674.png) 计算机最终所执行的都是机器语言,它是由“0”和“1”组成的二进制数,二进制是计算机语言的基础。 **计算机语言**的范围比**编程语言**的范围更广,后者是前者的子集。比如: - HTML 是**标记语言**,CSS 是**样式语言**,这两个是属于**计算机语言**;但不属于**编程语言**,因为它们编写出来的不是**程序**,只是简单的标记和样式。 - JavaScript 是属于**编程语言**,当然也属于**计算机语言**。 计算机语言的分类如下: ![image-20231001002948987](https://img.smyhvae.com/202310010029086.png) 从机器语言到汇编语言,再到高级语言,这些语言的发展越来越高级,编写方式越来越接近人的思维。我们来具体看看这三种计算机语言的发展历史。 ### 机器语言 计算机的存储单元只有0和1两种状态。 将数字0和1按照一定的规律组成的编码称为**机器码**,也称为**二进制编码**或者**机器指令**或者**计算机指令**。用这些机器指令所编写的程序称为**机器语言**。 机器语言的优点是可以直接被计算机识别、直接操作硬件、不需要经过编译解析、程序的执行效率很高;缺点是可读性差、可维护性差、出错概率高。 现如今,几乎没有人用这种方式编程。 ### 汇编语言 对于人类来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性和维护性的问题,就诞生了汇编语言。 **汇编语言**:是二进制指令的文本形式,与指令是一一对应的关系,比如,加法指令00000011写成汇编语言就是 ADD;汇编语言使用助记符(Mnemonics)来代替和表示特定低级机器语言的操作。 汇编语言经过**汇编器**,可翻译成机器语言,进而被 CPU 直接执行,所以汇编语言是最底层的低级语言。 不同的硬件系统有不同的汇编语言语法。换而言之,每一种特定的汇编语言和其特定的机器语言指令集是一一对应的。汇编语言的常见的应用场景有:**操作系统内核、驱动程序、单片机程序**等。 举例:用汇编语言写一个简单的加法程序“3+5=8”,加数和结果分别存在内存单元NUM1、NUM2、NUM3中。 [代码实现](https://blog.csdn.net/qq_40871466/article/details/84720703): ```asm data segment num1 db 3 num2 db 5 num3 db ? data ends code segment assume cs:code,ds:data start:mov ax,data mov ds,ax mov al,num1 add al,num2 mov num3,al mov ax,4c00h int 21h code ends end start ``` ### 高级语言 高级语言更接近人的思维方式。 优点是可读性好、易于理解;上手门槛低,无需熟悉硬件知识即可进行编程入门的学习;很多高级语言可跨平台兼容,具备可移植性,在不同的机器上运行。 缺点是无法直接被计算机硬件识别,需要通过**编译器**翻译为机器语言后,才能在计算机上运行。 编译器的作用,就是将高级语言写好的程序,翻译成一条条机器指令。 ## 编程语言 ### 概念 **编程**:让计算机为解决特定的问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。 **编程语言**:是一种用于编写计算机**程序**的形式化语言。它定义了一套语法和规则,用来描述计算机程序的结构和逻辑,并精确定义了在不同情况下需要执行的行为。编程语言通过编写程序代码,将人类的思想和需求转化为计算机可以理解和执行的指令。不同的编程语言有不同的特点和用途,这些特点至少有: - 数据结构和算法、数据处理 - 流程控制(if语句、循环语句等) - 引用机制和重用机制 - 设计思想 不同的编程语言有不同的语法,需要遵守。常见的编程语言有 C 语言、C++、Java、JavaScript、Python 等。 如今通用的编程语言大致可以分成两类:**高级语言和汇编语言**。 - **高级语言**:主要是相对于低级语言而言,它并不是特指某一种具体的语言,而是包括了很多编程语言,比如:C 语言、C++、Java、C#、PHP、JavaScript、Python、Objective-C、Swift、Go 语言等。 - **汇编语言**:与机器语言实质是相同的,都是直接对硬件操作,只不过指令采用了英文缩写的标识符,容易识别和记忆。 ### 编程语言的发展 从汇编语言发展到现在,编程语言层出不穷,具体数目已无法考证,比较流行的有超过200种以上。 比如在1958年发明的 Lisp 语言,历史悠久(是世界上第二个发明出来的语言,只比 FORTRAN 语言晚一年),现在仍然非常流行,是人工智能领域最受欢迎的编程语言。《黑客与画家》这本书的作者也十分推崇 Lisp 语言。 ## 编译型语言 VS 解释型语言 ### 翻译器 计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所编写的高级语言翻译成机器语言,计算机才能执行程序。为此,我们需要一个翻译器。**程序语言翻译成机器语言的工具,被称为翻译器**。 由此可见,所谓的“翻译”,指的是将人类所编写的源代码转换(翻译)为机器能够执行的指令,这也被称为二进制化。 翻译器翻译的方式有两种:一种是**编译**,另一种是**解释**。两种方式之间的区别在于**翻译的时机**不同。 - **编译器**:在代码执行之前,事前把所有的代码一次性翻译好,生成中间代码文件,然后整体执行。 - **解释器**:边翻译,边执行(在代码执行时进行及时翻译,并立即执行)。当编译器以解释的方式运行时,也称之为解释器。 对应的语言,称之为“编译型语言”、“解释型语言”。 ### 1、编译型语言 - 定义:需要事先通过编译器**把所有的代码一次性翻译(编译/转换)好**,然后整体执行。比如 exe 文件。 - 优点:执行效率高,运行更快。 - 不足:移植性不好,不跨平台;编译之后如果需要修改就需要整个模块重新编译。 - 编译型语言举例:C、C++ 比如,C 语言的代码文件是`.c`后缀,翻译之后文件是`.obj`后缀,系统执行的是 obj 文件;再比如, java 语言的代码文件是`.java`后缀,翻译之后的文件是`.class`后缀。(但是,Java 语言不是严格的 编译型语言,这个稍后讲) 以 C 语言的`hello.c`来举例: ```c #include int main(int argc, char const *argv[]) { printf("Hello world!"); return 0; } ``` 对于以上 C 语言代码,main 是程序入口,实现的功能是打印字符串`hello world`到屏幕上,编译和执行过程如下: 1. C 语言代码经过预处理(比如依赖处理、宏替换)。以上方代码示例,`#include`会在预处理阶段被替换。 2. 编译:编译器会把 C 语言翻译成汇编程序。一条 C 语言通常被编译为多条汇编代码,同时编译器会对程序进行优化,生成目标汇编程序。旁征博 3. 汇编语言通过汇编器再汇编成目标程序`hello.o`。 4. 链接:程序中往往包含一些共享目标文件,如示例代码中的`printf()`函数位于静态库,需要经过链接器进行链接。 ![20211030-0031-2](https://img.smyhvae.com/20211030-0031-2.png) (上方图片来源:[JavaScript 基础-基本概念](https://www.jianshu.com/p/230093183f47) ) ![20211030-0026-2](http://img.smyhvae.com/20211030-0026-2.png) (上方图片来源:[编译型语言](https://p.0x06.cn/zh/program/) ) ### 2、解释型语言 - 定义:在运行过程中(runtime)通过解释器**边翻译边执行**,也就是逐行翻译。不需要事先一次性翻译,而是在运行时,边翻译变执行(翻译一行,执行一行)。 - 优点:移植性好,跨平台。 - 缺点:运行速度不如编译型语言。 - 解释型语言举例:JavaScript、PHP、Python。 为什么 JS 是解释型语言呢?这和浏览器的工作原理有关。浏览器中有一个专门的“JS 解析器”可以让 JS 边解析边执行。 由于少了事先编译这一步骤,所以解释型语言开发起来尤为方便,但是解释型语言运行较慢也是它的劣势。不过解释型语言中使用了 JIT 技术,使得运行速度得以改善。 ### Java 语言 Java 语言是属于半编译半解释型语言。翻译过程: (1)编译:`.java`代码文件先通过 javac 命令编译成`.class`文件。 (2)执行:`.class`文件再通过 jvm 虚拟机,解释执行。有了 jvm 的存在, java 就可以跨平台了。 ## JavaScript 的历史和发展 ### JavaScript 的历史 1、JavaScript 诞生于**1995 年**,是由**网景**公司(Netscape)的员工 Brendan Eich(兰登 • 艾奇,1961 年~)发明,最初命名为 LiveScript。1995 年 12 月与 SUN 公司合作,因市场宣传需要,为了蹭 Java 的热度,改名为 JavaScript。 发明这个语言的[背景](https://www.ruanyifeng.com/blog/2011/06/birth_of_javascript.html)是这样的:1994 年网景公司发布历史上第一个比较成熟的浏览器(Navigator 0.9), 但是只能浏览不能**交互**。1995 年为了解决表单有效性验证就要与服务器进行多次地往返交互问题,网景公司录用 Brendan Eich(兰登 • 艾奇),他只用了 10 天就设计并研发出 一种网页脚本语言——LiveScript 语言的第一版。 由于 Sun 公司当时的 Java 语言特别火,所以为了傍大牌,就借势改名为 JavaScript。Java 和 JavaScript 的关系,就好比:雷锋和雷峰塔的关系、老婆和老婆饼的关系、北大和北大青鸟的关系。“北大青鸟”就是傍“北大”的大牌。 JavaScript 是 Sun 公司注册并授权给 Netscape 使用的商标。后来 Sun 公司被 Oracle 收购,JavaScript 版权归 Oracle 所有。 1996 年,微软为了抢占市场,推出了`JScript`在 IE3.0 中使用。 ### ECMAScript 标准 ECMAScript 是一种由 ECMA 组织制定和发布的脚本语言规范。 1996 年 11 月网景公司向 ECMA(European Computer Manufacturers Association,欧洲电脑制造商协会,属于国际标准化组织)提交了 JS的语言标准,将其成为国际标准,以此对抗微软。 - ECMA 的技术委员负责制定和审核这个标准,成员由业内的大公司派出的工程师组成。该委员会定期开会,所有的邮件讨论和会议记录,都是公开的。 - 1997年7月,ECMA 组织发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言规范称为 ECMAScript,这个版本就是 ECMAScript 1.0 版。简而言之,ECMA-262是一份标准文件,定义了 ECMAScript 这个语言规范。 - JavaScript 成为了 ECMAScript最著名的实现之一。ECMAScript 和 JavaScript 的关系是,前者是后者的语法规范,后者是前者的一种实现。 - 除此之外,ActionScript 和 JScript 也是遵守 ECMAScript 规范的语言。 - ECMAScript 只用来标准化 JavaScript 这种语言的基本语法。与部署环境相关的标准则由其他标准规定,比如 DOM 的标准就是由 W3C组织(World Wide Web Consortium)制定的。 同时期还有其他的网页语言,比如 VBScript、JScript 等等,但是后来都被 JavaScript 打败了,所以现在的浏览器中,只运行一种脚本语言就是 JavaScript。JavaScript 是世界上用的最多的**脚本语言**。 JavaScript 是由公司开发而成的,问题是不便于其他的公司拓展和使用。所以 ECMA 组织,牵头制定了 JavaScript 的标准,取名为 ECMAScript。 简单来说,**ECMAScript 不是一门语言,而是一个标准**。ECMAScript 规定了 JS 的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套 JS 语法工业标准。 ECMAScript 在 2015 年 6 月,发布了 ECMAScript 6 版本(ES6),语言的能力更强,包含了很多新特性。 ### JavaScript语言是个大杂烩 Brendan Eich 这位天才只用了10天就设计出了 JS,但这门语言当时更像是一个[大杂烩](https://wangdoc.com/javascript/basic/history): - 基本语法:借鉴 C 语言和 Java 语言。 - 数据类型和数据结构:借鉴 Java 语言,包括将值分成原始值和对象两大类。 - 函数:借鉴 Scheme 语言和 Awk 语言,将函数当作第一等公民,并引入闭包。 - 基于原型的继承机制:借鉴 Self 语言(Smalltalk 的一种变种)。 - 正则表达式:借鉴 Perl 语言。 - 字符串和数组处理:借鉴 Python 语言。 由于设计时间太短,语言的一些细节考虑得不够严谨,导致后来很长一段时间,Javascript写出来的程序混乱不堪。所以,Javascript语言实际上是两种语言风格的混合体:(简化的)函数式编程+(简化的)面向对象编程。这是由Brendan Eich(函数式编程)与网景公司(面向对象编程)共同决定的。 十八世纪英国文学家约翰逊博士有一句名言说得好:“这个作品是好的,也是原创的,它的优秀之处并非原创,它的原创之处并不优秀。”(It is both good and original; but the part that is good is not original, and the part that is original is not good.) 后来,随着ES6语法的不断改进,JS语言越来越优秀。ECMA 和 ECMAScript 赋予了 JavaScript 新的能力和活力。 ### JavaScript 的发展:蒸蒸日上 2003 年之前,JavaScript 被认为“牛皮鲜”,用来制作页面上的广告,弹窗、漂浮的广告。什么东西让人烦,什么东西就是 JavaScript 开发的。所以很多浏览器就推出了屏蔽广告功能。 2004 年,JavaScript 命运开始改变。那一年,**谷歌公司开始带头使用 Ajax 技术**,Ajax 技术就是 JavaScript 的一个应用。并且,那时候人们逐渐开始提升用户体验了。Ajax 有一些应用场景。比如,当我们在搜索引擎框搜文字时,输入框下方的智能提示,可以通过 Ajax 实现。比如,当我们注册网易邮箱时,能够及时发现用户名是否被占用,而不用跳到另外一个页面。从 2005 年开始,几乎整个 B/S 开发界都在热情地追捧 Ajax。 2007 年乔布斯发布了第一款 iPhone,这一年开始,更多的用户使用移动设备上网。这一年,互联网开始标准化,按照 W3C 规则三层分离,JavaScript 越来越被重视。**JavaScript 在移动页面中,也是不可或缺的**。 2010 年,人们更加了解**HTML5 技术**,**HTML5 推出了一个东西叫做 Canvas**(画布),工程师可以在 Canvas 上进行游戏制作,利用的就是 JavaScript。 2011 年,**Node.js 诞生**,使 JavaScript 能够开发服务器端的程序。 如今,**WebApp**已经非常流行,也就是用**网页技术开发手机应用**。手机系统有 iOS、安卓。公司如果要开发一个“美团”App,就需要招聘四队人马:iOS 工程师 10 人、安卓工程师 12 人,前端工程师 8 人、后端工程师 16 人,共 50 人左右,开发成本巨大;而且如果要做需求迭代,就要改 3 个版本。现在,假设公司都用 web 技术,用 html+css+javascript 这一套技术就可以开发多种终端的页面。也易于迭代(网页如果改变,则所有的终端都会生效)。 有个故事是这么说的: > 2040 年,某年轻产品经理说:有没有那么一个东西,不需要下载客户端,不需要限制品牌系统,无论是鸿蒙、安卓苹果,Linux 和 Windows 等等都能实现,无差别预览信息。用户也能在这里交流,如果我们开发出来这个,我们这个产品的前景一定十分宽广。 > > 这时候一位 40 岁的老同志说:你说的这个是不是叫网页? > > 这是半个世纪前就有的。 虽然目前 WebApp(Web 应用)在功能和性能上的体验远不如 Native App(原生应用),但是“**在原生 App 中内嵌一些 H5 页面**”已经是一种趋势。 JavaScript 的发展,正在大放异彩,正如周爱民的《JavaScript 语言精髓与编程实战》中所描述的那样: > 是 ECMA 赋予了 JavaScript 新的能力和活力。 > > 在 2015 年 6 月,ES6 发布了。这个 ECMAScript 版本几乎集成了当时其他语言梦寐以求的所有明星特性,并优雅地、不留后患地解决了几乎所有的 JavaScript 遗留问题—当然,其中那些最大的、最本质的和核心的问题其实都已经在 ES5 推出时通过“严格模式(strict mode)”解决了。 > > ES6 提出了四大组件:Promise、类、模块、生成器/迭代器。这事实上是在并行语言、面向对象语言、结构化语言和函数式语言四个方向上的奠基工作。相对于这种重要性来说,其他类似于解构、展开、代理等看起来很炫很实用的特性,反倒是浮在表面的繁华了。 > > 主流引擎厂商开始通过 ES6 释放出它们的能量,于是 JavaScript 在许多新的环境中被应用起来,大量的新技术得以推动,例如,WebAssembly、Ohm、Deeplearn.js、TensorFlow.js、GPU.js、GraphQL、NativeScript 等。有了 Babel 这类项目的强大助力,新规范得以“让少数人先用起来”,而标准的发布也一路披荆斩棘,以至于实现了“一年一更”。 ### JS的应用越来越广泛 JS正在越来越多的场景和环境中得到应用,具体包括: - Web端网页 - H5页面(即移动端网页) - 小程序开发、公众号开发 - 跨端开发:Taro、ReactNative、Weex 等框架 - PC客户端/桌面应用开发:Electron 框架。比如 VS Code、Typora等软件就是基于 Electron 框架进行开发的。 - 后端开发:Node.js ## JavaScript语言的介绍 ### JavaScript 入门易学性 - JavaScript 对初学者比较友好。可以使用任何文本编辑工具编写,只需要浏览器就可以执行程序。 - JavaScript 是有界面效果的(相比之下,C 语言只有白底黑字)。 - JavaScript 的入门较简单(进阶不易)。比如,JS 是**弱变量类型**的语言,变量只需要用 var/let/const 来声明。而 Java 中变量的声明,要根据变量的类型来定义。 Java 中需要这样定义变量: ```java int a; float a; double a; String a; boolean a; ``` 而 JS 中,只需要用一种方式来定义: ```JavaScript // ES5 写法 var a; // ES6 写法 const b; let c; ``` ### JavaScript 既是前端语言,又是后端语言 当 JavaScript 运行在用户的终端网页,而不是运行在服务器上的时候,我们称之为“**前端语言**”。前端语言是服务于页面的显示和交互,不能直接操作数据库。 **后端语言**是运行在服务器上的,比如 Java、C++、PHP 等等,这些语言都能够操作数据库(对数据库进行“增删改查”),并在后台执行各种任务。 另外,Node.js 是用 JavaScript 开发的,我们也可以用 Node.js 技术进行服务器端编程。 ### JavaScript 的组成 JavaScript 基础分为三个部分: - **ECMAScript**:JavaScript 的**语法标准**。包括变量、表达式、运算符、函数、if 语句、for 语句等。 - **DOM**:Document Object Model(文档对象模型),JS 操作**页面上的元素**(标签)的 API。比如让盒子移动、变色、改变大小、轮播图等等。 - **BOM**:Browser Object Model(浏览器对象模型),JS 操作**浏览器部分功能**的 API。通过 BOM 可以操作浏览器窗口,比如弹框、控制浏览器跳转、获取浏览器分辨率等等。 通俗理解就是:ECMAScript 是 JS 的语法;DOM 和 BOM 是浏览器运行环境为 JS 提供的 API。 ### JavaScript 的特点 1、解释型语言。 2、遵守ECMAScript 标准。 3、单线程。 ## 参考链接 - [编程语言的一些概念](https://jameszhan.github.io/2014/09/25/programming-languages-concepts.html) ![pl_history](https://img.smyhvae.com/0964c49a88040dc69666487c6cbb6159d0dfd7e0.png) - [编程语言70年:谁是世界上最好的编程语言?](https://zhuanlan.zhihu.com/p/611924622) ![img](https://img.smyhvae.com/v2-fcdbef5c589522df3960e4cea9825a71_1440w.png) - [汇编语言(转) | Xian Rong](https://xianrong.github.io/2017/11/14/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%28%E8%BD%AC%29/) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/02-开始写JavaScript:hello world.md ================================================ --- title: 02-开始写JavaScript:hello world --- ## 开始写第一行 JavaScript:hello world JS 代码的书写位置在哪里呢?这个问题,也可以理解成:引入 JS 代码,有哪几种方式?有三种方式:(和 CSS 的引入方式类似) 1. **行内式**:写在HTML标签内部。 2. **内嵌式**(内联式):写在 script 标签中。 3. **外链式**:引入外部 JS 文件。 ### 方式 1:行内式 代码举例: ```javascript ``` 完整的可执行代码如下: ```html Document ``` 解释: - 可以将单行或少量 JS 代码写在 HTML 标签的事件属性中(以 on 开头的属性),比如放在上面的 `onclick`点击事件中。 - 这种书写方式,不推荐使用,原因是:可读性差,尤其是需要编写大量 JS 代码时,很难维护;引号多层嵌套时,也容易出错。 - 关于代码中的「引号」,在 HTML 标签中,我们推荐使用双引号,JS 中推荐使用单引号。 ### 方式 2、内嵌式(内联式) 我们可以在 HTML 页面的 `` 标签里放入``标签对,并在` ``` 解释: - tyue属性中的text 表示纯文本,因为 JavaScript 代码本身就是纯文本。当然,type属性可以省略,因为 JavaScript 是所有现代浏览器以及 HTML5 中的默认脚本语言。 - 可以将多行 JS 代码写到 ` ``` 解释: - 上面这段代码,依然是放到 body 标签里,可以和内嵌的 JS 代码并列。 - 上方代码的 script 标签已经引入了外部 JS 文件,所以这个标签里面,不可以再写 JS 代码。 - 方式2和方式3不能混用。 **总结**: 我们在实战开发中,基本都是采用方式 3,因为将 html 文件和 js 文件分开的方式,有利于代码的结构化和复用,符合高内聚、低耦合的思想。很少会有人把一大堆 JS 代码塞到 单个 html 文件里。 ## 拓展知识 ### window.onload:先加载,最后执行 上面的三种方式,有个共同的地方是:JS 代码都是写在 body 标签中的,准确来说,是在页面标签元素(比如 `title` 标签)的后面,在 ``结束标签的前面。 为什么一般是按这样的顺序来写呢?这是因为:浏览器默认遵循**HTML文档**的加载顺序,即按照**从上至下**的加载顺序解析网页(这句话很重要)。所以,通常情况下,我们会把JS代码写在 body 内部的末尾。 然而,当你需要通过 JS 来操作界面上的标签元素时,假如将 JS 代码或者` ``` 等价于代码二: ```html ``` 2、每一条语句末尾建议加上**分号**。 当存在换行符(line break)时,大多数情况下可以省略分号,JavaScript 会将换行符理解成“隐式”的分号,进而自动添加分号。 也就是说,分号不是必须加的,如果不写分号,浏览器会在换行符的位置自动添加分号,但是会消耗一些系统资源和性能,甚至有可能**添加错误**,导致一些奇怪的bug。 3、所有的符号都是英文的,比如括号、引号、分号。 如果你用的是搜狗拼音,**建议不要用 shift 切换中英文**(可以在搜狗软件里进行设置),不然很容易输入中文的分号;建议用 ctrl+space 切换中英文输入法。 4、JS 严格区分大小写。HTML和CSS不区分大小写,但是 JS 严格区分大小写。 ## 前端代码的注释 注释:即解释、注解。注释有利于提高代码的可读性,且有利于程序员之间的沟通和工作交接。 注释可以用来解释某一段代码的功能和作用;通过注释,还可以补充代码中未体现出来的部分。代码只是开发工作的结果,注释可以阐述开发工作的过程、思路、注意事项,以及踩过的坑。 注释可以是任何文字,可以写中文。 **我们不要把 HTML、CSS、JavaScript 三者的注释格式搞混淆了**。 ### HTML 的注释 格式: ```html ``` 举例: ```html
    ``` ### CSS 的注释 举例: ```html ``` 注意:CSS 只有`/* */`这种注释,没有`//`这种注释。而且注释要写在` ``` 页面效果: ![](http://img.smyhvae.com/20181229_1410.png) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/13-对象简介.md ================================================ --- title: 13-对象简介 --- ## 对象简介 ### 对象的概念 在 JavaScript 中,对象是一组**无序**的相关属性和方法的集合。 **对象的作用是:封装信息**。比如 Student 类里可以封装学生的姓名、年龄、成绩等。 对象具有**特征**(属性)和**行为**(方法)。 ### 对象包括哪些数据类型 我们知道,JS 中的数据类型,包括以下几种: - **基本数据类型(值类型)**:String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。 - **引用数据类型(引用类型)**:Object 对象。 只要不是那七种基本数据类型,就全都是对象。对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。 ### 对象的分类 1、内置对象: - 由 ES 标准中定义的对象,在任何的 ES 的实现中都可以使用。 - 比如:Object、Math、Date、String、Array、Number、Boolean、Function 等。 2、宿主对象: - 由 JS 的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。 - 比如 BOM、DOM,比如`console`、`document`。 3、自定义对象: - 由开发人员自己创建的对象。 通过 new 关键字创建出来的对象实例,都是属于对象类型。 ## 自定义对象 ### 为什么需要自定义对象 保存一个值时,可以使用**变量**,保存多个值(一组值)时,可以使用**数组**。 比如,如果要保存一个人的信息,通过数组的方式可以这样保存: ```javascript const arr = ['王二', 35, '男', '180']; ``` 上面这种表达方式比较乱。而如果用 JS 中的**自定义对象**来表达,**结构会更清晰**。如下: ```javascript const person = { name: 'qianguyihao', age: 30, sex: '男', favor: ['阅读', '羽毛球'], sayHi: function () { console.log('qianguyihao'); }, }; ``` 由此可见,自定义对象里面的属性均是**键值对(key: value)**,表示属性和值的映射关系: - 键/属性:属性名。 - 值:属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。 ### 自定义对象的语法 语法如下: ```js const obj = { key: value, key: value, key: value, }; ``` key 和 value 之间用冒号分隔,每组 key:vaue 之间用逗号分隔,最后一对 key:value 的末尾可以写逗号,也可以不写逗号。 问:对象的属性名是否需要加引号? 答:如果属性名不符合 JS 标识符的命名规范,则需要用引号包裹。比如: ```js const person = { 'my-name': 'qianguyihao', }; ``` 补充:其实,JS 的内置对象、宿主对象,底层也是通过自定义对象的形式(也就是键值对的形式)进行封装的。 ## 对象的属性值补充 ### 什么叫对象的方法【重要】 对象的属性值可以是任意的数据类型,也可以是个**函数**(也称之为方法)。换而言之,**如果对象的属性值是函数,则这个函数可被称之为对象的“方法”**。 ```javascript const obj = new Object(); obj.sayName = function () { console.log('qianguyihao'); }; // 没加括号,就是获取方法 console.log(obj.sayName); console.log('-----------'); // 加了括号,就是调用方法。即:执行函数内容,并执行函数体的内容 console.log(obj.sayName()); ``` 打印结果: ![](https://img.smyhvae.com/20221014_1130.png) ### 对象中的属性值,也可以是一个对象 举例: ```javascript //创建对象 obj1 var obj1 = new Object(); obj1.test = undefined; //创建对象 obj2 var obj2 = new Object(); obj2.name = 'qianguyihao'; //将整个 obj2 对象,设置为 obj1 的属性 obj1.test = obj2; console.log(obj1.test.name); ``` 打印结果为:qianguyihao ## 传值和传址的区别 ### 对象保存在哪里 1、基本数据类型的值直接保存在**栈内存**中,变量与变量之间是独立的,值与值之间是独立的,修改一个变量不会影响其他的变量。 2、对象是保存到**堆内存**中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。变量保存的是对象的内存地址(对象的引用)。换而言之,对象的值是保存在**堆内存**中的,而对象的引用(即变量)是保存在**栈内存**中的。 **如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响**。这句话很重要,我们来看看下面的例子。 ### 传值 代码举例: ```js let a = 1; let b = a; // 将 a 赋值给 b b = 2; // 修改 b 的值 ``` 上方代码中,当我修改 b 的值之后,a 的值并不会发生改变。这个大家都知道。我们继续往下看。 ### 传址(一个经典的例子) 代码举例: ```javascript var obj1 = new Object(); obj1.name = '孙悟空'; var obj2 = obj1; // 将 obj1 的地址赋值给 obj2。从此, obj1 和 obj2 指向了同一个堆内存空间 //修改obj2的name属性 obj2.name = '猪八戒'; ``` 上面的代码中,当我修改 obj2 的 name 属性后,会发现,obj1 的 name 属性也会被修改。因为 obj1 和 obj2 指向的是堆内存中的同一个地址。 这个例子要尤其注意,实战开发中,很容易忽略。 对于引用类型的数据,赋值相当于地址拷贝,a、b 指向了同一个堆内存地址。所以改了 b,a 也会变;本质上 a、b 就是一个东西。 如果你打算把引用类型 A 的值赋值给 B,让 A 和 B 相互不受影响的话,可以通过 Object.assign() 来复制对象。效果如下: ```js var obj1 = { name: '孙悟空' }; // 复制对象:把 obj1 赋值给 obj3。两者之间互不影响 var obj3 = Object.assign({}, obj1); ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/14-基本包装类型.md ================================================ --- title: 14-基本包装类型 --- ## 基本数据类型不能绑定属性和方法 属性和方法只能添加给对象,不能添加给基本数据类型。我们拿字符串来举例。 **1、基本数据类型:** 基本数据类型`string`是**无法绑定属性和方法**的。 ```javascript var str = 'qianguyihao'; str.aaa = 12; console.log(typeof str); //打印结果为:string console.log(str.aaa); //打印结果为:undefined ``` 上方代码中,当我们尝试打印`str.aaa`的时候,会发现打印结果为:undefined。也就是说,不能给 `string` 绑定属性和方法。 当然,我们可以打印 str.length、str.indexOf("m")等等。因为这两个方法的底层做了数据类型转换(**临时**将 `string` 字符串转换为 `String` 对象,然后再调用内置方法),也就是我们在下一段将要讲到的**包装类**。 **2、引用数据类型:** 引用数据类型`String`是可以绑定属性和方法的。如下: ```javascript var strObj = new String('smyhvae'); strObj.aaa = 123; console.log(strObj); console.log(typeof strObj); //打印结果:Object console.log(strObj.aaa); ``` 打印结果: ![](http://img.smyhvae.com/20180202_1351.png) 内置对象 Number 也有一些自带的方法,比如: - Number.MAX_VALUE; - Number.MIN_VALUE; 内置对象 Boolean 也有一些自带的方法,但是用的不多。 ### 基本包装类型 ### 介绍 我们都知道,JS 中的数据类型包括以下几种。 - 基本数据类型:String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。 - 引用数据类型:Object 对象。 JS 为我们提供了三个**基本包装类**: - String():将基本数据类型字符串,转换为 String 对象。 - Number():将基本数据类型的数字,转换为 Number 对象。 - Boolean():将基本数据类型的布尔值,转换为 Boolean 对象。 通过上面这这三个包装类,我们可以**将基本数据类型的数据转换为对象**。 代码举例: ```javascript let str1 = 'qianguyihao'; let str2 = new String('qianguyihao'); let num = new Number(3); let bool = new Boolean(true); console.log(typeof str1); // 打印结果:string console.log(typeof str2); // 注意,打印结果:object ``` **需要注意的是**:我们在实际应用中一般不会使用基本数据类型的**对象**。如果使用基本数据类型的对象,在做一些比较时可能会带来一些**不可预期**的结果。 比如说: ```javascript var boo1 = new Boolean(true); var boo2 = new Boolean(true); console.log(boo1 === boo2); // 打印结果竟然是:false ``` 再比如说: ```javascript var boo3 = new Boolean(false); if (boo3) { console.log('qianguyihao'); // 这行代码竟然执行了 } ``` ### 基本包装类型的作用 当我们对一些基本数据类型的值去调用属性和方法时,JS引擎会**临时使用包装类将基本数据类型转换为引用数据类型**(即“隐式类型转换”),这样的话,基本数据类型就有了属性和方法,然后再调用对象的属性和方法;调用完以后,再将其转换为基本数据类型。 举例: ```js var str = 'qianguyihao'; console.log(str.length); // 打印结果:11 ``` 比如,上面的代码,执行顺序是这样的: ```js // 步骤(1):把简单数据类型 string 转换为 引用数据类型 String,保存到临时变量中 var temp = new String('qianguyihao'); // 步骤(2):把临时变量的值 赋值给 str str = temp; // 步骤(3):销毁临时变量 temp = null; ``` ## 在底层,字符串以字符数组的形式保存 在底层,字符串是以字符数组的形式保存的。代码举例: ```javascript var str = 'smyhvae'; console.log(str.length); // 获取字符串的长度 console.log(str[2]); // 获取字符串中的第3个字符(下标为2的字符) ``` 上方代码中,`smyhvae`这个字符串在底层是以`["s", "m", "y", "h", "v", "a", "e"]`的形式保存的。因此,我们既可以获取字符串的长度,也可以获取指定索引 index 位置的单个字符。这很像数组中的操作。 再比如,String 对象的很多内置方法,也可以直接给字符串用。此时,也是临时将字符串转换为 String 对象,然后再调用内置方法。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/15-内置对象 String:字符串的常见方法.md ================================================ --- title: 15-内置对象 String:字符串的常见方法 --- ## 内置对象简介 > JavaScript 中的对象分为3种:自定义对象、内置对象、浏览器对象。 > 前面两种对象:是JS的基础内容,属于 ECMAScript; 第三个浏览器对象:属于JS独有,即 JS 内置的API。 **内置对象**:就是指这个语言自带的一些对象,供开发者使用,这些对象提供了一些常用或者基本而必要的功能(属性和方法)。 内置对象最大的优点就是帮助我们快速开发。 **JavaScript的内置对象**: | 内置对象 | 对象说明 | |:-------------|:-------------| | Arguments | 函数参数集合| | Array | 数组| | Boolean | 布尔对象| | Math | 数学对象| | Date | 日期时间| | Error | 异常对象| | Function | 函数构造器| | Number | 数值对象| | Object | 基础对象| | RegExp | 正则表达式对象| | String | 字符串对象| ## 字符串前言 > 在日常开发中,String 对象(字符串对象)的使用频率是非常高的。所以有必要详细介绍。 需要注意的是:**字符串的所有方法,都不会改变原字符串**(字符串的不可变性),操作完成后会返回一个新的值。 字符串的常见方法如下。 ## 查找字符串 ### 1、indexOf()/lastIndexOf():获取字符串中指定内容的索引 > 这个方法,是使用频率最高的一个方法。 **语法 1**: ```javascript 索引值 = str.indexOf(想要查询的字符串); ``` 备注:`indexOf()` 是从前向后查找字符串的位置。同理,`lastIndexOf()`是从后向前寻找。 **解释**:可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回其**第一次出现**的索引;如果没有找到指定的内容,则返回 -1。 因此可以得出一个重要技巧: - **如果获取的索引值为 0,说明字符串是以查询的参数为开头的**。 - 如果获取的索引值为-1,说明这个字符串中没有指定的内容。 举例 1:(查找单个字符) ```javascript const str = 'abcdea'; //给字符查索引(索引值为0,说明字符串以查询的参数为开头) console.log(str.indexOf('c')); console.log(str.lastIndexOf('c')); console.log(str.indexOf('a')); console.log(str.lastIndexOf('a')); ``` 打印结果: ![](http://img.smyhvae.com/20180202_1420.png) 举例 2:(查找字符串) ```js const name = 'qianguyihao'; console.log(name.indexOf('yi')); // 打印结果:6 ``` **语法 2**: 这个方法还可以指定第二个参数,用来指定查找的**起始位置**。语法如下: ```javascript 索引值 = str.indexOf(想要查询的字符串, [起始位置]); ``` 举例 3:(两个参数时,需要特别注意) ```javascript var str = 'qianguyihao'; result = str.indexOf('a', 3); // 从下标为3的位置开始查找 'a'这个字符 【重要】 console.log(result); // 打印结果:9 ``` 上方代码中,`indexOf()`方法中携带了两个参数,具体解释请看注释。 ### indexOf 举例 **案例**:查找字符串"qianguyihao"中,所有 `a` 出现的位置以及次数。 思路: (1)先查找第一个 a 出现的位置。 (2)只要 indexOf 返回的结果不是 -1 就继续往后查找。 (3)因为 indexOf 只能查找到第一个,所以后面的查找,可以利用第二个参数,在当前索引加 1,从而继续查找。 代码实现: ```js var str = 'qianguyihao'; var index = str.indexOf('a'); var num = 0; while (index !== -1) { console.log(index); num++; // 每打印一次,就计数一次 index = str.indexOf('a', index + 1); } console.log('a 出现的次数是: ' + num); ``` ### 2、search():获取字符串中指定内容的索引(参数里一般是正则) **语法**: ```javascript 索引值 = str.search(想要查找的字符串); 索引值 = str.search(正则表达式); ``` 备注:`search()` 方法里的参数,既可以传字符串,也可以传正则表达式。 **解释**:可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回其**第一次出现**的索引;如果没有找到指定的内容,则返回 -1。 举例: ```js const name = 'qianguyihao'; console.log(name.search('yi')); // 打印结果:6 console.log(name.search(/yi/i)); // 打印结果:6 ``` 备注:上方的`/yi/i`采用的是正则表达式的写法,意思是,让 name去匹配字符`yi`,忽略大小写。我们在后面会专门介绍正则表达式。 ### 3、includes():字符串中是否包含指定的内容 **语法**: ```js 布尔值 = str.includes(想要查找的字符串, [position]); ``` **解释**:判断一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回 true;否则返回 false。 参数中的 `position`:如果不指定,则默认为0;如果指定,则规定了检索的起始位置。 ```js const name = 'qianguyihao'; console.log(name.includes('yi')); // 打印结果:true console.log(name.includes('haha')); // 打印结果:false console.log(name.includes('yi',7)); // 打印结果:false ``` ### 4、startsWith():字符串是否以指定的内容开头 **语法**: ```js 布尔值 = str.startsWith(想要查找的内容, [position]); ``` **解释**:判断一个字符串是否以指定的子字符串开头。如果是,则返回 true;否则返回 false。 **参数中的position**: - 如果不指定,则默认为0。 - 如果指定,则规定了**检索的起始位置**。检索的范围包括:这个指定位置开始,直到字符串的末尾。即:[position, str.length) 举例: ```js const name = 'abcdefg'; console.log(name.startsWith('a')); // 打印结果:true console.log(name.startsWith('b')); // 打印结果:false // 因为指定了起始位置为3,所以是在 defg 这个字符串中检索。 console.log(name.startsWith('d',3)); // 打印结果:true console.log(name.startsWith('c',3)); // 打印结果:false ``` ### 5、endsWith():字符串是否以指定的内容结尾 **语法**: ```js 布尔值 = str.endsWith(想要查找的内容, [position]); ``` **解释**:判断一个字符串是否以指定的子字符串结尾。如果是,则返回 true;否则返回 false。 **参数中的position**: - 如果不指定,则默认为 str.length。 - 如果指定,则规定了**检索的结束位置**。检索的范围包括:从第一个字符串开始,直到这个指定的位置。即:[0, position) - 或者你可以这样简单理解:endsWith() 方法里的position,表示**检索的长度**。 注意:startsWith() 和 endsWith()这两个方法,他们的 position 的含义是不同的,请仔细区分。 举例: ```js const name = 'abcdefg'; console.log(name.endsWith('g')); // 打印结果:true console.log(name.endsWith('f')); // 打印结果:false // 因为指定了截止位置为3,所以是在 abc 这个长度为3字符串中检索 console.log(name.endsWith('c', 3)); // 打印结果:true console.log(name.endsWith('d', 3)); // 打印结果:false ``` 注意看上方的注释。 参考链接:[JavaScript endsWith()介绍](https://www.softwhy.com/article-2885-1.html) ## 获取指定位置的字符 ### 1、charAt(index) 语法: ```javascript 字符 = str.charAt(index); ``` 解释:返回字符串指定位置的字符。这里的 `str.charAt(index)`和`str[index]`的效果是一样的。 注意:字符串中第一个字符的下标是 0。如果参数 index 不在 [0, string.length) 之间,该方法将返回一个空字符串。 **代码举例**: ```javascript var str = new String('smyhvae'); for (var i = 0; i < str.length; i++) { console.log(str.charAt(i)); } ``` 打印结果: ![](http://img.smyhvae.com/20180202_1401.png) 上面这个例子一般不用。一般打印数组和 json 的时候用索引,打印 String 不建议用索引。 ### 2、str[index] `str.charAt(index)`和`str[index]`的效果是一样的,不再赘述。区别在于:`str[index]`是 H5 标准里新增的特性。 ### 3、charCodeAt(index) 语法: ```javascript 字符 = str.charCodeAt(index); ``` 解释:返回字符串指定位置的字符的 Unicode 编码。不会修改原字符串。 在实际应用中,通过这个方法,我们可以判断用户按下了哪个按键。 **代码举例**:打印字符串的**占位长度**。 提示:一个英文占一个位置,一个中文占两个位置。 思路:判断该字符是否在 0-127 之间(在的话是英文,不在是非英文)。 代码实现: ```html ``` 打印结果: ``` 30 24 ``` 从打印结果可以看出:字符串的长度是 24,但是却占了 30 个字符位(一个中文占两个字符位)。 另外,sort()方法其实底层也是用到了 charCodeAt(),因为用到了 Unicode 编码。 ## 字符串截取 ### 1、slice() > slice() 方法用的最多。 语法: ```javascript 新字符串 = str.slice(开始索引, 结束索引); //两个参数都是索引值。包左不包右。 ``` 解释:从字符串中截取指定的内容。不会修改原字符串,而是将截取到的内容返回。 注意:上面的参数,包左不包右。参数举例如下: - `(2, 5)` 截取时,包左不包右。 - `(2)` 表示**从指定的索引位置开始,截取到最后**。 - `(-3)` 表示从倒数第三个开始,截取到最后。 - `(1, -1)` 表示从第一个截取到倒数第一个。 - `(5, 2)` 表示前面的大,后面的小,返回值为空。 ### 2、substring() 语法: ```javascript 新字符串 = str.substring(开始索引, 结束索引); //两个参数都是索引值。包左不包右。 ``` 解释:从字符串中截取指定的内容。和`slice()`类似。 `substring()`和`slice()`是类似的。但不同之处在于: - `substring()`不能接受负值作为参数。如果传递了一个**负值**,则默认使用 0。 - `substring()`还会自动调整参数的位置,如果第二个参数小于第一个,则自动交换。比如说, `substring(1, 0)`相当于截取的是第一个字符。 ### 3、substr() 语法: ```javascript 字符串 = str.substr(开始索引, 截取的长度); ``` 解释:从字符串中截取指定的内容。不会修改原字符串,而是将截取到的内容返回。 注意,这个方法的第二个参数**截取的长度**,不是结束索引。 参数举例: - `(2,4)` 从索引值为 2 的字符开始,截取 4 个字符。 - `(1)` 从指定位置开始,截取到最后。 - `(-3)` 从倒数第几个开始,截取到最后。 备注:ECMAscript 没有对 `substr()` 方法进行标准化,因此不建议使用它。 ## String.fromCharCode() `String.fromCharCode()`:根据字符的 Unicode 编码获取字符。 代码举例: ```javascript var result1 = String.fromCharCode(72); var result2 = String.fromCharCode(20013); console.log(result1); // 打印结果:H console.log(result2); // 打印结果:中 ``` ## concat() 语法: ```javascript 新字符串 = str1.concat(str2); //连接两个字符串 ``` 解释:字符串的连接。 这种方法基本不用,直接把两个字符串相加就好。 是的,你会发现,数组中也有`concat()`方法,用于数组的连接。这个方法在数组中用得挺多的。 代码举例: ```javascript var str1 = 'qiangu'; var str2 = 'yihao'; var result = str1.concat(str2); console.log(result); // 打印结果:qianguyihao ``` ## split():字符串转换为数组 【重要】 语法: ```javascript 新的数组 = str.split(分隔符); ``` 解释:通过指定的分隔符,将一个字符串拆分成一个**数组**。不会改变原字符串。 备注:`split()`这个方法在实际开发中用得非常多。一般来说,从接口拿到的 json 数据中,经常会收到类似于`"q, i, a, n"`这样的字符串,前端需要将这个字符串拆分成`['q', 'i', 'a', 'n']`数组,这个时候`split()`方法就派上用场了。 **代码举例 1**: ```javascript var str = 'qian, gu, yi, hao'; // 用逗号隔开的字符串 var array = str.split(','); // 将字符串 str 拆分成数组,通过逗号来拆分 console.log(array); // 打印结果是数组:["qian", " gu", " yi", " hao"] ``` **代码举例 2**: ```javascript //split()方法:字符串变数组 var str3 = '千古壹号|qianguyihao|许嵩'; console.log('结果1:' +str3.split()); // 无参数,表示:把整个字符串作为一个元素添加到数组中。 console.log(str3.split('')); // 参数为空字符串,则表示:分隔字符串中每一个字符,分别添加到数组中 console.log(str3.split('|')); // 参数为指定字符,表示:用 '|' 分隔字符串。此分隔符将不会出现在数组的任意一个元素中 console.log(str3.split('许')); // 同上 ``` 打印结果:(都是数组) ![](http://img.smyhvae.com/20200611_2050.png) ## replace() 语法: ```javascript 新的字符串 = str.replace(被替换的子串,新的子串); ``` 解释:将字符串中的指定内容,替换为新的内容并返回。不会修改原字符串。 注意:这个方法,默认只会替换第一个被匹配到的字符。如果要全局替换,需要使用正则。 代码举例: ```javascript //replace()方法:替换 var str2 = 'Today is fine day,today is fine day !'; console.log(str2); console.log(str2.replace('today', 'tomorrow')); //只能替换第一个today console.log(str2.replace(/today/gi, 'tomorrow')); //这里用到了正则,才能替换所有的today ``` ## repeat():重复字符串 语法: ```js newStr = str.repeat(重复的次数); ``` 解释:将字符串重复指定的次数。会返回新的值,不会修改原字符串。 举例1: ```js const name = 'qianguyihao'; console.log(name.repeat(2)); // 打印内容:qianguyihaoqianguyihao ``` 举例2:(模糊字符串的后四位) ```js const telephone = '13088889999'; const mix_telephone = telephone.slice(0, -4) + '*'.repeat(4); // 模糊电话号码的后四位 console.log(telephone); // 打印结果:13088889999 console.log(mix_telephone); // 打印结果:1308888**** ``` ## trim() `trim()`:去除字符串前后的空白。 代码举例: ```javascript //去除字符串前后的空格,trim(); let str = ' a b c '; console.log(str); console.log(str.length); console.log(str.trim()); console.log(str.trim().length); ``` 打印结果: ![](http://img.smyhvae.com/20200607_2132.png) ## 大小写转换 举例: ```javascript var str = 'abcdEFG'; //转换成小写 console.log(str.toLowerCase()); //转换成大写 console.log(str.toUpperCase()); ``` ## html 方法 - anchor() 创建 a 链接 - big() - sub() - sup() - link() - bold() 注意,str.link() 返回值是字符串。 举例: ```javascript var str = '你好'; console.log(str.anchor()); console.log(str.big()); console.log(str.sub()); console.log(str.sup()); console.log(str.link('http://www.baidu.com')); console.log(str.bold()); ``` ![](http://img.smyhvae.com/20180202_1536.png) ## 字符串练习 **练习 1**:"smyhvaevaesmyh"查找字符串中所有 m 出现的位置。 代码实现: ```javascript var str2 = 'smyhvaevaesmyh'; for (var i = 0; i < str2.length; i++) { //如果指定位置的符号=== "o" //str2[i] if (str2.charAt(i) === 'm') { console.log(i); } } ``` **练习 2**:判断一个字符串中出现次数最多的字符,统计这个次数 ```html ``` 打印结果: ![](http://img.smyhvae.com/20180202_1540.png) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/16-内置对象:Number和Math.md ================================================ --- title: 16-内置对象:Number和Math --- ## 内置对象 Number 的常见方法 ### Number.isInteger() 判断是否为整数 语法: ``` 布尔值 = Number.isInteger(数字); ``` ### toFixed() 小数点后面保留多少位 语法: ```js 字符串 = myNum.toFixed(num); ``` 解释:将数字 myNum 的小数点后面保留 num 位小数(四舍五入),并返回。不会改变原数字。注意,**返回结果是字符串**。 参数 num:指定了小数点后面的位数。 举例: ```js let num = 3.456; let num2 = num.toFixed(2); console.log(num); // 打印结果:3.456 console.log(num2); // 打印结果:3.46 console.log(typeof num); // number console.log(typeof num2); // string ``` 上方代码中,`num2`的结果是3.46,但是请注意,`num`的类型Number型,而`num2`的类型却是String型。 另外需要注意的是,数字常量不能直接调 toFixed 方法。比如 `1.toFixed(2)`在 JS 中会引发语法错误。因为点号(.)被解释为数字字面量的一部分,而不是方法调用的分隔符。为了正确调用 toFixed 方法,可以使用括号或额外的点号。 toFixed()在这一点上,跟前面讲的 toString() 是类似的,推荐的做法是先把数字放到变量中存起来,然后通过变量调用 toFixed()。 ## 内置对象 Math 的常见方法 Math 和其他的对象不同,它不是一个构造函数,不需要创建对象。所以我们不需要 通过 new 来调用,而是直接使用里面的属性和方法即可。 Math属于一个工具类,里面封装了数学运算相关的属性和方法。如下: | 方法 | 描述 | 备注 | |:-------------|:-------------|:-------------| | Math.PI | 圆周率 | Math对象的属性 | | Math.abs() | **返回绝对值** | | | Math.random() | 生成0-1之间的**随机浮点数** | 取值范围是 [0,1) | | Math.floor() | **向下取整**(往小取值) | | | Math.ceil() | **向上取整**(往大取值) | | | Math.round() | 四舍五入取整(正数四舍五入,负数五舍六入) | | | Math.max(x, y, z) | 返回多个数中的最大值 | | | Math.min(x, y, z) | 返回多个数中的最小值 | | | Math.pow(x,y) | 乘方:返回 x 的 y 次幂 | | | Math.sqrt() | 开方:对一个数进行开方运算 | | **举例**: ```javascript var num = -0.6; console.log(Math.abs(num)); //取绝对值 console.log(Math.floor(num)); //向下取整,向小取 console.log(Math.ceil(num)); //向上取整,向大取 console.log(Math.round(num)); //四舍五入取整(正数四舍五入,负数五舍六入) console.log(Math.random()); //生成0-1之间的随机数 ``` 运行结果: ``` 0.6 -1 -0 -1 0.6453756205275165 ``` ## Math.abs():获绝对值 方法定义:返回绝对值。 注意: - 参数中可以接收字符串类型的数字,此时会将字符串做隐式类型转换,然后再调用 Math.abs() 方法。 代码举例: ```javascript console.log(Math.abs(2)); // 2 console.log(Math.abs(-2)); // 2 // 先做隐式类型转换,将 '-2'转换为数字类型 -2,然后再调用 Math.abs() console.log(Math.abs('-2')); console.log(Math.abs('hello')); // NaN ``` ## Math.random() 方法:生成随机数 方法定义:生成 [0, 1) 之间的**随机浮点数**。 我们来看几个例子。 ### 生成 [0, x) 之间的随机数 ```javascript Math.round(Math.random()*x) ``` ### 生成 [x, y) 之间的随机数 ```javascript Math.round(Math.random()*(y-x)+x) ``` ### 【重要】生成 [x, y]之间的随机整数 也就是说:生成两个整数之间的随机整数,**并且要包含这两个整数**。 这个功能很常用,我们可以将其封装成一个方法,代码实现如下: ```javascript /* * 生成两个整数之间的随机整数,并且要包含这两个整数 */ function getRandom(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } console.log(getRandom(1, 10)); ``` ### 举例:随机点名 根据上面的例子,我们还可以再延伸一下,来看看随机点名的例子。 ```javascript /* * 生成两个整数之间的随机整数,并且要包含这两个整数 */ function getRandom(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } const arr = ['许嵩', '邓紫棋', '毛不易', '解忧邵帅']; const index = getRandom(0, arr.length - 1); // 生成随机的index console.log(arr[index]); // 随机点名 ``` ## pow():乘方 如果想计算 `a 的 b 次方`,可以使用如下函数: ``` Math.pow(a, b); ``` Math的中文是“数学”,pow是“幂”。 **举例1:** ![](http://img.smyhvae.com/20180117_1730.png) 代码实现: ``` var a = Math.pow(3, Math.pow(2, 2)); console.log(a); ``` **举例2:** ![](http://img.smyhvae.com/20180117_1740.png) 代码实现: ``` var a = Math.pow(Math.pow(3, 2), 4); console.log(a); ``` ## sqrt():开方 如果想计算数值a的开二次方,可以使用如下函数: ``` Math.sqrt(a); ``` sqrt即“square 开方”。比如: ``` var a = Math.sqrt(36); ``` ## url 编码和解码 URI (Uniform ResourceIdentifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的URI中不能包含某些字符,例如空格。而这URI编码方法就可以对URI进行编码,它们用特殊的UTF-8编码替换所有无效的字符,从而让浏览器能够接受和理解。 ```javascript encodeURIComponent(); //把字符串作为 URI 组件进行编码 decodeURIComponent(); //把字符串作为 URI 组件进行解码 ``` 举例: ```javascript var url = "http://www.cnblogs.com/smyhvae/"; var str = encodeURIComponent(url); console.log(str); //打印url的编码 console.log(decodeURIComponent(str)); //对url进行编码后,再解码,还原为url ``` 打印结果: ![](http://img.smyhvae.com/20180202_1432.png) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/17-内置对象:Date.md ================================================ --- title: 17-内置对象:Date --- ## 内置对象:Date > Date 对象在实际开发中,使用得很频繁,且容易在细节地方出错,需要引起重视。 内置对象 Date 用来处理日期和时间。 **需要注意的是**:与 Math 对象不同,Date 对象是一个**构造函数** ,需要**先实例化**后才能使用。 ## 创建Date对象 创建Date对象有两种写法: - 写法一:如果Date()不写参数,就返回当前时间对象 - 写法二:如果Date()里面写参数,就返回括号里输入的时间对象 针对这两种写法,我们来具体讲一讲。 ### 写法一:不传递参数时,则获取系统的当前时间对象 代码举例: ```javascript var date1 = new Date(); console.log(date1); console.log(typeof date1); ``` 代码解释:不传递参数时,表示的是获取系统的当前时间对象。也可以理解成是:获取当前代码执行的时间。 打印结果: ``` Mon Feb 17 2020 21:57:22 GMT+0800 (中国标准时间) object ``` ### 写法二:传递参数 传递参数时,表示获取指定时间的时间对象。参数中既可以传递字符串,也可以传递数字,也可以传递时间戳。 通过传参的这种写法,我们可以把时间字符串/时间数字/时间戳,按照指定的格式,转换为时间对象。 举例1:(参数是字符串) ```js const date11 = new Date('2020/02/17 21:00:00'); console.log(date11); // Mon Feb 17 2020 21:00:00 GMT+0800 (中国标准时间) const date12 = new Date('2020/04/19'); // 返回的就是四月 console.log(date12); // Sun Apr 19 2020 00:00:00 GMT+0800 (中国标准时间) const date13 = new Date('2020-05-20'); console.log(date13); // Wed May 20 2020 08:00:00 GMT+0800 (中国标准时间) const date14 = new Date('Wed Jan 27 2017 12:00:00 GMT+0800 (中国标准时间)'); console.log(date14); // Fri Jan 27 2017 12:00:00 GMT+0800 (中国标准时间) ``` 举例2:(参数是多个数字) ```js const date21 = new Date(2020, 2, 18); // 注意,第二个参数返回的是三月,不是二月 console.log(date21); // Wed Mar 18 2020 00:00:00 GMT+0800 (中国标准时间) const date22 = new Date(2020, 3, 18, 22, 59, 58); console.log(date22); // Sat Apr 18 2020 22:59:58 GMT+0800 (中国标准时间) const params = [2020, 06, 12, 16, 20, 59]; const date23 = new Date(...params); console.log(date23); // Sun Jul 12 2020 16:20:59 GMT+0800 (中国标准时间) ``` 举例3:(参数是时间戳) ```js const date31 = new Date(1591950413388); console.log(date31); // Fri Jun 12 2020 16:26:53 GMT+0800 (中国标准时间) // 先把时间对象转换成时间戳,然后把时间戳转换成时间对象 const timestamp = new Date().getTime(); const date32 = new Date(timestamp); console.log(date32); // Fri Jun 12 2020 16:28:21 GMT+0800 (中国标准时间) ``` ## 日期的格式化 上一段内容里,我们获取到了 Date **对象**,但这个对象,打印出来的结果并不是特别直观。 如果我们需要获取日期的**指定部分**,就需要用到 Date对象自带的方法。 获取了日期指定的部分之后,我们就可以让日期按照指定的格式,进行展示(即日期的格式化)。比如说,我期望能以 `2020-02-02 19:30:59` 这种格式进行展示。 在这之前,我们先来看看 Date 对象有哪些方法。 ### Date对象的方法 Date对象 有如下方法,可以获取日期和时间的**指定部分**: | 方法名 | 含义 | 备注 | | ------------- | ----------------- | --------- | | getFullYear() | 获取年份 | | | getMonth() | **获取月: 0-11** | 0代表一月 | | getDate() | **获取日:1-31** | 获取的是几号 | | getDay() | **获取星期:0-6** | 0代表周日,1代表周一 | | getHours() | 获取小时:0-23 | | | getMinutes() | 获取分钟:0-59 | | | getSeconds() | 获取秒:0-59 | | | getMilliseconds() | 获取毫秒 | 1s = 1000ms | **代码举例**: ```javascript // 我在执行这行代码时,当前时间为 2019年2月4日,周一,13:23:52 var myDate = new Date(); console.log(myDate); // 打印结果:Mon Feb 04 2019 13:23:52 GMT+0800 (中国标准时间) console.log(myDate.getFullYear()); // 打印结果:2019 console.log(myDate.getMonth() + 1); // 打印结果:2 console.log(myDate.getDate()); // 打印结果:4 var dayArr = ['星期日', '星期一', '星期二', '星期三', '星期四','星期五', '星期六']; console.log(myDate.getDay()); // 打印结果:1 console.log(dayArr[myDate.getDay()]); // 打印结果:星期一 console.log(myDate.getHours()); // 打印结果:13 console.log(myDate.getMinutes()); // 打印结果:23 console.log(myDate.getSeconds()); // 打印结果:52 console.log(myDate.getMilliseconds()); // 打印结果:393 console.log(myDate.getTime()); // 获取时间戳。打印结果:1549257832393 ``` 获取了日期和时间的指定部分之后,我们把它们用字符串拼接起来,就可以按照自己想要的格式,来展示日期。 ### 举例:年月日的格式化 代码举例: ```js console.log(formatDate()); /* 方法:日期格式化。 格式要求:今年是:2020年02月02日 08:57:09 星期日 */ function formatDate() { var date = new Date(); var year = date.getFullYear(); // 年 var month = date.getMonth() + 1; // 月 var day = date.getDate(); // 日 var week = date.getDay(); // 星期几 var weekArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; var hour = date.getHours(); // 时 hour = hour < 10 ? '0' + hour : hour; // 如果只有一位,则前面补零 var minute = date.getMinutes(); // 分 minute = minute < 10 ? '0' + minute : minute; // 如果只有一位,则前面补零 var second = date.getSeconds(); // 秒 second = second < 10 ? '0' + second : second; // 如果只有一位,则前面补零 var result = '今天是:' + year + '年' + month + '月' + day + '日 ' + hour + ':' + minute + ':' + second + ' ' + weekArr[week]; return result; } ``` ## 获取时间戳 ### 时间戳的定义和作用 **时间戳**:指的是从格林威治标准时间的`1970年1月1日,0时0分0秒`到当前日期所花费的**毫秒数**(1秒 = 1000毫秒)。 计算机底层在保存时间时,使用的都是时间戳。时间戳的存在,就是为了**统一**时间的单位。 我们经常会利用时间戳来计算时间,因为它更精确。而且,在实战开发中,接口返回给前端的日期数据,都是以时间戳的形式。 我们再来看下面这样的代码: ```javascript var myDate = new Date("1970/01/01 0:0:0"); console.log(myDate.getTime()); // 获取时间戳 ``` 打印结果(可能会让你感到惊讶) ```javascript -28800000 ``` 为啥打印结果是`-28800000`,而不是`0`呢?这是因为,我们的当前代码,是在中文环境下运行的,与英文时间会存在**8个小时的时差**(中文时间比英文时间早了八个小时)。如果代码是在英文环境下运行,打印结果就是`0`。 ### getTime():获取时间戳 `getTime()` 获取日期对象的**时间戳**(单位:毫秒)。这个方法在实战开发中,用得比较多。但还有比它更常用的写法,我们往下看。 ### 获取 Date 对象的时间戳 代码演示: ```js // 方式一:获取 Date 对象的时间戳(最常用的写法) const timestamp1 = +new Date(); console.log(timestamp1); // 打印结果举例:1589448165370 // 方式二:获取 Date 对象的时间戳(较常用的写法) const timestamp2 = new Date().getTime(); console.log(timestamp2); // 打印结果举例:1589448165370 // 方式三:获取 Date 对象的时间戳 const timestamp3 = new Date().valueOf(); console.log(timestamp3); // 打印结果举例:1589448165370 // 方式4:获取 Date 对象的时间戳 const timestamp4 = new Date() * 1; console.log(timestamp4); // 打印结果举例:1589448165370 // 方式5:获取 Date 对象的时间戳 const timestamp5 = Number(new Date()); console.log(timestamp5); // 打印结果举例:1589448165370 ``` 上面这五种写法都可以获取任意 Date 对象的时间戳,最常见的写法是**方式一**,其次是方式二。 根据前面所讲的关于「时间戳」的概念,上方代码获取到的时间戳指的是:从 `1970年1月1日,0时0分0秒` 到现在所花费的总毫秒数。 ### 获取当前时间的时间戳 如果我们要获取**当前时间**的时间戳,除了上面的几种方式之外,还有另一种方式。代码如下: ```js // 方式六:获取当前时间的时间戳(很常用的写法) console.log(Date.now()); // 打印结果举例:1589448165370 ``` 上面这种方式六,用得也很多。只不过,`Date.now()`是H5标准中新增的特性,如果你的项目需要兼容低版本的IE浏览器,就不要用了。这年头,谁还用IE呢? ### 利用时间戳检测代码的执行时间 我们可以在业务代码的前面定义 `时间戳1`,在业务代码的后面定义 `时间戳2`。把这两个时间戳相减,就能得出业务代码的执行时间。 ### format() 将时间对象转换为指定格式。 参考链接: ## 练习 ### 举例1:模拟日历 要求每天打开这个页面,都能定时显示当前的日期。 代码实现: ```html
    ``` 实现效果: ![](http://img.smyhvae.com/20180202_1110.png) ### 举例2:发布会倒计时 实现思路: - 设置一个定时器,每间隔1毫秒就自动刷新一次div的内容。 - 核心算法:输入的时间戳减去当前的时间戳,就是剩余时间(即倒计时),然后转换成时分秒。 代码实现: ```html
    ``` 实现效果: ![](http://img.smyhvae.com/20180202_1130.gif) ## Moment.js Moment.js 是一个轻量级的JavaScript时间库,我们可以利用它很方便地进行时间操作,提升开发效率。 - 中文官网: 使用举例: ```html Document ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/18-数组简介.md ================================================ --- title: 18-数组简介 --- > 之前学习的数据类型,只能存储一个值(字符串也为一个值)。如果我们想存储多个值,就可以使用数组。 ## 数组简介 数组(Array)是属于**内置对象**,数组和普通对象的功能类似,都可以用来存储一些值。不同的是: - 普通对象是使用字符串作为属性名,而数组是使用数字作为**索引**来操作元素。索引:从 0 开始的整数就是索引。 数组的存储性能比普通对象要好。在实际开发中我们经常使用数组存储一些数据(尤其是**列表数据**),使用频率非常高。 ![](http://img.smyhvae.com/20200612_1707.png) 比如说,上面这个页面的列表数据,它的数据结构就是一个数组。 数组中的元素可以是任意的数据类型,可以是对象,可以是函数,也可以是数组。数组的元素中,如果存放的是数组,我们就称这种数组为二维数组。 接下来,我们讲一讲数组的基本操作。 ## 创建数组对象 ### 方式一:使用字面量创建数组 举例: ```javascript let arr1 = []; // 创建一个空的数组 let arr2 = [1, 2, 3]; // 创建带初始值的数组 ``` 方式一最简单,也用得最多。 ### 方式二:使用构造函数创建数组 语法: ```js let arr = new Array(参数); let arr = Array(参数); ``` 如果**参数为空**,表示创建一个空数组;如果参数是**一个数值**,表示数组的长度;如果**有多个参数**,表示数组中的元素内容。 举个例子: ```javascript // 方式一 let arr1 = [11, 12, 13]; // 方式二 let arr2 = new Array(); // 参数为空:创建空数组 let arr3 = new Array(4); // 参数为 size let arr4 = new Array(15, 16, 17); // 参数为多个数值:创建一个带数据的数组 console.log(typeof arr1); // 打印结果:object console.log('arr1 = ' + JSON.stringify(arr1)); console.log('arr2 = ' + JSON.stringify(arr2)); console.log('arr3 = ' + JSON.stringify(arr3)); console.log('arr4 = ' + JSON.stringify(arr4)); ``` 打印结果: ```javascript object; arr1 = [11, 12, 13]; arr2 = []; arr3 = [null, null, null, null]; arr4 = [15, 16, 17]; ``` 从上方打印结果的第一行可以看出,数组的类型是属于**对象**。 ### 数组中的元素的类型 数组中可以存放**任意类型**的数据,例如字符串、数字、布尔值、对象等。 比如: ```javascript const arr = ['qianguyihao', 28, true, { name: 'qianguyihao' }]; ``` 我们甚至可以在数组里存放数组。比如: ```js const arr2 = [ [11, 12, 13], [21, 22, 23], ]; ``` ## 数组的基本操作 ### 数组的索引 **索引** (下标) :用来访问数组元素的序号,代表的是数组中的元素在数组中的位置(下标从 0 开始算起)。 数组可以通过索引来访问、修改对应的数组元素。我们继续看看。 ### 向数组中添加元素 语法: ```javascript 数组[索引] = 值; ``` 代码举例: ```javascript const arr = []; // 向数组中添加元素 arr[0] = 10; arr[1] = 20; arr[2] = 30; arr[3] = 40; arr[5] = 50; console.log(JSON.stringify(arr)); ``` 打印结果: ``` [10,20,30,40,null,50] ``` ### 获取数组中的元素 语法: ```javascript 数组[索引]; ``` 如果读取不存在的索引(比如元素没那么多),系统不会报错,而是返回 undefined。 代码举例: ```javascript const arr = [21, 22, 23]; console.log(arr[0]); // 打印结果:21 console.log(arr[5]); // 打印结果:undefined ``` ### 获取数组的长度 可以使用`length`属性来获取数组的长度(即“元素的个数”)。 数组的长度是元素个数,不要跟索引号混淆。 语法: ```javascript 数组的长度 = 数组名.length; ``` 代码举例: ```javascript const arr = [21, 22, 23]; console.log(arr.length); // 打印结果:3 ``` 补充: 对于连续的数组,使用 length 可以获取到数组的长度(元素的个数);对于非连续的数组(即“稀疏数组”,本文稍后会讲),length 的值会大于元素的个数。因此,尽量不要创建非连续的数组。 ### 修改数组的长度 可以通过修改length属性修改数组的长度。 - 如果修改的 length 大于原长度,则多出部分会空出来,置为 null。 - 如果修改的 length 小于原长度,则多出的元素会被删除,数组将从后面删除元素。 - (特例:伪数组 arguments 的长度可以修改,但是不能修改里面的元素,以后单独讲。) 代码举例: ```javascript const arr1 = [11, 12, 13]; const arr2 = [21, 22, 23]; // 修改数组 arr1 的 length arr1.length = 1; console.log(JSON.stringify(arr1)); // 修改数组 arr2 的 length arr2.length = 5; console.log(JSON.stringify(arr2)); ``` 打印结果: ```javascript [11] [21, 22, 23, null, null] ``` ### 遍历数组 **遍历**: 就是把数组中的每个元素从头到尾都访问一次。 最简单的做法是通过 for 循环,遍历数组中的每一项。举例: ```javascript const arr = [10, 20, 30, 40, 50]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); // 打印出数组中的每一项 } ``` 下一篇文章,会学习数组的各种方法,到时候,会有更多的做法去遍历数组。 ## JS语言中,数组的注意点 > 和其他编程语言相比,JS语言中的数组比较灵活,有许多与众不同的地方。 1、如果访问数组中不存在的索引时,不会报错,会返回undefined。 2、当数组的存储空间不够时,数组会自动扩容。其它编程语言中数组的大小是固定的,不会自动扩容。 3、数组可以存储不同类型数据,其它编程语言中数组只能存储相同类型数据。 4、数组分配的存储空间不一定是连续的。其它语言数组分配的存储空间是连续的。 JS中的数组采用"哈希映射"的方式分配存储空间,我们可以通过索引找到对应空间。各大浏览器也对数组分配的存储空间进行了优化:如果存储的都是相同类型的数据,则会尽量分配连续的存储空间;如果存储的不是相同的数据类型,则不会分配连续的存储空间。 ## 数组的解构赋值 解构赋值是ES6中新增的一种赋值方式。 ES5中,如果想把数组中的元素赋值给其他变量,是这样做的: ```js const arr = [1, 2, [3,4]]; let a = arr[0]; // 1 let b = arr[1]; // 2 let c = arr[2]; // [3, 4] ``` 上面这种写法比较啰嗦。通过ES6中的结构复制,我们可以像下面这样做。 1、数组解构赋值,代码举例: ```js let [a, b, c] = [1, 2, [3, 4]]; console.log(a); // 1 console.log(b); // 2 console.log(c); // [3, 4] ``` 注意点: (1)等号左边的个数和格式,必须和右边的一模一样,才能完全解构。 (2)当然,左边的个数和右边的个数,可以不一样。 2、默认值。在赋值之前,我们可以给左边的变量指定**默认值**: ```js let [a, b = 3, c = 4] = [1, 2]; console.log(a); // 1 console.log(b); // 2。默认值被覆盖。 console.log(c); // 4。继续保持默认值。 ``` 3、我们可以使用ES6中新增的**扩展运算符**打包剩余的数据。如果使用了扩展运算符, 那么扩展运算符只能写在最后。代码举例: ```js let [a, ...b] = [1, 2, 3]; console.log(a); // 1 console.log(b); // [2, 3] ``` ## 稀疏数组与密集数组 > 这个知识点,简单了解即可。 - 稀疏数组:索引不连续、数组长度大于元素个数的数组,可以简单理解为有 `empty`(有空隙)的数组。 - 密集数组:索引连续、数组长度等于元素个数的数组。 参考链接: - [JavaScript 之稀疏数组与密集数组](https://juejin.cn/post/6975531514444562462) - [JS 稀疏数组](https://github.com/JunreyCen/blog/issues/10) - [JS 中的稀疏数组和密集数组](https://juejin.cn/post/6844904050152964109) - [译]JavaScript中的稀疏数组与密集数组:https://www.cnblogs.com/ziyunfei/archive/2012/09/16/2687165.html - [JavaScript || 数组](https://segmentfault.com/a/1190000008533942) ## 案例 ### 例 1:翻转数组 代码实现: ```javascript const arr = [10, 20, 30, 40, 50]; // 原始数组 const newArr = []; // 翻转后的数组 for (let i = 0; i < arr.length; i++) { newArr[i] = arr[arr.length - i - 1]; } console.log(JSON.stringify(newArr)); ``` 打印结果: ``` [50,40,30,20,10] ``` ### 例 2:冒泡排序 代码实现: ```javascript const arr = [20, 10, 50, 30, 40]; for (let i = 0; i < arr.length - 1; i++) { for (let j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) { let temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } console.log(JSON.stringify(arr)); ``` 打印结果: ``` [10,20,30,40,50] ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/19-数组的常见方法.md ================================================ --- title: 19-数组的常见方法 --- ## 数组的方法清单 ### 数组的类型相关 | 方法 | 描述 | 备注 | | :------------------------------- | :----------------------------------------------- | :--------------- | | Array.isArray() | 判断是否为数组 | | | toString() | 将数组转换为字符串 | 不会改变原数组 | | join() | 将数组转换为字符串,返回结果为**转换后的字符串** | 不会改变原数组 | | 字符串的方法:split() | 将字符串按照指定的分隔符,组装为数组 | 不会改变原字符串 | | | | | | Array.from(arrayLike) | 将**伪数组**转化为**真数组** | | | Array.of(value1, value2, value3) | 创建数组:将**一系列值**转换成数组 | | 注意: (1)获取数组的长度是用`length`属性,不是方法。关于 `length`属性,详见上一篇文章。 (2)`split()`是字符串的方法,不是数组的方法。 ### 数组元素的添加和删除 | 方法 | 描述 | 备注 | | :-------- | :------------------------------------------------------------------------- | :------------- | | push() | 向数组的**最后面**插入一个或多个元素,返回结果为新数组的**长度** | 会改变原数组 | | pop() | 删除数组中的**最后一个**元素,返回结果为**被删除的元素** | 会改变原数组 | | unshift() | 在数组**最前面**插入一个或多个元素,返回结果为新数组的**长度** | 会改变原数组 | | shift() | 删除数组中的**第一个**元素,返回结果为**被删除的元素** | 会改变原数组 | | | | | | splice() | 从数组中**删除**指定的一个或多个元素,返回结果为**被删除元素组成的新数组** | 会改变原数组 | | slice() | 从数组中**提取**指定的一个或多个元素,返回结果为**新的数组** | 不会改变原数组 | | | | | | concat() | 合并数组:连接两个或多个数组,返回结果为**新的数组** | 不会改变原数组 | | fill() | 填充数组:用固定的值填充数组,返回结果为**新的数组** | 会改变原数组 | ### 数组排序 | 方法 | 描述 | 备注 | | :-------- | :------------------------------------------------------ | :----------- | | reverse() | 反转数组,返回结果为**反转后的数组** | 会改变原数组 | | sort() | 对数组的元素,默认按照**Unicode 编码**,从小到大进行排序 | 会改变原数组 | ### 查找数组的元素 | 方法 | 描述 | 备注 | | :-------------------- | :----------------------------------------------------------------------------- | :------------------------------------------------------- | | indexOf(value) | 从前往后索引,检索一个数组中是否含有指定的元素 | | | lastIndexOf(value) | 从后往前索引,检索一个数组中是否含有指定的元素 | | | includes(item) | 数组中是否包含指定的内容 | | | find(function()) | 找出**第一个**满足「指定条件返回 true」的元素 | | | findIndex(function()) | 找出**第一个**满足「指定条件返回 true」的元素的 index | | | every() | 确保数组中的每个元素都满足「指定条件返回 true」,则停止遍历,此方法才返回 true | 全真才为真。要求每一项都返回 true,最终的结果才返回 true | | some() | 数组中只要有一个元素满足「指定条件返回 true」,则停止遍历,此方法就返回 true | 一真即真。只要有一项返回 true,最终的结果就返回 true | ### 遍历数组 | 方法 | 描述 | 备注 | | :-------- | :----------------------------------------------------------- | :----------------------------------------------------------- | | for 循环 | 最传统的方式遍历数组,这个大家都懂 | | | forEach() | 遍历数组,但需要兼容 IE8 以上 | 不会改变原数组。forEach() 没有返回值。也就是说,它的返回值是 undefined | | for of | 遍历数组(ES6语法) | 不会改变原数组。另外,不要使用 for in 遍历数组 | | map() | 对原数组中的每一项进行加工,将组成新的数组 | 不会改变原数组 | | filter() | 过滤数组:返回结果是 true 的项,将组成新的数组,返回结果为**新的数组** | 不会改变原数组 | | reduce | 接收一个函数作为累加器,返回值是回调函数累计处理的结果 | 比较复杂 | ## isArray():判断是否为数组 语法: ```javascript 布尔值 = Array.isArray(被检测的数组); ``` 以前,我们会通过 `A instanceof B`来判断 A 是否属于 B 类型。但是在数组里,这种 instanceof 方法已经用的不多了,因为有 isArray()方法。 ## 数组转换为字符串 数组转为字符串,有三种方式。 ### 方式1、toString() ```javascript // 语法 字符串 = 数组.toString(); // 举例 const result = [1, 3, 5].toString(); // 转换结果 result 为字符串 '1, 3, 5' ``` 解释:把数组转换成字符串,每一项用英文逗号`,`分割。 备注:大多数的数据类型都可以使用`.toString()`方法,将其转换为字符串。 ### 方式 2 ```js // 语法 字符串 = String(数组); // 举例 const result = String([1, 3, 5]); // 转换结果 result 为字符串 '1, 3, 5' ``` ### 方式 3:join()方法 ```js 字符串 = 数组.join(','); // 将数组转为字符串,每一项用 英文逗号 分隔 ``` 关于 join()方法的详细介绍,详见下一段。 ## join() `join()`:将数组转换为字符串,返回结果为**转换后的字符串**(不会改变原来的数组)。 补充:`join()`方法可以指定一个**字符串**作为参数,这个参数是元素之间的**连接符**;如果不指定连接符,则默认使用英文逗号`,` 作为连接符,此时和 `toString()的`效果是一致的。 语法: ```javascript 新的字符串 = 原数组.join(参数); // 参数选填 ``` 代码举例: ```javascript const arr = ['a', 'b', 'c']; const result1 = arr.join(); // 这里没有指定连接符,所以默认使用 , 作为连接符 const result2 = arr.join('-'); // 使用指定的字符串作为连接符 console.log(typeof arr); // 打印结果:object console.log(typeof result1); // 打印结果:string console.log('arr =' + JSON.stringify(arr)); console.log('result1:' + result1); console.log('result2:' + result2); ``` 上方代码中,最后三行的打印结果是: ```bash arr =["a","b","c"] result1:a,b,c result2:a-b-c ``` ## split() > 注意,`split()`是字符串的方法,不是数组的方法。 语法: ```javascript 新的数组 = str.split(分隔符); ``` 解释:通过指定的分隔符,将一个字符串拆分成一个**数组**。不会改变原字符串。 备注:`split()`这个方法在实际开发中用得非常多。一般来说,从接口拿到的 json 数据中,经常会收到类似于`"q, i, a, n"`这样的字符串,前端需要将这个字符串拆分成`['q', 'i', 'a', 'n']`数组,这个时候`split()`方法就派上用场了。 ## Array.from():将伪数组转换为真数组 **语法**: ```javascript array = Array.from(arrayLike); ``` **作用**:将**伪数组**或可遍历对象转换为**真数组**。 代码举例: ```js const name = 'qianguyihao'; console.log(Array.from(name)); // 打印结果是数组:["q","i","a","n","g","u","y","i","h","a","o"] ``` ### 伪数组与真数组的区别 **伪数组**:包含 length 属性的对象或可迭代的对象。 另外,伪数组的原型链中没有 Array.prototype,而真数组的原型链中有 Array.prototype。因此伪数组没有数组的一般方法,比如 pop()、join() 等方法。 ### 伪数组举例 ```html ``` 上面的布局中,有三个 button 标签,我们通过`getElementsByTagName`获取到的`btnArray`实际上是**伪数组**,并不是真实的数组: ![](http://img.smyhvae.com/20180402_1116.png) 既然`btnArray`是伪数组,它就不能使用数组的一般方法,否则会报错: ![](http://img.smyhvae.com/20180402_1121.png) 解决办法:采用`Array.from`方法将`btnArray`这个伪数组转换为真数组即可: ```javascript Array.from(btnArray); ``` 然后就可以使用数组的一般方法了: ![](http://img.smyhvae.com/20180402_1125.png) ## Array.of():创建数组 **语法**: ```javascript Array.of(value1, value2, value3); ``` **作用**:根据参数里的内容,创建数组。 **举例**: ```javascript const arr = Array.of(1, 'abc', true); console.log(arr); // 打印结果是数组:[1, "abc", true] ``` 补充:`new Array()`和 `Array.of()`的区别在于:当参数只有一个时,前者表示数组的长度,后者表示数组中的内容。 ## 数组元素的添加和删除 ### push() `push()`:向数组的**最后面**插入一个或多个元素,返回结果为新数组的**长度**。会改变原数组,因为原数组变成了新数组。 语法: ```javascript 新数组的长度 = 数组.push(元素); 新数组的长度 = 数组.push(元素1,元素2 ...); ``` 代码举例: ```javascript var arr = ['王一', '王二', '王三']; var result1 = arr.push('王四'); // 末尾插入一个元素 var result2 = arr.push('王五', '王六'); // 末尾插入多个元素 console.log(JSON.stringify(arr)); // 打印结果:["王一","王二","王三","王四","王五","王六"] console.log(result1); // 打印结果:4 console.log(result2); // 打印结果:6 ``` ### pop() `pop()`:删除数组中的**最后一个**元素,返回结果为**被删除的元素**。 语法: ```javascript 被删除的元素 = 数组.pop(); ``` 代码举例: ```javascript var arr = ['王一', '王二', '王三']; var result1 = arr.pop(); console.log(JSON.stringify(arr)); // 打印结果:["王一","王二"] console.log(result1); // 打印结果:王三 ``` ### unshift() `unshift()`:在数组**最前面**插入一个或多个元素,返回结果为新数组的**长度**。会改变原数组,将原数组变成了新数组。插入元素后,其他元素的索引会依次调整。 语法: ```javascript 新数组的长度 = 数组.unshift(元素); 新数组的长度 = 数组.unshift(元素1,元素2...); ``` 代码举例: ```javascript var arr = ['王一', '王二', '王三']; var result1 = arr.unshift('王四'); // 最前面插入一个元素 var result2 = arr.unshift('王五', '王六'); // 最前面插入多个元素 console.log(JSON.stringify(arr)); // 打印结果:["王五","王六","王四","王一","王二","王三"] console.log(result1); // 打印结果:4 console.log(result2); // 打印结果:6 ``` ### shift() `shift()`:删除数组中的**第一个**元素,返回结果为**被删除的元素**。 语法: ```javascript 被删除的元素 = 数组.shift(); ``` 代码举例: ```javascript var arr = ['王一', '王二', '王三']; var result1 = arr.shift(); console.log(JSON.stringify(arr)); // 打印结果:["王二","王三"] console.log(result1); // 打印结果:王一 ``` ### splice() `splice()`:从数组中**删除**指定的一个或多个元素,返回结果为**被删除元素组成的新数组**(会改变原来的数组)。 备注:该方法会改变原数组,会将指定元素从原数组中删除;被删除的元素会封装到一个新的数组中返回。 语法: ```javascript 新数组 = 原数组.splice(起始索引index); 新数组 = 原数组.splice(起始索引index, 需要删除的个数); 新数组 = 原数组.splice(起始索引index, 需要删除的个数, 新的元素1, 新的元素2...); ``` 上方语法中,第三个及之后的参数,表示:删除元素之后,向原数组中添加新的元素,这些元素将会自动插入到起始位置索引的前面。也可以理解成:删除了哪些元素,就在那些元素的所在位置补充新的内容。 `slice()`方法和`splice()`方法很容易搞混,请一定要注意区分。 举例 1: ```javascript var arr1 = ['a', 'b', 'c', 'd', 'e', 'f']; var result1 = arr1.splice(1); //从第index为1的位置开始,删除元素 console.log('arr1:' + JSON.stringify(arr1)); console.log('result1:' + JSON.stringify(result1)); ``` 打印结果: ``` arr1:["a"] result1:["b","c","d","e","f"] ``` 举例 2: ```javascript var arr2 = ['a', 'b', 'c', 'd', 'e', 'f']; var result2 = arr2.splice(-2); //删除最后两个元素 console.log('arr2:' + JSON.stringify(arr2)); console.log('result2:' + JSON.stringify(result2)); ``` 打印结果: ``` arr2:["a","b","c","d"] result2:["e","f"] ``` 举例 3: ```javascript var arr3 = ['a', 'b', 'c', 'd', 'e', 'f']; var result3 = arr3.splice(1, 3); //从第index为1的位置开始删除元素,一共删除三个元素 console.log('arr3:' + JSON.stringify(arr3)); console.log('result3:' + JSON.stringify(result3)); ``` 打印结果: ``` arr3:["a","e","f"] result3:["b","c","d"] ``` 举例4:(删除指定元素,用得很多) ```js const arr4 = ['a', 'b', 'c', 'd']; arr4.splice(arr4.indexOf('c'), 1); // 删除数组中的'c'这个元素 console.log('arr4:' + JSON.stringify(arr4)); ``` 举例 5:(**第三个参数**的用法) ```javascript var arr5 = ['a', 'b', 'c', 'd', 'e', 'f']; //从第index为1的位置开始删除元素,一共删除三个元素。并且在index=1的位置前面追加两个元素"千古壹号"、"vae"(其实就是将index为1的元素改为"千古壹号",index为2的元素改为"vae")。 var result5 = arr5.splice(1, 3, '千古壹号', 'vae'); console.log('arr5:' + JSON.stringify(arr5)); console.log('result5:' + JSON.stringify(result5)); ``` 打印结果: ```javascript arr5:["a","千古壹号","vae","e","f"] result5:["b","c","d"] ``` 我们再看个类似的例子: ```js // 需求:针对数组 [a, b, c, d] 将索引为1的数据修改为e, 索引为2的修改为f // 写法1:普通写法 const arr = [a, b, c ,d]; arr[1] = 'e'; arr[2] = 'f'; // 写法2:通过 splice() 实现 const arr = [a, b, c ,d]; arr.splice(1,2, 'e', 'f'); ``` ### concat() `concat()`:连接两个或多个数组,返回结果为**新的数组**。不会改变原数组。`concat()`方法的作用是**数组合并**。 语法: ```javascript 新数组 = 数组1.concat(数组2, 数组3 ...); ``` 举例: ```javascript const arr1 = [1, 2, 3]; const arr2 = ['a', 'b', 'c']; const arr3 = ['千古壹号', 'vae']; const result1 = arr1.concat(arr2); const result2 = arr2.concat(arr1, arr3); console.log('arr1 =' + JSON.stringify(arr1)); console.log('arr2 =' + JSON.stringify(arr2)); console.log('arr3 =' + JSON.stringify(arr3)); console.log('result1 =' + JSON.stringify(result1)); console.log('result2 =' + JSON.stringify(result2)); ``` 打印结果: ```javascript arr1 = [1, 2, 3]; arr2 = ['a', 'b', 'c']; arr3 = ['千古壹号', 'vae']; result1 = [1, 2, 3, 'a', 'b', 'c']; result2 = ['a', 'b', 'c', 1, 2, 3, '千古壹号', 'vae']; ``` 从打印结果中可以看到,原数组并没有被修改。 **数组合并的另一种方式**: 我们可以使用`...`这种扩展运算符,将两个数组进行合并。举例如下: ```js const arr1 = [1, 2, 3]; const result = ['a', 'b', 'c', ...arr1]; console.log(JSON.stringify(result)); // 打印结果:["a","b","c",1,2,3] ``` 备注:数组不能使用加号进行拼接。如果使用加号进行拼接会先转换成字符串再拼接。 ### slice() `slice()`:从数组中**提取**指定的一个或者多个元素,返回结果为**新的数组**(不会改变原来的数组)。 备注:该方法不会改变原数组,而是将截取到的元素封装到一个新数组中返回。 **语法**: ```javascript 新数组 = 原数组.slice(开始位置的索引); 新数组 = 原数组.slice(开始位置的索引, 结束位置的索引); //注意:提取的元素中,包含开始位置,不包含结束位置 ``` 举例: ```javascript const arr = ['a', 'b', 'c', 'd', 'e', 'f']; const result1 = arr.slice(); // 不加参数时,则获取所有的元素。相当于数组的整体赋值 const result2 = arr.slice(2); // 从第二个值开始提取,直到末尾 const result3 = arr.slice(-2); // 提取最后两个元素 const result4 = arr.slice(2, 4); // 提取从第二个到第四个之间的元素(不包括第四个元素) const result5 = arr.slice(4, 2); // 空 console.log('arr:' + JSON.stringify(arr)); console.log('result1:' + JSON.stringify(result1)); console.log('result2:' + JSON.stringify(result2)); console.log('result3:' + JSON.stringify(result3)); console.log('result4:' + JSON.stringify(result4)); console.log('result5:' + JSON.stringify(result5)); ``` 打印结果: ```javascript arr: ['a', 'b', 'c', 'd', 'e', 'f']; result1: ['a', 'b', 'c', 'd', 'e', 'f']; result2: ['c', 'd', 'e', 'f']; result3: ['e', 'f']; result4: ['c', 'd']; result5: []; ``` **补充**: 很多前端开发人员会用 slice()将伪数组,转化为真数组。写法如下: ```javascript // 方式1 array = Array.prototype.slice.call(arrayLike); // 方式2 array = [].slice.call(arrayLike); ``` ES6 看不下去这种蹩脚的转化方法,于是出了一个新的 API:(专门用来将伪数组转化成真数组) ```javascript array = Array.from(arrayLike); ``` 关于这个 API 的详细介绍,上面的内容已经讲了,请往前翻。 ### fill() `fill()`:用一个固定值填充数组,返回结果为**新的数组**。会改变原数组。 语法: ```js // 用一个固定值填充数组。数组里的每个元素都会被这个固定值填充 新数组 = 数组.fill(固定值); // 从 startIndex 开始的数组元素,用固定值填充 新数组 = 数组.fill(固定值, startIndex); // 从 startIndex 到 endIndex 之间的元素(包左不包右),用固定值填充 新数组 = 数组.fill(固定值, startIndex, endIndex); ``` 举例1: ```js // 创建一个长度为4的空数组,然后用 'f' 来填充这个空数组 console.log(Array(4).fill('f')); // ['f', 'f', 'f,' 'f'] // 将现有数组的每一个元素都进行填充 console.log(['a', 'b', 'c', 'd'].fill('f')); // ['f', 'f', 'f,' 'f'] ``` 举例2: ```js // 指定位置进行填充 let arr1 = ['a', 'b', 'c', 'd']; let arr2 = arr1.fill('f', 1, 3); console.log(arr1); // ['a', 'f', 'f,' 'd'] console.log(arr2); // ['a', 'f', 'f,' 'd'] ``` ## reverse() `reverse()`:反转数组,返回结果为**反转后的数组**(会改变原来的数组)。 语法: ```js 反转后的数组 = 数组.reverse(); ``` 举例: ```javascript var arr = ['a', 'b', 'c', 'd', 'e', 'f']; var result = arr.reverse(); // 将数组 arr 进行反转 console.log('arr =' + JSON.stringify(arr)); console.log('result =' + JSON.stringify(result)); ``` 打印结果: ``` arr =["f","e","d","c","b","a"] result =["f","e","d","c","b","a"] ``` 从打印结果可以看出,原来的数组已经被改变了。 ## sort() > sort()方法需要好好理解。 `sort()`:对数组的元素进行从小到大来排序(会改变原来的数组)。 ### 无参时 如果在使用 sort() 方法时不带参,则默认按照元素的**Unicode 编码**,从小到大进行排序。 **举例 1**:(当数组中的元素为字符串时) ```javascript let arr1 = ['e', 'b', 'd', 'a', 'f', 'c']; let result = arr1.sort(); // 将数组 arr1 进行排序 console.log('arr1 =' + JSON.stringify(arr1)); console.log('result =' + JSON.stringify(result)); ``` 打印结果: ``` arr1 =["a","b","c","d","e","f"] result =["a","b","c","d","e","f"] ``` 从上方的打印结果中,我们可以看到,sort 方法会改变原数组,而且方法的返回值也是同样的结果。 **举例 2**:(当数组中的元素为数字时) ```javascript let arr2 = [5, 2, 11, 3, 4, 1]; let result = arr2.sort(); // 将数组 arr2 进行排序 console.log('arr2 =' + JSON.stringify(arr2)); console.log('result =' + JSON.stringify(result)); ``` 打印结果: ``` arr2 =[1,11,2,3,4,5] result =[1,11,2,3,4,5] ``` 上方的打印结果中,你会发现,使用 sort() 排序后,数字`11`竟然在数字`2`的前面。这是为啥呢?因为上面讲到了,`sort()`方法是按照**Unicode 编码**进行排序的。 那如果我想让 arr2 里的数字,完全按照从小到大排序,怎么操作呢?继续往下看。 ### 带参时,自定义排序规则 如果在 sort()方法中带参,我们就可以**自定义**排序规则。具体做法如下: 我们可以在 sort()的参数中添加一个回调函数,来指定排序规则。回调函数中需要定义两个形参,JS将会分别使用数组中的元素作为实参去调用回调函数。 JS根据回调函数的返回值来决定元素的排序:(重要) - 如果返回一个大于 0 的值,则元素会交换位置 - **如果返回一个小于 0 的值,则不交换位置**。 - 如果返回一个等于 0 的值,则认为两个元素相等,则不交换位置 如果只是看上面的文字,可能不太好理解,我们来看看下面的例子,你肯定就能明白。 ### 举例:将数组中的数字按照从小到大排序 **写法 1**: ```javascript var arr = [5, 2, 11, 3, 4, 1]; // 自定义排序规则 var result = arr.sort(function (a, b) { if (a > b) { // 如果 a 大于 b,则交换 a 和 b 的位置 return 1; } else if (a < b) { // 如果 a 小于 b,则位置不变 return -1; } else { // 如果 a 等于 b,则位置不变 return 0; } }); console.log('arr =' + JSON.stringify(arr)); console.log('result =' + JSON.stringify(result)); ``` 打印结果: ```javascript arr = [1, 2, 3, 4, 5, 11]; result = [1, 2, 3, 4, 5, 11]; ``` 上方代码的写法太啰嗦了,其实也可以简化为如下写法: **写法 2**:(ES5写法) ```javascript var arr = [5, 2, 11, 3, 4, 1]; // 自定义排序规则 var result = arr.sort(function (a, b) { return a - b; // 升序排列 // return b - a; // 降序排列 }); console.log('arr =' + JSON.stringify(arr)); console.log('result =' + JSON.stringify(result)); ``` 打印结果不变。 上方代码还可以写成 ES6 的形式,也就是将 function 改为箭头函数,其写法如下。 **写法 3**:(ES6写法,箭头函数) ```js let arr = [5, 2, 11, 3, 4, 1]; // 自定义排序规则 let result = arr.sort((a, b) => { return a - b; // 升序排列 }); console.log('arr =' + JSON.stringify(arr)); console.log('result =' + JSON.stringify(result)); ``` 上方代码,因为函数体内只有一句话,所以可以去掉 return 语句,继续简化为如下写法。 **写法 4**:(推荐写法) ```js let arr = [5, 2, 11, 3, 4, 1]; // 自定义排序规则:升序排列 let result = arr.sort((a, b) => a - b); console.log('arr =' + JSON.stringify(arr)); console.log('result =' + JSON.stringify(result)); ``` 上面的各种写法中,写法 4 是我们在实战开发中用得最多的。 为了确保代码的简洁优雅,接下来的讲解中,凡是涉及到函数,我们将尽量采用 ES6 中的箭头函数来写。 ### 举例:将数组从小到大排序 将数组从小到大排序,这个例子很常见。但在实际开发中,总会有一些花样。 下面这段代码,在实际开发中,经常用到,一定要掌握。完整代码如下: ```html Document ``` 打印结果: ``` qianguyihao 排序前的数组:[ {"title":"品牌鞋子,高品质低价入手","publishTime":200}, {"title":"不是很贵,但是很暖","publishTime":100}, {"title":"无法拒绝的美食,跟我一起吃","publishTime":300} ] qianguyihao 排序后的数组:[ {"title":"不是很贵,但是很暖","publishTime":100}, {"title":"品牌鞋子,高品质低价入手","publishTime":200}, {"title":"无法拒绝的美食,跟我一起吃","publishTime":300} ] ``` 上方代码中,肯定有人会问: publishTime 字段已经是 int 类型了,为啥在排序前还要做一次 parseInt() 转换?这是因为,这种数据,一般是后台接口返回给前端的,数据可能是 int 类型、也可能是字符串类型,所以前端还是统一先做一下 partInt() 比较保险。这是一种良好的工作习惯和风险意识。 ## indexOf() 和 lastIndexOf():获取元素的索引 **语法 1**: ```javascript 元素的索引 = 数组.indexOf(想要查询的元素); 元素的索引 = 数组.lastIndexOf(想要查询的元素); ``` 备注:`indexOf()` 是从左往右查找元素的位置。同理,`lastIndexOf()`是从右往左寻找。 **解释**:可以检索一个数组中是否含有指定的元素。如果数组中含有该元素,则会返回其**第一次出现**的索引,并立即停止查找;如果没有找到指定的内容,则返回 -1。 这个方法的作用: - 如果找到了指定的元素,就返回元素对应的位置。 - 如果没有找到指定的元素,就会返回-1。 **注意**:`indexOf()`在检索时,是严格类型约束,类似于`===`。 **举例** : ```javascript const arr = ['a', 'b', 'c', 'd', 'e', 'd', 'c']; console.log(arr.indexOf('c')); //从前往后,找第一个"c"在哪个位置 console.log(arr.lastIndexOf('d')); //从后往前,找第一个"d"在哪个位置 ``` 打印结果: ``` 2 5 ``` **举例**: ```js let arr = ['1', '2', '3', '4', '5']; console.log(arr.indexOf(2)); ``` 打印结果: ``` -1 ``` **语法 2**: 这个方法还可以指定第二个参数,用来指定查找的**起始位置**。语法如下: ```javascript 索引值 = 数组.indexOf(想要查找的元素, [查找的起始位置]); ``` 这个方法的第二个参数非常巧妙,数据结构与算法的面试题中,时常出现。 举例:(两个参数时,需要特别注意) ```javascript let arr = ['q', 'i', 'a', 'n', 'g', 'u', 'y', 'i', 'h', 'a', 'o']; result = str.indexOf('a', 3); // 从下标为3的位置开始查找 'a'这个元素 【重要】 console.log(result); // 打印结果:9 ``` 上方代码中,`indexOf()`方法中携带了两个参数,具体解释请看注释。 ## includes() **语法**: ```js 布尔值 = arr.includes(想要查找的元素, [position]); ``` **解释**:判断一个数组中是否包含指定的元素。如果是,则会返回 true;否则返回 false。 参数中的 `position`:如果不指定,则默认为0;如果指定,则规定了检索的起始位置。 ```js const arr = [11, 12, 13, 14, 15]; console.log(arr.includes(12)); // 打印结果:true console.log(arr.includes(20)); // 打印结果:false console.log(arr.includes(11, 1)); // 打印结果:false ``` ## find()和findIndex() ### find() **语法**: ```javascript const itemResult = arr.find((currentItem, currentIndex, currentArray) => { return true; }); ``` **作用**:找出**第一个**满足「指定条件返回 true」的元素,并立即停止查找;如果没找到,则返回 undefined。 备注:一旦找到符合条件的第一个元素,将不再继续往下遍历。 举例1: ```javascript let arr = [2, 3, 2, 5, 7, 6]; let result = arr.find((item, index) => { return item > 4; //遍历数组arr,一旦发现有第一个元素大于4,就把这个元素返回 // 上面这行代码是简写方式;完整写法也可以这样写:ccif (item > 4) {return true} }); console.log(result); //打印结果:5 ``` 重要提醒:如果改变了 itemResult 内部的值,则 arr 里的对应元素,它的值也会被改变。举例如下。 举例2:todo ### findIndex() **语法**: ```javascript const indexResult = arr.findIndex((currentItem, currentIndex, currentArray) => { return true; }); ``` **作用**:找出**第一个**满足「指定条件返回 true」的元素的索引,并立即停止遍历;如果没找到,则返回 -1。 举例: > 我们直接把上面find 方法的代码示例改成 findIndex,看看效果。 ```javascript let arr = [2, 3, 2, 5, 7, 6]; let result = arr.findIndex((item, index) => { return item > 4; //遍历数组arr,一旦发现有第一个元素大于4,就把这个元素的index返回 }); console.log(result); //打印结果:3 ``` ## every()和some() ### every() **语法**: ```javascript const boolResult = arr.every((currentItem, currentIndex, currentArray) => { return true; }); ``` `every()`:对数组中每一项运行回调函数,如果都返回 true,every 就返回 true;如果有一项返回 false,则停止遍历,此方法返回 false。 注意:every()方法的返回值是 boolean 值,参数是回调函数。 举例: ```javascript var arr1 = ['千古', '宿敌', '南山忆', '素颜']; var bool1 = arr1.every(function (item, index, array) { if (item.length > 2) { return false; } return true; }); console.log(bool1); //输出结果:false。只要有一个元素的长度是超过两个字符的,就返回false var arr2 = ['千古', '宿敌', '南山', '素颜']; var bool2 = arr2.every(function (item, index, array) { if (item.length > 2) { return false; } return true; }); console.log(bool2); //输出结果:true。因为每个元素的长度都是两个字符。 ``` ### some() `some()`:对数组中每一个元素运行回调函数,只要有一个元素返回 true,则停止遍历,此方法返回 true。 注意:some()方法的返回值是 boolean 值。 ### every() 和 some() 的使用场景 every() 和 some() 这两个方法,初学者很容易搞混。要怎么区分呢?你可以这样记: - every():全部真,才为真。当你需要让数组中的每一个元素都满足指定条件时,那就使用 every()。 - some():一个真,则为真,点到为止。数组中只要有一个元素满足指定条件时,就停止遍历。那就使用 some()。 ## valueOf():返回数组本身 ```javascript 数组本身 = 数组.valueOf(); ``` 这个方法的意义不大。因为我们直接写数组对象的名字,就已经是数组本身了。 ## 遍历数组 ### 概念 **遍历数组**:获取并操作数组中的每一个元素,然后得到想要的返回结果。在实战开发中使用得非常频繁。 语法: ```js // ES5语法 数组/boolean/无 = 数组.forEach/map/filter(function (item, index, arr) { 相关代码和返回值; }) // ES6语法 数组/boolean/无 = 数组.forEach/map/filter((item, index, arr) => { 相关代码和返回值; }) ``` 有了上面这些方法(其实远不止这几个),就可以替代 for 循环了。 我们先来看看传统的for循环,然后依次介绍其他方法。 ### for 循环遍历 举例: ```javascript const arr = ['千古壹号', '许嵩', 'vae']; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); // arr[i]代表的是数组中的每一个元素i } console.log(JSON.stringify(arr)); ``` 打印结果: ``` 千古壹号 许嵩 vae ["千古壹号","许嵩","vae"] ``` ## forEach() > `forEach()` 这种遍历方法只支持 IE8 以上的浏览器。IE8 及以下的浏览器均不支持该方法。所以如果需要兼容 IE8,则不要使用 forEach,改为使用 for 循环来遍历即可。 ### 语法 ```js // ES5语法 arr.forEach(function (currentItem, currentIndex, currentArray) { console.log(currentValue); }); // ES6语法 arr.forEach((currentItem, currentIndex, currentArray) => { console.log(currentValue); }); ``` forEach()方法需要一个函数作为参数。这种函数,是由我们创建但是不由我们调用的,我们称为回调函数。 数组中有几个元素,该回调函数就会执行几次。 回调函数中传递三个参数: - 参数1:当前正在遍历的元素 - 参数2:当前正在遍历的元素的索引 - 参数3:正在遍历的数组 注意,forEach() 没有返回值。也可以理解成:forEach() 的返回值是 undefined。如果你尝试 `newArray = currentArray.forEach()`这种方式来接收,是达不到效果的。 代码举例: ```javascript let myArr = ['王一', '王二', '王三']; myArr.forEach((currentItem, currentIndex, currentArray) => { console.log('item:' + currentItem); console.log('index:' + currentIndex); console.log('arr:' + JSON.stringify(currentArray)); console.log('----------'); }); ``` 打印结果: ```javascript item:王一 index:0 arr:["王一","王二","王三"] ---------- item:王二 index:1 arr:["王一","王二","王三"] ---------- item:王三 index:2 arr:["王一","王二","王三"] ---------- ``` ### forEach() 会不会改变原数组? forEach() 会不会改变原数组?关于这个问题,大部分人会搞错。我们来看看下面的代码。 **1、数组的元素是基本数据类型**:(无法改变原数组) ```js let numArr = [1, 2, 3]; numArr.forEach((item) => { item = item * 2; }); console.log(JSON.stringify(numArr)); // 打印结果:[1, 2, 3] ``` 上面这段代码,你可要看仔细了,打印结果是 `[1, 2, 3]`,不是 `[2, 4, 6]`。 **2、数组的元素是引用数据类型**:(直接修改整个元素对象时,无法改变原数组) ```js let objArr = [ { name: '千古壹号', age: 20 }, { name: '许嵩', age: 30 }, ]; objArr.forEach((item) => { item = { name: '邓紫棋', age: '29', }; }); console.log(JSON.stringify(objArr)); // 打印结果:[{"name":"千古壹号","age":20},{"name":"许嵩","age":30}] ``` **3、数组的元素是引用数据类型**:(修改元素对象里的某个属性时,可以改变原数组) ```js let objArr = [ { name: '千古壹号', age: 28 }, { name: '许嵩', age: 30 }, ]; objArr.forEach((item) => { item.name = '邓紫棋'; }); console.log(JSON.stringify(objArr)); // 打印结果:[{"name":"邓紫棋","age":28},{"name":"邓紫棋","age":30}] ``` 如果你需要通过 forEach 修改原数组,建议用 forEach 里面的参数 2 和参数 3 来做,具体请看下面的标准做法。 **4、forEach() 通过参数 2、参数 3 修改原数组**:(标准做法,一定要看) ```js // 1、数组的元素是基本数据类型 let numArr = [1, 2, 3]; numArr.forEach((item, index, arr) => { arr[index] = arr[index] * 2; }); console.log(JSON.stringify(numArr)); // 打印结果:[2,4,6] // 2、数组的元素是引用数据类型时,直接修改对象 let objArr = [ { name: '千古壹号', age: 28 }, { name: '许嵩', age: 34 }, ]; objArr.forEach((item, index, arr) => { arr[index] = { name: '小明', age: '10', }; }); console.log(JSON.stringify(objArr)); // 打印结果:[{"name":"小明","age":"10"},{"name":"小明","age":"10"}] // 3、数组的元素是引用数据类型时,修改对象的某个属性 let objArr2 = [ { name: '千古壹号', age: 28 }, { name: '许嵩', age: 34 }, ]; objArr2.forEach((item, index, arr) => { arr[index].name = '小明'; }); console.log(JSON.stringify(objArr2)); // 打印结果:[{"name":"小明","age":28},{"name":"小明","age":34}] ``` **总结**: 如果纯粹只是遍历数组,那么,可以用 forEach() 方法。但是,如果你想在遍历数组的同时,去改变数组里的元素内容,那么,最好是用 map() 方法来做,不要用 forEach()方法,避免出现一些低级错误。 参考链接: - [forEach 到底可以改变原数组吗?](https://juejin.im/post/5d526a4ae51d4557dc774e7d) - [forEach 会改变原数组值吗](https://lhajh.github.io/js/2018/05/26/Does-forEach-change-the-original-array-value.html) ### 空数组调用 forEach() 方法时,会不会报错? 例1: ```js const arr1 = undefined; arr.forEach(item => { console.log(item); item.name = 'qianguyihao'; }); ``` 上面的代码中,数组 arr1 并不存在,所以会报错`Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')` 例2: ```js const arr2 = []; arr2.forEach(item => { console.log(item); item.name = "qianguyihao"; }); ``` 上面的代码中,arr2是空数组,但是在遍历时并不会报错,因为 forEach 是数组的内置方法。arr2作为空数组,是属于特殊的数组,数组在调用内置方法时不会报错。在上面的例2中,forEach 对空数组不会执行回调函数(也就意味着,console.log 那行不会执行),因为没有元素需要遍历。 如果把 forEach() 换成 map()方法,也是一样的道理。 ## for of ES6语法推出了 for of,可用于循环遍历数组。 ### 语法 ```js for(let value of arr) { console.log(value); } ``` ### 不要使用 for in 遍历数组 for in 是专门用于遍历对象的。对象的属性是无序的(而数组的元素有顺序),for in循环就是专门用于遍历无序的对象。所以,不要用 for in 遍历数组。 for in语法: ```js for (let key in obj) { console.log(key); console.log(obj.key); } ``` ## map() ### 语法 ```js // ES5语法 const newArr = arr.map(function (currentItem, currentIndex, currentArray) { return newItem; }); // ES6语法 const newArr = arr.map((currentItem, currentIndex, currentArray) => { return newItem; }); ``` 解释:对数组中每一项运行回调函数,返回该函数的结果,组成的新数组(返回的是**加工后**的新数组)。不会改变原数组。 作用:对数组中的每一项进行加工。 **举例 1**:(拷贝的过程中改变数组元素的值) 有一个已知的数组 arr1,我要求让 arr1 中的每个元素的值都加 10,这里就可以用到 map 方法。代码举例: ```javascript const arr1 = [1, 3, 6, 2, 5, 6]; const arr2 = arr1.map(item => { return item + 10; //让arr1中的每个元素加10 }); console.log(arr2); // 数组 arr2 的值:[11, 13, 16, 12, 15, 16] ``` **举例 2**:【重要案例,实际开发中经常用到】 将 A 数组中某个属性的值,存储到 B 数组中。代码举例: ```javascript const arr1 = [ { name: '千古壹号', age: '28' }, { name: '许嵩', age: '32' }, ]; // 举例2.1、将数组 arr1 中的 name 属性,存储到 数组 arr2 中 const arr2 = arr1.map(item => item.name); // 上面的代码是简写的方式。完整写法是下面这样:(这两种写法是等价的) const _arr2 = arr1.map(item => { return item.name; }); // 举例2.2、将数组 arr1 中的 name、age这两个属性,改一下“键”的名字,存储到 arr3中 const arr3 = arr1.map(item => ({ myName: item.name, myAge: item.age, })); // 将数组 arr1 中的 name 属性,存储到 数组 arr2 中 console.log('arr1:' + JSON.stringify(arr1)); console.log('arr2:' + JSON.stringify(arr2)); console.log('arr3:' + JSON.stringify(arr3)); ``` 打印结果: ``` arr1:[{"name":"千古壹号","age":"28"},{"name":"许嵩","age":"32"}] arr2:["千古壹号","许嵩"] arr3:[{"myName":"千古壹号","myAge":"28"},{"myName":"许嵩","myAge":"32"}] ``` map 的应用场景,主要就是以上两种。 ### map() 方法会不会改变原数组? 答案:不一定。 举例: ```javascript const arr = [ { name: "qianguyihao1", age: 22, }, { name: "qianguyihao2", age: 23, }, ]; arr.map((item) => { item.name = "haha"; // 修改 item 里的某个属性 return item; }); console.log(JSON.stringify(arr)); ``` 打印结果: ``` [{"name":"haha","age":22},{"name":"haha","age":23}] ``` 总结:map方法如果是修改整个item的值,则不会改变原数组。但如果是修改 item 里面的某个属性,那就会改变原数组。 ### map()在遍历时,如果不写 return 会怎么样 举例: ```js const arr1 = [{ name: 'hehe1' }, { name: 'hehe2' }]; const arr2 = arr1.map(item => { item.name = 'haha'; }); console.log(arr1); console.log(arr2); ``` 代码执行完成后: - arr1 的结果:[{ name: 'haha' }, { name: 'haha' }] - arr2 的结果:[undefined, undefined] 由此可见,如果 map() 方法中没有 return 语句也是合法的,它会默认返回 `undefined`。 所以,针对对象数组,**如果你只是想修改对象中的某个属性值,而不想创建新数组的话,建议使用 forEach() 方法,而不是 map() 方法**。map() 方法的初衷是创建一个新数组。 ## filter() ### 语法 ```js const newArr = arr.filter((currentItem, currentIndex, currentArray) => { return true; }); ``` 解释:对数组中的**每一项**运行回调函数,该函数返回结果是 true 的项,将组成新的数组(返回值就是这个新数组)。不会改变原数组。 作用:对数组进行过滤。 ### 举例 **举例 1**:找出数组 arr1 中大于 4 的元素,返回一个新的数组。代码如下: ```javascript let arr1 = [1, 3, 6, 2, 5, 6]; let arr2 = arr1.filter(item => { if (item > 4) { return true; // 将arr1中大于4的元素返回,组成新的数组 } return false; }); console.log(JSON.stringify(arr1)); // 打印结果:[1,3,6,2,5,6] console.log(JSON.stringify(arr2)); // 打印结果:[6,5,6] ``` 上方代码更简洁的写法如下: ```javascript let arr1 = [1, 3, 6, 2, 5, 6]; let arr2 = arr1.filter(item => item > 4); // 将arr1中大于4的元素返回,组成新的数组 console.log(JSON.stringify(arr1)); // 打印结果:[1,3,6,2,5,6] console.log(JSON.stringify(arr2)); // 打印结果:[6,5,6] ``` **举例 2**: 获取对象数组 arr1 中指定类型的对象,放到数组 arr2 中。代码举例如下: ```javascript const arr1 = [ { name: '许嵩', type: '一线' }, { name: '周杰伦', type: '退居二线' }, { name: '邓紫棋', type: '一线' }, ]; const arr2 = arr1.filter(item => item.type == '一线'); // 筛选出一线歌手 console.log(JSON.stringify(arr2)); ``` 打印结果: ```javascript [ { name: '许嵩', type: '一线' }, { name: '邓紫棋', type: '一线' }, ]; ``` ### 两端代码对比 仔细看看下面这两段代码,有什么区别。数组 arr2的打印结果是不一样的。 第一段代码: ```js const arr1 = [ { name: 'a', num: 1, }, { name: 'b', num: 2, }, ]; const arr2 = []; const arr3 = dataList.filter(item => { return item.num === 1; arr2.push(item); }); console.log(arr2); ``` 第二段代码: ```js const arr1 = [ { name: 'a', num: 1, }, { name: 'b', num: 2, }, ]; const arr2 = []; const arr3 = dataList.filter(item => { if (item.num === 1) return item; arr2.push(item); }); console.log('smyhvae arr2:', arr2); ``` 分析: - 第一段代码的打印结果是 空数组 `[]`。因为`return` 语句位于回调函数的第一行,所以一旦执行就直接返回,导致后面的 `arr2.push(item);` 永远不会被执行,因此 `arr2` 始终为空。 - 第二段代码的打印结果是` [{ name: 'b', num: 2 }]`。由于 `return` 语句位于 `if` 语句内部,只有在特定条件下(`item.num === 1`)才会终止回调函数,否则 `arr2.push(item);` 仍然会被执行,因此 `arr2` 中会有值。 ## reduce() ### reduce() 语法 > reduce 的发音:[rɪ'djuːs]。中文含义是减少,但这个方法跟“减少”没有任何关系。 reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。返回值是回调函数累计处理的结果。 **语法**: ```javascript arr.reduce(function (previousValue, currentValue, currentIndex, arr) {}, initialValue); ``` 参数解释: - previousValue:必填,上一次调用回调函数时的返回值 - currentValue:必填,当前正在处理的数组元素 - currentIndex:选填,当前正在处理的数组元素下标 - arr:选填,调用 reduce()方法的数组 - initialValue:选填,可选的初始值(作为第一次调用回调函数时传给 previousValue 的值) 在以往的数组方法中,匿名的回调函数里是传三个参数:item、index、arr。但是在 reduce() 方法中,前面多传了一个参数`previousValue`,这个参数的意思是上一次调用回调函数时的返回值。第一次执行回调函数时,previousValue 没有值怎么办?可以用 initialValue 参数传给它。 备注:绝大多数人在一开始接触 reduce() 的时候会很懵逼,但是没关系,有事没事多看几遍,自然就掌握了。如果能熟练使用 reduce() 的用法,将能替代很多其他的数组方法,并逐渐走上进阶之路,领先于他人。 为了方便理解 reduce(),我们先来看看下面的简单代码,过渡一下: ```js let arr1 = [1, 2, 3, 4, 5, 6]; arr1.reduce((prev, item) => { console.log(prev); console.log(item); console.log('------'); return 88; }, 0); ``` 打印结果: ``` 0 1 ------ 88 2 ------ 88 3 ------ 88 4 ------ 88 5 ------ 88 6 ------ ``` 上面的代码中,由于`return`的是固定值,所以 prev 打印的也是固定值(只有初始值是 0,剩下的遍历中,都是打印 88)。 现在来升级一下,实际开发中,prev 的值往往是动态变化的,这便是 reduce()的精妙之处。我们来看几个例子就明白了。 ### reduce() 的常见应用 **举例 1**、求和: 计算数组中所有元素项的总和。代码实现: ```javascript const arr = [2, 0, 1, 9, 6]; // 数组求和 const total = arr.reduce((prev, item) => { return prev + item; }); console.log('total:' + total); // 打印结果:18 ``` **举例 2**、统计某个元素出现的次数: 代码实现: ```js // 定义方法:统一 value 这个元素在数组 arr 中出现的次数 function repeatCount(arr, value) { if (!arr || arr.length == 0) return 0; return arr.reduce((totalCount, item) => { totalCount += item == value ? 1 : 0; return totalCount; }, 0); } let arr1 = [1, 2, 6, 5, 6, 1, 6]; console.log(repeatCount(arr1, 6)); // 打印结果:3 ``` **举例 3**、求元素的最大值: 代码实现: ```js const arr = [2, 0, 1, 9, 6]; // 数组求最大值 const maxValue = arr.reduce((prev, item) => { return prev > item ? prev : item; }); console.log(maxValue); // 打印结果:9 ``` 参考链接: - [JS reduce 函数](https://juejin.im/post/5d78aa3451882521397645ae) ## 数组练习 ### splice()练习:数组去重 代码实现: ```javascript //创建一个数组 const arr = [1, 2, 3, 2, 2, 1, 3, 4, 2, 5]; //去除数组中重复的数字 //获取数组中的每一个元素 for (let i = 0; i < arr.length; i++) { /*获取当前元素后的所有元素*/ for (let j = i + 1; j < arr.length; j++) { //console.log("---->"+arr[j]); //判断两个元素的值是否相等 if (arr[i] == arr[j]) { //如果相等则证明出现了重复的元素,则删除j对应的元素 arr.splice(j, 1); //当删除了当前j所在的元素以后,后边的元素会自动补位 //此时将不会再比较这个元素,我们需要再比较一次j所在位置的元素 //使j自减 j--; } } } console.log(arr); ``` ### 清空数组 清空数组,有以下几种方式: ```javascript const arr = [1, 2, 3]; arr = []; //方式1:推荐 arr.length = 0; //方式2:length属性可以赋值,在其它语言中length是只读 arr.splice(0); //方式3:删除数组中所有元素。也可以写成 arr.splice(0, arr.length) ``` ### join() 练习 **问题**:将一个字符串数组输出为`|`分割的形式,比如“千古|宿敌|素颜”。使用两种方式实现。 答案: 方式 1:(不推荐) ```javascript var arr = ['千古', '宿敌', '素颜']; var str = arr[0]; var separator = '|'; for (var i = 1; i < arr.length; i++) { str += separator + arr[i]; //从第1个数组元素开始,每个元素前面加上符号"|" } console.log(str); ``` 输出结果: ![](http://img.smyhvae.com/20180126_1336.png) 不推荐这种方式,因为:由于字符串的不变性,str 拼接过多的话,容易导致内存溢出(很多个 str 都堆放在栈里)。 方式 2:(推荐。通过 array 数组自带的 api 来实现) ```javascript var arr = ['千古', '宿敌', '素颜']; console.log(arr.join('|')); ``` 结果: ![](http://img.smyhvae.com/20180126_1339.png) ### reverse() 练习 题目:将一个字符串数组的元素的顺序进行反转,使用两种种方式实现。提示:第 i 个和第 length-i-1 个进行交换。 答案: 方式 1: ```javascript function reverse(array) { var newArr = []; for (var i = array.length - 1; i >= 0; i--) { newArr[newArr.length] = array[i]; } return newArr; } ``` 方式 2:(算法里比较常见的方式) ```javascript function reverse(array) { for (var i = 0; i < array.length / 2; i++) { var temp = array[i]; array[i] = array[array.length - 1 - i]; array[array.length - 1 - i] = temp; } return array; } ``` 方式 3:(数组自带的 reverse 方法) 现在我们学习了数组自带的 api,我们就可以直接使用 reverse()方法。 ### 练习:数组去重 问题:编写一个方法去掉一个数组中的重复元素。 分析:创建一个新数组,循环遍历,只要新数组中有老数组的值,就不用再添加了。 答案: ```javascript // 编写一个方法 去掉一个数组的重复元素 var arr = [1, 2, 3, 4, 5, 2, 3, 4]; console.log(arr); var aaa = fn(arr); console.log(aaa); //思路:创建一个新数组,循环遍历,只要新数组中有老数组的值,就不用再添加了。 function fn(array) { var newArr = []; for (var i = 0; i < array.length; i++) { //开闭原则 var bool = true; //每次都要判断新数组中是否有旧数组中的值。 for (var j = 0; j < newArr.length; j++) { if (array[i] === newArr[j]) { bool = false; } } if (bool) { newArr[newArr.length] = array[i]; } } return newArr; } ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/20-函数简介.md ================================================ --- title: 20-函数简介 --- ## 函数的介绍 函数:就是一些功能或语句的**封装**。在需要的时候,通过**调用**的形式,执行这些语句。 补充: - **函数也是一个对象** - 使用`typeof`检查一个函数对象时,会返回 function **函数的作用**: - 一次定义,多次调用。将大量重复的语句抽取出来,写在函数里,以后需要这些语句时,可以直接调用函数,避免重复劳动。 - 简化代码,可读性更强,让编程模块化。高内聚、低耦合。 来看个例子: ```javascript console.log("你好"); sayHello(); // 调用函数 sayHello(); // 再调用一次函数 // 定义函数 function sayHello(){ console.log("欢迎"); console.log("welcome"); } ``` ## 函数的定义/声明 我们使用`function`关键字定义函数,中文含义是“函数”、“功能”。可以使用如下方式进行定义。 ### 方式一:函数声明(命名函数) 使用`函数声明`来创建一个函数。语法: ```javascript function 函数名([形参1,形参2...形参N]){ // 备注:语法中的中括号,表示“可选” // 函数体语句 } ``` 举例: ```javascript function sum(a, b){ return a+b; } ``` 解释如下: - 函数名:命名规定和变量的命名规定一样,必须符合JS标识符的命名规则。只能是字母、数字、下划线、美元符号,不能以数字开头。 - 圆括号里,是形参列表,可选。即使没有形参,也必须书写圆括号。 - 大括号里,是函数体语句。 PS:在有些编辑器中,方法写完之后,我们在方法的前面输入`/**`,然后回车,会发现,注释的格式会自动补齐。 ### 方式二:函数表达式(匿名函数) 使用`函数表达式`来创建一个函数。语法: ```javascript const 变量名 = function([形参1,形参2...形参N]){ 语句.... } ``` 举例: ```javascript const fun2 = function() { console.log("我是匿名函数中封装的代码"); }; ``` 解释如下: - 上面的 fun2 是变量名,不是函数名。 - 函数表达式的声明方式跟声明变量类似,只不过变量里存的是值,而函数表达式里存的是函数。 - 函数表达式也可以传递参数。 从方式二的举例中可以看出:所谓的“函数表达式”,其实就是将匿名函数赋值给一个变量。因为,一个匿名函数终究还是要给它一个接收对象,进而方便地调用这个函数。 ### 方式三:使用构造函数 new Function() 使用构造函数`new Function()`来创建一个对象。这种方式,用的少。 语法: ```javascript const 变量名/函数名 = new Function('形参1', '形参2', '函数体'); ``` 注意,Function 里面的参数都必须是**字符串**格式。也就是说,形参也必须放在**字符串**里;函数体也是放在**字符串**里包裹起来,放在 Function 的最后一个参数的位置。 代码举例: ```javascript const fun3 = new Function('a', 'b', 'console.log("我是函数内部的内容"); console.log(a + b);'); fun3(1, 2); // 调用函数 ``` 打印结果: ``` 我是函数内部的内容 3 ``` **分析**: 方式3的写法很少用,原因如下: - 不方便书写:写法过于啰嗦和麻烦。 - 执行效率较低:首先需要把字符串转换为 js 代码,然后再执行。 ### 小结 1、**所有的函数,都是 `Fuction` 的“实例”**(或者说是“实例对象”)。函数本质上都是通过 new Function 得到的。 2、函数既然是实例对象,那么,**函数也属于“对象”**。还可以通过如下特征,来佐证函数属于对象: (1)我们直接打印某一个函数,比如 `console.log(fun2)`,发现它的里面有`__proto__`。(这个是属于原型的知识,后续再讲) (2)我们还可以打印 `console.log(fun2 instanceof Object)`,发现打印结果为 `true`。这说明 fun2 函数就是属于 Object。 ## 函数的调用 调用函数即:执行函数体中的语句。函数必须要等到被调用时才执行。 ### 方式1:普通函数的调用 函数调用的语法: ```javascript // 写法1(最常用) 函数名(); // 写法2 函数名.call(); ``` 代码举例: ```javascript function fn1() { console.log('我是函数体里面的内容1'); } function fn2() { console.log('我是函数体里面的内容2'); } fn1(); // 调用函数 fn2.call(); // 调用函数 ``` ### 方式2:通过对象的方法来调用 ```javascript var obj = { a: 'qianguyihao', fn2: function() { console.log('千古壹号,永不止步!'); }, }; obj.fn2(); // 调用函数 ``` 如果一个函数是作为一个对象的属性保存,那么,我们称这个函数是这个对象的**方法**。 PS:关于函数和方法的区别,本文的后续内容里有讲到,可以往下面翻。 ### 方式3:立即执行函数 代码举例: ```javascript (function() { console.log('我是立即执行函数'); })(); ``` 立即执行函数在定义后,会自动调用。 PS:关于立即执行函数,本文的后续内容里有讲到,可以往下面翻。 上面讲到的这三种方式,是用得最多的。接下来讲到的三种方式,暂时看不懂也没关系,可以等学完其他的知识点,再回过头来看。 ### 方式4:通过构造函数来调用 代码举例: ```javascript function Fun3() { console.log('千古壹号,永不止步~'); } new Fun3(); ``` 这种方式用得不多。 ### 方式5:绑定事件函数 代码举例: ```html Document
    我是按钮,请点击我
    ``` 这里涉及到DOM操作和事件的知识点,后续再讲。 ### 方式6:定时器函数 代码举例:(每间隔一秒,将 数字 加1) ```javascript let num = 1; setInterval(function () { num ++; console.log(num); }, 1000); ``` 这里涉及到定时器的知识点。 ## 函数的参数:形参和实参 ### 定义 函数的参数包括形参和实参。形参是函数内的一些**待定值**。在调用函数时,需传入这些参数的具体值(即实参)。 可以在函数的`()`中指定一个或多个参数,也可以不指定参数。多个参数之间用英文逗号隔开。 举例: ```js // a, b 是形参,表示待定值 function add(a, b) { const sum = a + b; console.log(sum); } // 1, 2 是实参,表示传入的具体值。调用函数时,传入实参 add(1, 2); ``` **形参:** - 概念:形式上的参数。定义函数时传递的待定值(此时并不知道是什么值)。 - 声明形参相当于在函数内部声明了变量,但并不赋值。也可以说,**形参的默认值是 undefined**。 **实参**: - 概念:实际上的参数。调用函数时传递的具体值。实参将传递给函数中对应的形参。 举例: ```javascript // 调用函数 add(3, 4); add("3", 4); add("Hello", "World"); // 定义函数:求和 function add(a, b) { console.log(a + b); } ``` 控制台输出结果: ``` 7 34 helloworld ``` ### 形参和实参的个数 实际参数和形式参数的个数,可以不同。调用函数时,解析器不会检查实参的数量。 - 如果实参个数 > 形参个数,则末尾的实参是多余的,不会被赋值,因为没有形参能接收它。 - 如果实参个数 < 形参个数,则末尾的形参是多余的,值是 undefined,因为它没有接收到实参。(undefined参与运算时,表达式的运算结果为NaN) 代码举例: ```javascript function sum(a, b) { console.log(a + b); } sum(1, 2); sum(1, 2, 3); sum(1); ``` 打印结果: ``` 3 3 NaN ``` ### 实参的数据类型 函数的实参可以是任意的数据类型。调用函数时,解析器不会检查实参类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型检查。 ## 函数的返回值 ### return 关键字 函数体内可以没有返回值,也可以根据需要加返回值。语法格式:`return 函数的返回值`。 举例: ```javascript console.log(sum(3, 4)); // 将函数的返回值打印出来 //函数:求和 function sum(a, b) { return a + b; } ``` return关键字的作用既可以是**终止函数**,也可以给函数添加返回值。 解释: (1)return 后的返回值将会作为函数的执行结果返回,可以定义一个变量,来接收该返回值。 (2)在函数中,return后的语句都不会执行。也就是说,函数在执行完 return 语句之后,会立即退出函数。 (3)如果return语句后不跟任何值,就相当于返回一个undefined (4)如果函数中不写return,则也会返回undefined (5)返回值可以是任意的数据类型,可以是对象,也可以是函数。 (6)return 只能返回一个值。如果用逗号隔开多个值,则以最后一个为准。 ### break、continue、return 的区别 - break :结束当前的循环体(如 for、while) - continue :跳出本次循环,继续执行下次循环(如 for、while) - return :1、退出循环。2、返回 return 语句中的值,同时结束当前的函数体内的代码,退出当前函数。 ## 函数名、函数体和函数加载问题(重要,请记住) 我们要记住:**函数名 == 整个函数**。举例: ```javascript console.log(fn) == console.log(function fn(){alert(1)}); //定义fn方法 function fn(){ alert(1) }; ``` 我们知道,当我们在调用一个函数时,通常使用`函数()`这种格式;可如果,我们是直接使用`函数`这种格式,它的作用相当于整个函数。 **函数的加载问题**:JS加载的时候,只加载函数名,不加载函数体。所以如果想使用内部的成员变量,需要调用函数。 ### fn() 和 fn 的区别【重要】 - `fn()`:调用函数。调用之后,还获取了函数的返回值。 - `fn`:函数对象。相当于直接获取了整个函数对象。 ## 方法 函数也可以成为对象的属性。**如果一个函数是作为一个对象的属性保存,那么,我们称这个函数是这个对象的方法**。 调用这个函数就说调用对象的方法(method)。函数和方法,有什么本质的区别吗?它只是名称上的区别,并没有其他的区别。 函数举例: ```javascript // 调用函数 fn(); ``` 方法举例: ```javascript // 调用方法 obj.fn(); ``` 我们可以这样说,如果直接是`fn()`,那就说明是函数调用。如果是`XX.fn()`的这种形式,那就说明是**方法**调用。 ## 类数组对象 arguments > 这部分,初学者可能看不懂,可以以后再来看。 在调用函数时,浏览器每次都会传递进两个隐含的参数: - 1.函数的上下文对象 this - 2.**封装实参的对象** arguments 这一段,我们来讲一下 arguments。例如: ```javascript function foo() { console.log(arguments); console.log(typeof arguments); } foo('a', 'b'); ``` 打印结果: ![](https://img.smyhvae.com/20220725_2000.png) ### 定义 函数内的 arguments 是一个**类数组对象**,里面存储的是它接收到的**实参列表**。所有函数都内置了一个 arguments 对象,有个讲究的地方是:只有函数才有arguments。 具体来说,在调用函数时,我们所传递的实参都会在 arguments 中保存。**arguments 代表的是所有实参**。 arguments 的展示形式是一个**伪数组**。意思是,它和数组有点像,但它并不是数组。它具有以下特点: - 可以进行遍历;具有数组的 length 属性,可以获取长度。 - 可以通过索引(从0开始计数)存储数据、获取和操作数据。比如,我们可以通过索引访问某个实参。 - 不能调用数组的方法。比如push()、pop() 等方法都没有。 我们看一下 arguments 的使用。 ### arguments.length 返回函数实参的个数 arguments.length 可以用来获取**实参的个数**。 举例: ```javascript fn(2, 4); fn(2, 4, 6); fn(2, 4, 6, 8); function fn(a, b) { console.log(arguments); console.log(fn.length); //获取形参的个数 console.log(arguments.length); //获取实参的个数 console.log('----------------'); } ``` 打印结果: ![](http://img.smyhvae.com/20180125_2140.png) 此外,即使我们不定义形参,也可以通过 arguments 来获取实参:arguments[0] 表示第一个实参、arguments[1] 表示第二个实参,以此类推。 举例:将传入的实参进行求和,无论实参的个数有多少。代码实现: ```js function foo() { let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } const result = foo(1, 2); console.log(result); ``` ### arguments.callee 返回正在执行的函数 arguments 里边有一个属性叫做 callee,这个属性对应一个函数对象,就是当前正在指向的函数对象。 ```javascript function fun() { console.log(arguments.callee == fun); // 打印结果为true } fun('hello'); ``` 在使用函数**递归**调用时,推荐使用 arguments.callee 代替函数名本身。 ### arguments 可以修改元素 arguments 还可以**修改元素,但不能改变数组的长度**。举例: ```javascript fn(2, 4); fn(2, 4, 6); fn(2, 4, 6, 8); function fn(a, b) { arguments[0] = 99; // 将实参的第一个数改为99 arguments.push(8); // 此方法不通过,因为无法增加元素 } ``` ### 使用场景举例 当我们不确定有多少个参数传递的时候,可以用 **arguments** 来获取。 **举例**:利用 arguments 求函数实参中的最大值。 代码实现: ```javascript function getMaxValue() { var max = arguments[0]; // 通过 arguments 遍历实参 for (var i = 0; i < arguments.length; i++) { if (max < arguments[i]) { max = arguments[i]; } } return max; } console.log(getMaxValue(1, 3, 7, 5)); ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/21-递归函数.md ================================================ --- title: 21-递归函数 --- ## 递归函数 ### 概念 如果一个函数在内部调用这个函数自身,这个函数就是递归函数。 递归在数据结构和算法中经常用到,可以将很多复杂的数据模型拆解为简单问题进行求解。一定要掌握。 ### 递归的要素 - 递归模式:把大问题拆解为小问题进行分析。也称为递归体。 - 边界条件:需要确定递归到何时结束。也称为递归出口。 ### 代码演示:计算阶乘 提问:求一个正整数的阶乘。 **普通写法:** ```js // 函数:计算一个正整数的阶乘 function factorial(n) { let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120 ``` 现在,我们学习了递归函数之后,会有更简洁的写法。 **递归写法:** ```js // 递归函数:计算一个正整数的阶乘 function factorial(n) { // 递归出口:如果计算1的阶乘,就不用递归了 if (n == 1) return 1; // 开始递归:如果当前这个 n 不是1,就返回 n * (n-1)! return n * factorial(n - 1); } console.log(factorial(5)); // 120 ``` ## 递归函数的案例 ### 寻找所有的喇叭花数 题目:喇叭花数是一个**三位数**,其每一位数字的阶乘之和恰好等于它本身,即`abc=a! + b! + c!`,其中abc表示一个三位数。请找出所有的喇叭花数。 思路:将计算某个数字的阶乘封装成函数。 代码实现: ```js // 递归函数:计算一个数的阶乘 function factorial(n) { // 递归出口:如果计算1的阶乘,就不用递归了 if (n == 1) return 1; // 开始递归:如果当前这个 n 不是1,就返回 n * (n-1)! return n * factorial(n - 1); } // 穷举法,从100到999遍历,寻找喇叭花数 for (let i = 100; i <= 999; i++) { // 将数字i转为字符串 const i_str = i.toString(); // abc分别表示百位、十位、个位 const a = Number(i_str[0]); const b = Number(i_str[1]); const c = Number(i_str[2]); // 根据喇叭花数的条件进行判断 if (factorial(a) + factorial(b) + factorial(c) == i) { console.log(i); } } ``` 打印结果: ``` 145 ``` ### 斐波那契数列 斐波那契数列是这样一个数列:1、1、2、3、5、8、13、21、34......最早是由意大利数学家斐波那契开始研究的。它的规律是:下标为0和1的项,值为1;从下标为2的项开始,每一项等于前面两项之和。 提问:请找出斐波那契数列的前10项。 代码实现: ```js // 递归函数:返回斐波那契数列中下标为n的那一项的值 function fib(n) { // 下标为0和1的项,值为1 if (n == 0 || n == 1) return 1; // 从下标为2的项开始,每一项等于前面两项之和 return fib(n - 1) + fib(n - 2); } // 循环语句:打印斐波那契数列的前10项 for (let i = 0; i < 15; i++) { console.log(fib(i)); } ``` ### 小结 关于递归的案例,今后我们还会学习更多的应用场景。比如**深拷贝**就会用到递归。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/22-立即执行函数.md ================================================ --- title: 22-立即执行函数 --- ## 立即执行函数 IIFE ### 概念 函数定义完,就立即被调用,这种函数叫做立即执行函数。英文是 IIFE(Immediately-invoked function expression),立即调用函数表达式。 ### 语法格式 语法1: ```js (function() { // 函数体 })(); ``` 语法2:(立即执行函数也可以传参) ```js (function() { // 函数体 })(a, b); ``` 语法解释: - `function(){}`这种写法,需要再加一对圆括号,变成`` ### 举例 现有匿名函数如下: ```javascript function(a, b) { console.log("a = " + a); console.log("b = " + b); }; ``` 立即执行函数如下: ```javascript (function(a, b) { console.log("a = " + a); console.log("b = " + b); })(123, 456); ``` 立即执行函数往往只会执行一次。为什么呢?因为没有变量保存它,执行完了之后,就找不到它了。 ## IIFE的作用 ### 为变量赋值 当给变量赋值需要一些较复杂的计算时,使用IIFE显得语法更紧凑。 ```js const sex = 'male'; const nickName = (function () { if (sex == 'male') { return '帅哥'; } else { return '美女'; } })(); console.log(nickName); ``` ### 将全局变量变为局部变量 现有如下代码: ```js var arr = []; for (var i = 0; i < 5; i++) { arr.push(function () { console.log(i); }); } arr[2](); // 打印5 ``` 我们知道,上方代码中,i 是全局变量,所有函数共享内存中的同一个变量i。 现在,我们通过立即执行函数进行改造: ```js var arr = []; for (var i = 0; i < 5; i++) { (function (i) { arr.push(function () { console.log(i); }); })(i); } arr[2](); // 打印2 ``` 上方代码中,i作为传递给了IIFE的形参,让 i 得以成为 IIFE 的局部变量;并让 IIFE 并形成了闭包(`arr[2]()`打印出了IIFE内部变量 i 的值,说明形成了闭包)。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/23-作用域、变量提升、函数提升.md ================================================ --- title: 23-作用域、变量提升、函数提升 --- ## 作用域 ### 作用域(Scope)的概念和分类 - **概念**:作用域是一个变量或函数的作用范围。作用域在**函数定义**时,就已经确定了。 - **目的**:为了提高程序的可靠性,同时减少命名冲突。 在 JS 中,一共有两种作用域:(ES5 中) - **全局作用域**:作用于整个 script 标签内部,或者作用于一个独立的 JS 文件。 - **函数作用域**(局部作用域):作用于函数内的代码环境。 ### 全局作用域 和 window 对象 直接编写在 script 标签中的 JS 代码,都在全局作用域。全局作用域在页面打开时创建,在页面关闭时销毁。 在全局作用域中有一个全局对象 window,它代表的是浏览器的窗口,由浏览器创建,我们可以直接使用。相关知识点如下: - 创建的**变量**都会作为 window 对象的属性保存。比如在全局作用域内写 `const a = 100`,这里的 `a` 等价于 `window.a`。 - 创建的**函数**都会作为 window 对象的方法保存。 ### 作用域的访问关系 在内部作用域中可以访问外部作用域的变量,在外部作用域中无法访问到内部作用域的变量。 代码举例: ```javascript const a = 'aaa'; function foo() { const b = 'bbb'; console.log(a); // 打印结果:aaa。说明 内层作用域 可以访问 外层作用域 里的变量 } foo(); console.log(b); // 报错:Uncaught ReferenceError: b is not defined。说明 外层作用域 无法访问 内层作用域 里的变量 ``` ### 变量的作用域 根据作用域的不同,变量可以分为两类:全局变量、局部变量。 **全局变量**: - 在全局作用域下声明的变量,叫「全局变量」。在全局作用域的任何一地方,都可以访问这个变量。 - 在全局作用域下声明的变量是全局变量。 **局部变量**: - 定义在函数作用域的变量,叫「局部变量」。仅限函数内部访问这个变量。 - 函数的**形参**也是属于局部变量。 从执行效率来看全局变量和局部变量: - 全局变量:只有浏览器关闭时才会被销毁,比较占内存。 - 局部变量:当其所在的代码块运行结束后,就会被销毁,比较节约内存。 **特殊情况:** (1)无论是在函数外还是函数内,变量如果未经声明就赋值(意思是,如果不加var/let/const),这个变量是**全局变量**。 比如: ```js // 声明变量时如果不加var/let/const,这个变量是全局变量。且可以被修改。 function fn() { a = 1; } fn(); // 这行代码必须要写,否则下一行代码执行时会报错:Uncaught ReferenceError: a is not defined console.log(a); // 打印结果:1 ``` 当然,我们不建议这么用。 (2)如果局部变量和全局变量重名,则在函数内部,变量是以局部变量为准。 ### 作用域的上下级关系 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(**就近原则**)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错 ReferenceError。 在函数中要访问全局变量可以使用 window 对象。(比如说,全局作用域和函数作用域都定义了变量 a,如果想访问全局变量,可以使用`window.a`) ## 全局作用域的预处理 **预处理(预解析)**的概念:JS在解析代码之前,有一个“预处理(预解析)”阶段,将当前 JS 代码中所有变量的定义和函数的定义,放到所有代码的最前面。 (打个比方,学生在学习文言文之前,会扫读整篇文章,做简单的预习。) 这种预解析,也称之为声明提前。 ### 变量的声明提前(变量提升) 使用 var 关键字声明的变量( 比如 `var a = 1`),**会在所有的代码执行之前被声明**(但是不会赋值)。但是如果声明变量时不是用 var 关键字(比如直接写`a = 1`),则变量不会被声明提前。 **举例 1**: ```javascript console.log(a); var a = 123; ``` 打印结果:undefined。注意,打印结果并没有报错,而是 undefined,说明变量 a 被提前声明了,只是尚未被赋值。 **举例 2**: ```javascript console.log(a); a = 123; //此时a相当于window.a ``` 程序会报错:`Uncaught ReferenceError: a is not defined`。 **举例 3**: ```javascript a = 123; //此时a相当于window.a console.log(a); ``` 打印结果:123。 **举例 4**: ```javascript foo(); function foo() { if (false) { var i = 123; } console.log(i); } ``` 打印结果:undefined。注意,打印结果并没有报错,而是 undefined。这个例子,再次说明了:变量 i 在函数执行前,就被提前声明了,只是尚未被赋值。 例 4 中, `if(false)`里面的代码虽然不会被执行,但是整个代码有**解析**的环节,解析的时候就已经把 变量 i 给提前声明了。 **总结**: 既然在ES5 中存在变量提升的现象,那么,在实战开发中,为了避免出错,建议先声明一个变量,然后再使用这个变量。 ### 函数的声明提前(函数提升) **函数声明**: 使用`函数声明`的形式创建的函数`function foo(){}`,**会被声明提前**。 也就是说,整个函数会在所有的代码执行之前就被**创建完成**。所以,在代码顺序上,我们可以先调用函数,再定义函数。 代码举例: ```javascript fn1(); // 虽然 函数 fn1 的定义是在后面,但是因为被提前声明了, 所以此处可以调用函数 function fn1() { console.log('我是函数 fn1'); } ``` **函数表达式**: 使用`函数表达式`创建的函数`const foo = function(){}`,**不会被声明提前**,所以不能在声明前调用。 很好理解,因为此时只是变量 foo 被提升了,且值为 undefined,并没有把 `function(){}` 赋值给 foo。 所以,下面的例子会报错: ```js // 不会报错,可以正常执行函数,正常打印结果 fun1(); // 此时 fun2 相当于 undefined。执行时会报错:Uncaught ReferenceError: Cannot access 'fun2' before initialization fun2(); // 函数声明,会被提前声明 function fun1() { console.log('我是 fun1 函数'); } // 函数表达式,不会被提前声明 const fun2 = function () { console.log('我是 fun12 函数'); }; ``` ### 函数提升优先于变量提升 在JS的规则中,函数提升优先于变量提升。来看看下面这段代码,你认为打印结果应该如何:(这是一道经典面试题) ```js fun(); // 打印 B // 变量提升 var fun = function () { console.log('A'); }; // 函数提升 function fun() { console.log('B'); } fun(); // 打印 A ``` 打印结果: ``` B A ``` 当声明被提前后,上方代码的实际顺序可以这样理解:(把它当成伪代码理解即可) ```js /*伪代码*/ // 函数提升 function fun() { console.log('B'); } var fun = undefined; fun(); // 打印 B fun = function () { console.log('A'); }; fun(); // 打印A ``` 当然,上方代码是ES5写法,如果把 var 改成ES6中的 let/const,代码会报错`Uncaught SyntaxError: Identifier 'fun' has already been declared`。也就是说,ES6中不需要关心谁优先提升的问题了。 ## 函数作用域的预处理 上一段讲的是全局作用域中的声明提前。在函数作用域中,也有声明提前的现象: - 函数中,使用 var 关键字声明的变量,会在函数中所有代码执行之前被提前声明。 - 函数中,没有 var 声明的变量都是**全局变量**,且并不会被提前声明。 举例: ```javascript var a = 1; function foo() { console.log(a); a = 2; // 此处的a相当于window.a } foo(); console.log(a); //打印结果是2 ``` 上方代码中,执行 foo() 后,函数里面的打印结果是`1`。如果去掉第一行代码,执行 foo() 后,函数里面的打印结果是`Uncaught ReferenceError: a is not defined`。 **补充**:定义形参就相当于在函数作用域中声明了变量。举例如下: ```javascript function fun(e) { // 这个函数中,因为有了形参 e,此时相当于在函数内部的第一行代码里,写了 var e; console.log(e); } fun(); //打印结果为 undefined fun(123); //打印结果为123 ``` ## ES5中没有块级作用域 在其他编程语言中(如 Java、C#等),存在块级作用域,由`{}`包括起来。比如在 Java 语言中,if 语句里创建的变量,只能在 if 语句内部使用: ```java if(true){ int num = 123; system.out.print(num); // 123 } system.out.print(num); // 报错 ``` 但是,在 ES5 中没有块级作用域。举例如下: ```javascript if (true) { var num = 123; console.log(num); //123 } console.log(num); //123(可以正常打印) ``` ## 作用域链 先来认识函数的嵌套: - 只要是代码,就至少有一个作用域 - 函数内部有局部作用域 - 如果函数内部还嵌套了函数,那么在这个作用域中就又诞生了另一个作用域。 基于上面几条内容,我们可以得出作用域链的概念。 **作用域链**:在嵌套函数中,变量会从内到外逐层寻找它的定义(查找时,采用**就近原则**)。也就是说,采用的是链式查找的方式来决定取哪个值,这种结构称之为作用域链。 代码举例: ```javascript var num = 10; function fn() { // 外部函数 var num = 20; function fun() { // 内部函数 console.log(num); } fun(); } fn(); ``` 打印结果:20。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/24-预编译.md ================================================ --- title: 24-预编译 --- > 我们在上一篇文章《作用域》中简单讲过“变量提升”,今天来讲一下预编译,这对我们深入理解变量提升会有帮助。 ## JavaScript 运行三部曲 - 语法分析 - 预编译 - 解释执行 ## 预编译前奏 > 在讲预编译前,我们先来普及下面两个规律。 ### 两个规律 **规律1:任何变量,如果未经声明就赋值,此变量是属于 window 的属性**,而且不会做变量提升。(注意,无论在哪个作用域内赋值) 比如说,如果我们直接在代码里写 `console.log(a)`,这肯定会报错的,提示找不到 `a`。但如果我直接写 `a = 100`,这就不会报错,此时,这个 `a` 就是 `window.a`。 **规律2:一切声明的全局变量,全是window的属性**。(注意,这里说的是在全局作用域内声明的全局变量,不是说局部变量) 比如,当定义 `var a = 200` 时,这个 `a` 就是 `window.a`。 由此,我们可以看出:**window 代表了全局作用域**(是说「代表」,没说「等于」)。 ### 举例 掌握了上面两句话之后,我们再来看看下面的例子。 ```javascript function foo() { var a = b = 100; // 连续赋值 } foo(); console.log(window.b); // 在全局范围内访问 b console.log(b); // 在全局范围内访问 b,但是前面没有加 window 这个关键字 console.log(window.a); // 在全局范围内访问 a console.log(a); // 在全局范围内访问 a,但是前面没有加 window 这个关键字 ``` 上方代码的打印结果: ``` 100 100 undefined (会报错,提示 Uncaught ReferenceError: a is not defined) ``` **解释**: 当执行了`foo()`函数之后, `var a = b = 100` 这行**连续赋值**的代码等价于 `var a = (b = 100)`,其执行顺序是: (1)先把 100 赋值给 b; (2)再声明变量 a; (3)再把 b 的值赋值给 a。 我们可以看到,b 是未经声明的变量就被赋值了,此时,根据规律1,这个 b 是属于 `window.b`;而 a 的作用域仅限于 foo() 函数内部,不属于 window。所以也就有了这样的打印结果。 ## 预编译 ### 函数预编译的步骤 > 函数预编译,发生在函数执行的前一刻。 (1)创建AO对象。AO即 Activation Object 活跃对象,其实就是「执行期上下文」。 (2)找形参和变量声明,将形参名和变量作为 AO 的属性名,值为undefined。 (3)将实参值和形参统一,实参的值赋给形参。 (4)查找函数声明,函数名作为 AO 对象的属性名,值为整个函数体。 这个地方比较难理解。但只有了解了函数的预编译,才能理解明白函数的执行顺序。 代码举例: ```javascript function fn(a) { console.log(a); var a = 666; console.log(a); function a() {} console.log(a); var b = function() {}; console.log(b); function c() {} } fn(1); ``` 打印结果: ``` ƒ a() {} 666 666 ƒ () {} ``` ## 参考链接 - JavaScript预编译原理分析: - - 预编译及变量提升: - - - 宏任务&微任务相关: ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/25-this指向.md ================================================ --- title: 25-this指向 --- ## 执行期上下文 当**函数执行**时(准确来说,是在函数发生预编译的前一刻),会创建一个执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境。 每调用一次函数,就会创建一个新的上下文对象,他们之间是相互独立且独一无二的。当函数执行完毕,它所产生的执行期上下文会被销毁。 参考链接: ## this 指向 解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是 this,this 指向的是一个对象,这个对象我们称为函数执行的 上下文对象。 ### ES5 函数内 this 的指向【非常重要】 我们在《JavaScript 基础/函数.md》这篇文章讲过,函数的调用有**六种**形式。 在ES5语法中,根据函数的调用方式的不同,this 会指向不同的对象: 1、以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。比如`fun();`相当于`window.fun();` 2、以方法的形式调用时,this 指向调用方法的那个对象 3、以构造函数的形式调用时,this 指向实例对象 4、以事件绑定函数的形式调用时,this 指向**绑定事件的对象** 5、使用 call 和 apply 调用时,this 指向指定的那个对象 **第 1 条的举例**: ```javascript function fun() { console.log(this); console.log(this.name); } var obj1 = { name: 'smyh', sayName: fun, }; var obj2 = { name: 'vae', sayName: fun, }; var name = '全局的name属性'; //以函数形式调用,this是window fun(); //可以理解成 window.fun() ``` 打印结果: ``` Window 全局的name属性 ``` 上面的举例可以看出,this 指向的是 window 对象,所以 this.name 指的是全局的 name。 **第 2 条的举例**: ```javascript function fun() { console.log(this); console.log(this.name); } var obj1 = { name: 'smyh', sayName: fun, }; var obj2 = { name: 'vae', sayName: fun, }; var name = '全局的name属性'; //以方法的形式调用,this是调用方法的对象 obj2.sayName(); ``` 打印结果: ``` Object vae ``` 上面的举例可以看出,this 指向的是 对象 obj2 ,所以 this.name 指的是 obj2.name。 ### ES6 箭头函数中 this 的指向 ES6 中的箭头函数并不使用上面的准则,而是会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。 ### 改变函数内部的 this 指向 JS 专门为我们提供了一些方法来改变函数内部的 this 指向。常见的方法有 call()、apply()、bind() 方法。继续往下看。 ## call() ### call() 方法的作用 call() 方法的作用:可以**调用**一个函数,与此同时,它还可以改变这个函数内部的 this 指向。 call() 方法的另一个应用:**可以实现继承**。之所以能实现继承,其实是利用了上面的作用。 语法: ```js fn1.call(想要将this指向哪里, 函数实参1, 函数实参2); ``` 备注:第一个参数中,如果不需要改变 this 指向,则传 null。 ### call() 方法举例 **举例 1**、通过 call() 调用函数: ```js const obj1 = { nickName: 'qianguyihao', age: 28, }; function fn1() { console.log(this); console.log(this.nickName); } fn1.call(this); // this的指向并没有被改变,此时相当于 fn1(); ``` 上方代码的打印结果: ``` window undefined ``` 上面的代码,跟普通的函数调用 `fn1()` 没有区别。 **举例 2**、通过 call() 改变 this 指向: ```js var obj1 = { nickName: 'qianguyihao', age: 28, }; function fn1(a, b) { console.log(this); console.log(this.nickName); console.log(a + b); } fn1.call(obj1, 2, 4); // 先将 this 指向 obj1,然后执行 fn1() 函数 ``` 上方代码的打印结果: ``` obj1 qianguyihao 6 ``` **举例 3**、通过 call() 实现继承: ```js // 给 Father 增加 name 和 age 属性 function Father(myName, myAge) { this.name = myName; this.age = myAge; } function Son(myName, myAge) { // 【下面这一行,重要代码】 // 通过这一步,将 father 里面的 this 修改为 Son 里面的 this;另外,给 Son 加上相应的参数,让 Son 自动拥有 Father 里的属性。最终实现继承 Father.call(this, myName, myAge); } const son1 = new Son('千古壹号', 28); console.log(JSON.stringify(son1)); ``` 上方代码中,通过 call() 方法,让 Son 继承了 Father 里面的 name 和 age 属性。 打印结果: ``` {"myName":"千古壹号","myAge":28} ``` ## apply() 方法 ### apply() 方法的作用 apply() 方法的作用:可以**调用**一个函数,与此同时,它还可以改变这个函数内部的 this 指向。这一点,和 call()类似。 apply() 方法的应用: 由于 apply()需要传递**数组**,所以它有一些巧妙应用,稍后看接下来的应用举例就知道了。 语法: ```js fn1.apply(想要将this指向哪里, [函数实参1, 函数实参2]); ``` 备注:第一个参数中,如果不需要改变 this 指向,则传 null。 到这里可以看出, call() 和 apply() 方法的作用是相同的。唯一的区别在于,apply() 里面传入的**实参,必须是数组(或者伪数组)**。 ### apply() 方法举例 **举例**、通过 apply() 改变 this 指向: ```js var obj1 = { nickName: 'qianguyihao', age: 28, }; function fn1(a) { console.log(this); console.log(this.nickName); console.log(a); } fn1.apply(obj1, ['hello']); // 先将 this 指向 obj1,然后执行 fn1() 函数 ``` 注意,上方代码中,apply() 里面传实参时,需要以数组的形式。即便是传一个实参,也需要传数组。 打印结果: ``` obj1 qianguyihao hello ``` ### apply() 方法的巧妙应用:求数组的最大值 我们知道,如果想要求数组中元素的最大值,数组本身是没有自带方法的。那怎么办呢? 虽然数组里没有获取最大值的方法,但是数值里有 `Math.max(数字1,数字2,数字3)` 方法,可以获取**多个数值中的最大值**。 另外,由于 apply() 方法在传递实参时,传的刚好是**数组**,所以我们可以 通过 Math.max() 和 apply() 曲线救国。 **举例**:求数组中多个元素的最大值: ```js const arr1 = [3, 7, 10, 8]; // 下面这一行代码的目的,无需改变 this 指向,所以:第一个参数填 null,或者填 Math,或者填 this 都可以。严格模式中,不让填null。 const maxValue = Math.max.apply(Math, arr1); // 求数组 arr1 中元素的最大值 console.log(maxValue); const minValue = Math.min.apply(Math, arr1); // 求数组 arr1 中元素的最小值 console.log(minValue); ``` 打印结果: ``` 10 3 ``` ## bind() 方法 ### bind() 方法的作用 bind() 方法**不会调用函数**,但是可以改变函数内部的 this 指向。 把call()、apply()、bind()这三个方法做一下对比,你会发现:实际开发中, bind() 方法使用得最为频繁。如果有些函数,我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用 bind() 是最为合适的。 语法: ```js 新函数 = fn1.bind(想要将this指向哪里, 函数实参1, 函数实参2); ``` 参数: - 第一个参数:在 fn1 函数运行时,指定 fn1 函数的this 指向。如果不需要改变 this 指向,则传 null。 - 其他参数:fn1 函数的实参。 解释:它不会调用 fn1 函数,但会返回 由指定this 和指定实参的**原函数拷贝**。可以看出, bind() 方法是有返回值的。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/26-闭包.md ================================================ --- title: 26-闭包 --- ## 闭包的引入 我们知道,变量根据作用域的不同分为两种:全局变量和局部变量。 - 函数内部可以访问全局变量和局部变量。 - 函数外部只能访问全局变量,不能访问局部变量。 - 当函数执行完毕,本作用域内的局部变量会销毁。 比如下面这样的代码: ```js function foo() { let a = 1; } foo(); console.log(a); // 打印报错:Uncaught ReferenceError: a is not defined ``` 上方代码中,由于变量 `a` 是函数内的局部变量,所以外部无法访问。 但是,在有些场景下,我们就是想要在函数外部访问**函数内部作用域的局部变量**,那要怎么办呢?这就引入了闭包的概念。 ## 什么是闭包 ### 闭包(closure)的概念 **闭包**:如果**外部作用域**有权访问另外一个**函数内部**的**局部变量**时,那就产生了闭包。这个内部函数称之为闭包函数。注意,这里强调的是访问**局部变量**。 闭包代码举例: ```js function fun1() { const a = 10; return function fun2() { console.log(a); }; } fun1(); // 调用外部函数,就能得到内部函数,并用 变量 result 接收 const result = fun1(); // 在 fun1函数的外部,执行了内部函数 fun2,并访问到了 fun2的内部变量a result(); // 10 ``` 打印结果: ``` 10 ``` 上方代码中,外部作用域(即全局作用域) 访问了函数 fun1 中的局部变量,那么,在 fun1 中就产生了闭包,函数 fun1是闭包函数。 全局作用域中,并没有定义变量a。正常情况下作为函数内的局部变量 a,无法被外部访问到。但是通过闭包,我们最后还是可以在全局作用域中拿到局部变量 a 的值。 注意,闭包函数是fun1,不是fun2。fun2在这里的作用是让全局作用域访问到变量a,fun2只是一个桥梁。 ### 闭包的生命周期 1. 产生:内部函数fn1被声明时(即被创建时,不是被调用时)就产生了。 2. 死亡:嵌套的内部函数成为垃圾对象时。(比如fun1 = null,就可以让 fun1 成为垃圾对象) ### 在 chrome 浏览器控制台中,调试闭包 上面的代码中,要怎么验证,确实产生了闭包呢?我们可以在 chrome 浏览器的控制台中设置断点,当代码执行到 `console.log(a)`这一行的时候,如下图所示: ![](http://img.smyhvae.com/20200703_2055.png) 上图中, Local 指的是局部作用域,Global 指的是全局作用域;而 Closure 则是**闭包**,fn1 是一个闭包函数。 ## 闭包的表现形式 ### 形式1:将一个函数作为另一个函数的返回值 ```javascript function fn1() { var a = 2 function fn2() { a++ console.log(a) } return fn2 } var f = fn1(); //执行外部函数fn1,返回的是内部函数fn2 f() // 3 //执行fn2 f() // 4 //再次执行fn2 ``` 当f()第二次执行的时候,a加1了,也就说明了:闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。 上面的代码中,虽然调用了内部函数两次,但是,闭包对象只创建了一个。 也就是说,要看闭包对象创建了几个,就看:**外部函数执行了几次**(与内部函数执行几次无关)。 ### 形式2:将函数作为实参传递给另一个函数调用 在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。 ```javascript function showDelay(msg, time) { setTimeout(function() { //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg alert(msg) }, time) } showDelay('qianguyihao', 2000) ``` 上面的代码中,闭包是里面的function,因为它是嵌套的子函数,而且引用了外部函数的变量msg。 ## 闭包的作用 - 作用1:延长局部变量的生命周期。 - 作用2:让函数外部可以操作(读写)函数内部的数据(变量/函数)。 代码演示: ```javascript function fun1() { let a = 2 function fun2() { a++ console.log(a) } return fun2; } const foo = fun1(); //执行外部函数fn1,返回的是内部函数fn2 foo() // 3 //执行fun2 foo() // 4 //再次执行fun2 ``` 上方代码中,foo 代表的就是整个 fun2 函数。当执行了 `foo()` 语句之后,也就执行了fun2()函数,fun1() 函数内就产生了闭包。 **作用1分析**: 一般来说,在 fn1() 函数执行完毕后,它里面的变量 a 会立即销毁。但此时由于产生了闭包,所以 **fun1 函数中的变量 a 不会立即销毁,仍然保留在内存中,因为 fn2 函数还要继续调用变量 a**。只有等所有函数把变量 a 调用完了,变量 a 才会销毁。 **作用2分析:** 在执行 `foo()`语句之后,竟然能够打印出 `3`,这就完美通过闭包实现了:全局作用域成功访问到了局部作用域中的变量 a。 达到的效果是:**外界看不到变量a,但可以操作a**。当然,如果你真想看到a,可以在fun2中将a返回即可。 ## 闭包的应用场景 ### 场景1:高阶函数 题目:不同的班级有不同成绩检测标准。比如:A班的合格线是60分,B 班的合格线是70分。已知某个人班级和分数,请用闭包函数判断他的成绩是否合格。 思路:创建成绩检测函数 checkStandard(n),检查成绩 n 是否合格,函数返回布尔值。 代码实现: ```js // 高阶函数:判断学生的分数是否合格。形参 standardTemp 为标准线 function createCheckTemp(standardTemp) { // 形参 n 表示具体学生的分数 function checkTemp(n) { if (n >= standardTemp) { alert('成绩合格'); } else { alert('成绩不合格'); } } return checkTemp; } // 创建一个 checkStandard_A 函数,它以60分为合格线 var checkStandard_A = createCheckTemp(60); // 再创建一个 checkStandard_B 函数,它以70分为合格线 var checkStandard_B = createCheckTemp(70); // 调用函数 checkStandard_A(65); // 成绩合格 checkStandard_B(65); // 成绩不合格 ``` 对于A班来说,它的闭包函数是createCheckTemp(),闭包范围是 checkTemp()函数和参数`standardTemp = 60`。对于B班来说,它的闭包函数是全新的createCheckTemp(),闭包范围是全新的checkTemp()函数和全新的参数`standardTemp = 70`。 因为有闭包存在,所以,并不会因为 createCheckTemp() 执行完毕后就销毁 standardTemp 的值;且A班和B班的standardTemp参数不会混淆。 备注:关于“高阶函数”的更多解释,我们在以后的内容中讲解。 ### 场景2:封装JS模块 闭包的第二个使用场景是:定义具有特定功能的JS模块,将所有的数据和功能都封装在一个函数内部,只向外暴露指定的对象或方法。模块的调用者,只能调用模块暴露的对象或方法来实现对应的功能。 比如有这样一个需求:定义一个私有变量a,要求a只能被进行指定操作(加减),不能进行其他操作(乘除)。在 Java、C++ 等语言中,有私有属性的概念,但在JS中只能通过闭包模拟。 我们来看看下面的代码,如何通过闭包封装JS模块。 写法1: (1)myModule.js:(定义一个模块,向外暴露多个方法,供外界调用) ```javascript function myModule() { //私有数据 var msg = 'Qinguyihao Haha' //操作私有数据的函数 function doSomething() { console.log('doSomething() ' + msg.toUpperCase()); //字符串大写 } function doOtherthing() { console.log('doOtherthing() ' + msg.toLowerCase()) //字符串小写 } //通过【对象字面量】的形式进行包裹,向外暴露多个函数 return { doSomething1: doSomething, doOtherthing2: doOtherthing } } ``` 上方代码中,外界只能通过doSomething1和doOtherthing2来操作里面的数据,但不让外界看到里面的具体实现。 (2)index.html: ```html 闭包的应用-自定义JS模块 ``` 写法2: 同样是实现上面的功能,我们还采取另外一种写法,写起来更方便。如下: (1)myModule2.js:(是一个立即执行的匿名函数) ```javascript (function () { //私有数据 var msg = 'Qinguyihao Haha' //操作私有数据的函数 function doSomething() { console.log('doSomething() ' + msg.toUpperCase()) } function doOtherthing() { console.log('doOtherthing() ' + msg.toLowerCase()) } //外部函数是即使运行的匿名函数,我们可以把两个方法直接传给window对象 window.myModule = { doSomething1: doSomething, doOtherthing2: doOtherthing } })() ``` (2)index.html: ```html 闭包的应用-自定义JS模块 ``` 上方两个文件中,我们在`myModule2.js`里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。 **小结:** 写法1和写法2都采用了闭包。 ## 内存溢出和内存泄露 > 我们再讲两个概念,这两个概念和闭包有些联系。 ### 内存泄漏 **内存泄漏**:**占用的内存**没有及时释放。 内存泄露的次数积累多了,就容易导致内存溢出。 **常见的内存泄露**: 1、意外的全局变量 2、没有及时清理的计时器或回调函数 3、闭包 情况1举例: ```javascript // 意外的全局变量 function fn() { a = new Array(10000000); console.log(a); } fn(); ``` 情况2举例: ```javascript // 没有及时清理的计时器或回调函数 var intervalId = setInterval(function () { //启动循环定时器后不清理 console.log('----') }, 1000) // clearInterval(intervalId); //清理定时器 ``` 情况3举例: ```js function fn1() { var a = 4; function fn2() { console.log(++a) } return fn2 } var f = fn1() f() // f = null //让内部函数成为垃圾对象-->回收闭包 ``` ### 内存溢出 **内存溢出**:程序运行时出现的错误。当程序运行**需要的内存**超过**剩余的内存**时,就抛出内存溢出的错误。 代码举例: ```javascript var obj = {}; for (var i = 0; i < 10000; i++) { obj[i] = new Array(10000000); //把所有的数组内容都放到obj里保存,导致obj占用了很大的内存空间 console.log("-----"); } ``` ### 闭包是否会造成内存泄漏 一般来说,答案是否定的。因为内存泄漏是非预期情况,本来想回收,但实际没回收;而闭包是预期情况,一般不会造成内存泄漏。 但如果因代码质量不高,滥用闭包,也会造成内存泄漏。 ## 闭包面试题 代码举例: ```js function addCount() { let count = 0; return function () { count = count + 1; console.log(count); }; } const fun1 = addCount(); const fun2 = addCount(); fun1(); fun2(); fun1(); fun2(); ``` 打印结果: ``` 1 1 2 2 ``` 代码解释: (1)fun1 和 fun2 这两个闭包函数是互不影响的,因此第一次调用时,count变量都是0,最终各自都输出1。 (2)第二次调用时,由于闭包有记忆性,所以各自会在上一次的结果上再加1,因此输出2。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/27-面向对象简介.md ================================================ --- title: 27-面向对象简介 --- ## 面向过程和面向对象 ### 面向过程 **面向过程**:先分析好的具体步骤,然后按照步骤,一步步解决问题。 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 缺点:没有面向对象易维护、易复用、易扩展。 ### 面向对象 **面向对象**(OOP,Object Oriented Programming):以对象功能来划分问题,而不是步骤。 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护。 缺点:性能比面向过程低。 ### 面向对象的编程思想 面向对象的编程思想:对代码和数据进行封装,并以对象调用的方式,对外提供统一的调用接口。 比如说,当我们在开车的时候,无需关心汽车的内部构造有多复杂,对于大多数人而言,只需要会开、知道汽车有哪些功能就行了。 ### 面向对象的特性 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。面向对象编程具有灵活、代码可复用、容易维护和开发的优点,适合多人合作的大型软件项目,更符合我们认识事物的规律。 面向对象的特性如下: - 封装性 - 继承性 - 多态性 ### JS 中的面向对象 JS 中的面向对象,是基于**原型**的面向对象。JS 中的对象(Object)是依靠构造器(constructor)和原型(prototype)构造出来的。 另外,在ES6中,新引入了 类(Class)和继承(Extends)来实现面向对象。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/28-对象的创建&构造函数.md ================================================ --- title: 28-对象的创建&构造函数 --- > 在看本文之前,可以先复习前面的一篇文章:《04-JavaScript 基础/11-对象简介.md》 ## 创建自定义对象的几种方法 ### 方式一:对象字面量 **对象的字面量**就是一个{}。里面的属性和方法均是**键值对**: - 键:相当于属性名。 - 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。 使用对象字面量来创建一个对象,非常简洁,举例如下:: ```javascript var obj = {}; ``` 使用对象字面量,可以在创建对象时,直接指定对象中的属性。语法:{属性名:属性值,属性名:属性值....} 例 1:(一个简单的对象) ```js const obj1 = { name: '千古壹号', age: 28 }; ``` 例 2:(一个较复杂的对象) ```javascript const obj2 = { name: "千古壹号", age: 26, isBoy: true, // 还可以存放一个嵌套的对象 test: { id: 123, tel: 180 } //我们还可以在对象中增加一个方法。以后可以通过obj2.sayName()的方式调用这个方法 sayName: function() { console.log(this.name); } }; console.log(JSON.stringify(obj2)); obj2.sayName(); ``` 对象字面量的属性名可以加引号也可以不加,建议不加。如果要使用一些特殊的名字,则必须加引号。 属性名和属性值是一组一组的键值对结构,键和值之间使用`:`连接,多个值对之间使用`,`隔开。 ### 方式二:工厂模式 new Object() 通过该方法可以大批量的创建对象。 ```javascript /* * 使用工厂方法创建对象 * 通过该方法可以大批量的创建对象 */ function createPerson(name, age, gender) { //创建一个新的对象 var obj = new Object(); //向对象中添加属性 obj.name = name; obj.age = age; obj.gender = gender; obj.sayName = function () { alert(this.name); }; //将新的对象返回 return obj; } var obj2 = createPerson('猪八戒', 28, '男'); var obj3 = createPerson('白骨精', 16, '女'); var obj4 = createPerson('蜘蛛精', 18, '女'); ``` 第一次看到这种工厂模式时,你可能会觉得陌生。如果简化一下,可以写成下面这种形式,更容易理解:(也就是,利用 new Object 创建对象) ```javascript var obj = new Obejct(); obj.name = '猪八戒'; obj.age = 28; obj.gender = '男'; obj.sayHi = function () { alert('hello world'); }; ``` **弊端:** 使用工厂方法创建的对象,使用的构造函数都是 Object。**所以创建的对象都是 Object 这个类型,就导致我们无法区分出多种不同类型的对象**。 ### 方式三:利用构造函数 ```javascript //利用构造函数自定义对象 var stu1 = new Student('smyh'); console.log(stu1); stu1.sayHi(); var stu2 = new Student('vae'); console.log(stu2); stu2.sayHi(); // 创建一个构造函数 function Student(name) { this.name = name; //this指的是当前对象实例【重要】 this.sayHi = function () { console.log(this.name + '厉害了'); }; } ``` 打印结果: ![](http://img.smyhvae.com/20180125_1350.png) 接下来,我们专门来讲一下构造函数。 ## 构造函数 ### 代码引入 ```javascript // 创建一个构造函数,专门用来创建Person对象 function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayName = function () { alert(this.name); }; } var per = new Person('孙悟空', 18, '男'); var per2 = new Person('玉兔精', 16, '女'); var per3 = new Person('奔波霸', 38, '男'); // 创建一个构造函数,专门用来创建 Dog 对象 function Dog() {} var dog = new Dog(); ``` ### 构造函数的概念 **构造函数**:是一种特殊的函数,主要用来创建和初始化对象,也就是为对象的成员变量赋初始值。它与 `new` 一起使用才有意义。 我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个构造函数里面。 ### 构造函数和普通函数的区别 构造函数的创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。 构造函数和普通函数的区别就是**调用方式**的不同:普通函数是直接调用,而构造函数需要使用 new 关键字来调用。 **this 的指向也有所不同**: - 1.以函数的形式调用时,this 永远都是 window。比如`fun();`相当于`window.fun();` - 2.以方法的形式调用时,this 是调用方法的那个对象 - 3.以构造函数的形式调用时,this 是新创建的实例对象 ### new 一个构造函数的执行流程 new 在执行时,会做下面这四件事: (1)开辟内存空间,在内存中创建一个新的空对象。 (2)让 this 指向这个新的对象。 (3)执行构造函数里面的代码,给这个新对象添加属性和方法。 (4)返回这个新对象(所以构造函数里面不需要 return)。 因为 this 指的是 new 一个 Object 之后的对象实例。于是,下面这段代码: ```javascript // 创建一个函数 function createStudent(name) { var student = new Object(); student.name = name; //第一个name指的是student对象定义的变量。第二个name指的是createStudent函数的参数。二者不一样 } ``` 可以改进为: ```javascript // 创建一个函数 function Student(name) { this.name = name; //this指的是构造函数中的对象实例 } ``` 注意上方代码中的注释。 ### 静态成员和实例成员 JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。 - 静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问。 - 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问。 ### 类、实例 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个**类**。 通过一个构造函数创建的对象,称为该类的**实例**。 ### instanceof 使用 instanceof 可以检查**一个对象是否为一个类的实例**。 **语法如下**: ```javascript 对象 instanceof 构造函数; ``` 如果是,则返回 true;否则返回 false。 **代码举例**: ```javascript function Person() {} function Dog() {} var person1 = new Person(); var dog1 = new Dog(); console.log(person1 instanceof Person); // 打印结果: true console.log(dog1 instanceof Person); // 打印结果:false console.log(dog1 instanceof Object); // 所有的对象都是Object的后代。因此,打印结果为:true ``` 根据上方代码中的最后一行,需要补充一点:**所有的对象都是 Object 的后代,因此 `任何对象 instanceof Object` 的返回结果都是 true**。 ## others ### json 的介绍 > 对象字面量和 json 比较像,这里我们对 json 做一个简单介绍。 JSON:JavaScript Object Notation(JavaScript 对象表示形式)。 JSON 和对象字面量的区别:JSON 的属性必须用双引号引号引起来,对象字面量可以省略。 json 举例: ``` { "name" : "zs", "age" : 18, "sex" : true, "sayHi" : function() { console.log(this.name); } }; ``` 注:json 里一般放常量、数组、对象等,但很少放 function。 另外,对象和 json 没有长度,json.length 的打印结果是 undefined。于是乎,自然也就不能用 for 循环遍历(因为遍历时需要获取长度 length)。 **json 遍历的方法:** json 采用 `for...in...`进行遍历,和数组的遍历方式不同。如下: ```html ``` 打印结果: ![](http://img.smyhvae.com/20180203_1518.png) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/29-对象的基本操作.md ================================================ --- title: 29-对象的基本操作 --- ## 对象的基本操作 ### 创建对象 使用 new 关键字调用的函数,是构造函数 constructor。**构造函数是专门用来创建对象的函数**。 例如: ```javascript const obj = new Object(); ``` 记住,使用`typeof`检查一个对象时,会返回`object`。 关于创建对象的更多方式,可以看上一篇文章《对象的创建&构造函数》。 ### 向对象中添加属性 在对象中保存的值称为属性。 向对象添加属性的语法: ```javascript 对象.属性名 = 属性值; ``` 举例: ```javascript const obj = new Object(); //向obj中添加一个name属性 obj.name = '孙悟空'; //向obj中添加一个gender属性 obj.gender = '男'; //向obj中添加一个age属性 obj.age = 18; console.log(JSON.stringify(obj)); // 将 obj 以字符串的形式打印出来 ``` 打印结果: ``` { "name":"孙悟空", "gender":"男", "age":18 } ``` 这里我们也可以看出一个规律:如果对象里本身没有某个属性,则用点语法赋值时,这个属性会被创建出来。 ### 获取对象中的属性 **方式 1**: 语法: ```javascript 对象.属性名; ``` 如果获取对象中没有的属性,不会报错而是返回`undefined`。 举例: ```javascript const obj = new Object(); //向obj中添加一个name属性 obj.name = '孙悟空'; //向obj中添加一个gender属性 obj.gender = '男'; //向obj中添加一个age属性 obj.age = 18; // 获取对象中的属性,并打印出来 console.log(obj.gender); // 打印结果:男 console.log(obj.color); // 打印结果:undefined ``` **方式 2**:可以使用`[]`这种形式去操作属性 如果属性名的命名规范没有遵循标识符的命名规范,就不能采用`.`的方式来操作对象的属性,则必须用方括号的形式来访问。比如说,`123`这种属性名,如果我们直接写成`obj.123 = 789`来操作属性,是会报错的。那怎么办呢?办法如下: 语法格式如下:(读取时,也是采用这种方式) ```javascript // 注意,括号里的属性名,必须要加引号 // 获取属性 对象['属性名'] // 设置属性值 对象['属性名'] = 属性值; ``` 上面这种语法格式,举例如下: ```javascript obj['123'] = 789; ``` 当然,如果属性名遵循了标识符的命名规范,也可以使用方括号操作属性。 **重要**:使用`[]`这种形式去操作属性会更灵活,因为我们可以在`[]`中传递一个**变量**。也就是说,如果属性名以变量的形式存储,请记得也必须使用方括号的形式操作属性。这在日常开发中,使用得非常多。比如: ```js const person = { name: '千古壹号', age: 30 } const myKey = 'name'; // 错误的访问方式 console.log(obj.myKey); // undefined // 正确的访问方式 console.log(obj[myKey]); // 千古壹号 ``` ### 修改对象的属性值 语法: ```javascript 对象.属性名 = 新值; ``` 举例: ```javascript obj.name = 'qiangu yihao'; ``` ### 删除对象的属性 语法: ```javascript delete obj.name; ``` ### in 运算符 通过该运算符可以检查一个对象中是否含有指定的属性。如果有则返回 true,没有则返回 false。 语法: ```javascript '属性名' in 对象; ``` 举例: ```javascript //检查对象 obj 中是否含有name属性 console.log('name' in obj); ``` 我们平时使用的对象不一定是自己创建的,可能是从接口获取的,这个时候,in 运算符可以派上用场。 当然,还有一种写法可以达到上述目的: ```js if (obj.name) { // 如果对象 obj 中有name属性,我就继续做某某事情。 } ``` ## for of:遍历数组 ES6 中,如果我们要遍历一个数组,可以这样做: ```js let arr1 = [2, 6, 8, 5]; for (let value of arr1) { console.log(value); } ``` 打印结果: ``` 2 6 8 5 ``` for ... of 的循环可以避免我们开拓内存空间,增加代码运行效率,所以建议大家在以后的工作中使用 for…of 遍历数组。 注意,上面的数组中,`for ... of`获取的是数组里的值;如果采用`for ... in`遍历数组,则获取的是 index 索引值。 ### Map 对象的遍历 `for ... of`既可以遍历数组,也可以遍历 Map 对象。 ## for in:遍历对象的属性 > `for ... in`主要用于遍历对象,不建议用来遍历数组。 语法: ```javascript for (const 变量 in 对象) { } ``` 解释:对象中有几个属性,循环体就会执行几次。每次执行时,会将对象中的**每个属性的 属性名 赋值给变量**。 语法举例: ```javascript for (var key in obj) { console.log(key); // 这里的 key 是:对象属性的键(也就是属性名) console.log(obj[key]); // 这里的 obj[key] 是:对象属性的值(也就是属性值) } ``` 举例: ```html ``` 打印结果: ``` 属性名:name 属性值:qianguyihao 属性名:age 属性值:26 属性名:gender 属性值:男 属性名:address 属性值:shenzhen 属性名:sayHi 属性值:function() { console.log(this.name); } ``` ### for in 遍历数组(不建议) 另外,for in 当然也可以用来遍历数组(只是不建议),此时的 key 是数组的索引。举例如下: ```js const arr = ['hello1', 'hello2', 'hello3']; for (const key in arr) { console.log('属性名:' + key); console.log('属性值:' + arr[key]); } ``` 打印结果: ``` 属性名:0 属性值:hello1 属性名:1 属性值:hello2 属性名:2 属性值:hello3 ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/30-浅拷贝和深拷贝.md ================================================ --- title: 30-浅拷贝和深拷贝 --- ## 概念 - 浅拷贝:只拷贝最外面一层的数据;更深层次的对象,只拷贝引用。 - 深拷贝:拷贝多层数据;每一层级别的数据都会拷贝。 **总结**: 拷贝引用的时候,是属于**传址**,而非**传值**。关于传值和传址的区别,是很基础的内容,详见《JavaScript 基础/对象简介.md》这篇文章。 深拷贝会把对象里**所有的数据**重新复制到新的内存空间,是最彻底的拷贝。 ## 浅拷贝的实现方式 ### 用 for in 实现浅拷贝(比较繁琐) ```js const obj1 = { name: 'qianguyihao', age: 28, info: { desc: '很厉害', }, }; const obj2 = {}; // 用 for in 将 obj1 的值拷贝给 obj2 for (let key in obj1) { obj2[key] = obj1[key]; } console.log('obj2:' + JSON.stringify(obj2)); obj1.info.desc = '永不止步'; // 当修改 obj1 的第二层数据时,obj2的值也会被改变。所以 for in 是浅拷贝 console.log('obj2:' + JSON.stringify(obj2)); ``` 上方代码中,用 for in 做拷贝时,只能做到浅拷贝。也就是说,在 obj2 中, name 和 age 这两个属性会单独存放在新的内存地址中,和 obj1 没有关系。但是,`obj2.info` 属性,跟 `obj1.info`属性,**它俩指向的是同一个堆内存地址**。所以,当我修改 `obj1.info` 里的值之后,`obj2.info`的值也会被修改。 打印结果如下: ``` obj2:{"name":"qianguyihao","age":28,"info":{"desc":"很厉害"}} obj2:{"name":"qianguyihao","age":28,"info":{"desc":"永不止步"}} ``` ### 用 Object.assgin() 实现浅拷贝(推荐的方式) 上面的 for in 方法做浅拷贝过于繁琐。ES6 给我们提供了新的语法糖,通过 `Object.assgin()` 可以实现**浅拷贝**。 `Object.assgin()` 在日常开发中,使用得相当频繁,非掌握不可。 **语法**: ```js // 语法1 obj2 = Object.assgin(obj2, obj1); // 语法2 Object.assign(目标对象, 源对象1, 源对象2...); ``` **解释**:将`obj1` 拷贝给 `obj2`。执行完毕后,obj2 的值会被更新。 **作用**:将 obj1 的值追加到 obj2 中。如果对象里的属性名相同,会被覆盖。 从语法2中可以看出,Object.assign() 可以将多个“源对象”拷贝到“目标对象”中。 **例 1**: ```js const obj1 = { name: 'qianguyihao', age: 28, info: { desc: 'hello', }, }; // 浅拷贝:把 obj1 拷贝给 obj2。如果 obj1 只有一层数据,那么,obj1 和 obj2 则互不影响 const obj2 = Object.assign({}, obj1); console.log('obj2:' + JSON.stringify(obj2)); obj1.info.desc = '永不止步'; // 由于 Object.assign() 只是浅拷贝,所以当修改 obj1 的第二层数据时,obj2 对应的值也会被改变。 console.log('obj2:' + JSON.stringify(obj2)); ``` 代码解释:由于 Object.assign() 只是浅拷贝,所以在当前这个案例中, obj2 中的 name 属性和 age 属性是单独存放在新的堆内存地址中的,和 obj1 没有关系;但是,`obj2.info` 属性,跟 `obj1.info`属性,**它俩指向的是同一个堆内存地址**。所以,当我修改 `obj1.info` 里的值之后,`obj2.info`的值也会被修改。 打印结果: ``` obj2:{"name":"qianguyihao","age":28,"info":{"desc":"hello"}} obj2:{"name":"qianguyihao","age":28,"info":{"desc":"永不止步"}} ``` **例 2**: ```js const myObj = { name: 'qianguyihao', age: 28, }; // 【写法1】浅拷贝:把 myObj 拷贝给 obj1 const obj1 = {}; Object.assign(obj1, myObj); // 【写法2】浅拷贝:把 myObj 拷贝给 obj2 const obj2 = Object.assign({}, myObj); // 【写法3】浅拷贝:把 myObj 拷贝给 obj31。注意,这里的 obj31 和 obj32 其实是等价的,他们指向了同一个内存地址 const obj31 = {}; const obj32 = Object.assign(obj31, myObj); ``` 上面这三种写法,是等价的。所以,当我们需要将对象 A 复制(拷贝)给对象 B,不要直接使用 `B = A`,而是要使用 Object.assign(B, A)。 **例 3**: ```js let obj1 = { name: 'qianguyihao', age: 26 }; let obj2 = { city: 'shenzhen', age: 28 }; let obj3 = {}; Object.assign(obj3, obj1, obj2); // 将 obj1、obj2的内容赋值给 obj3 console.log(obj3); // {name: "qianguyihao", age: 28, city: "shenzhen"} ``` 上面的代码,可以理解成:将多个对象(obj1和obj2)合并成一个对象 obj3。 **例4**:【重要】 ```js const obj1 = { name: 'qianguyihao', age: 28, desc: 'hello world', }; const obj2 = { name: '许嵩', sex: '男', }; // 浅拷贝:把 obj1 赋值给 obj2。这一行,是关键代码。这行代码的返回值也是 obj2 Object.assign(obj2, obj1); console.log(JSON.stringify(obj2)); ``` 打印结果: ``` { "name":"qianguyihao", "sex":"男", "age":28, "desc":"hello world" } ``` 注意,**例 4 在实际开发中,会经常遇到,一定要掌握**。它的作用是:将 obj1 的值追加到 obj2 中。如果两个对象里的属性名相同,则 obj2 中的值会被 obj1 中的值覆盖。 **例5:** ```js const a1 = undefined; const a2 = null; Object.assgin(a1, {name: 'qiangu'}); // 报错:TypeError. Cannot convert undefined or null to object Object.assgin(a1, {name: 'yihao'}); // 报错:TypeError. Cannot convert undefined or null to object ``` Object.assign() 方法的第一个参数是目标对象,如果目标对象是 undefined 或 null,则会报错 TypeError。 所以,为了避免报错,我们要先确目标对象存在。比如使用短路运算符确保 a1 是存在的,就不会报错: ```js const a1 = undefined || {}; // 短路苏奶奶福,确保 obj 是存在的对象 Object.assgin(a1, {name: 'qiangu'}); ``` ## 深拷贝的实现方式 深拷贝其实就是将浅拷贝进行递归。 ### 用 for in 递归实现深拷贝 代码实现: ```js let obj1 = { name: 'qianguyihao', age: 28, info: { desc: 'hello', }, color: ['red', 'blue', 'green'], }; let obj2 = {}; deepCopy(obj2, obj1); console.log(obj2); obj1.info.desc = 'github'; console.log(obj2); // 方法:深拷贝 function deepCopy(newObj, oldObj) { for (let key in oldObj) { // 获取属性值 oldObj[key] let item = oldObj[key]; // 判断这个值是否是数组 if (item instanceof Array) { newObj[key] = []; deepCopy(newObj[key], item); } else if (item instanceof Object) { // 判断这个值是否是对象 newObj[key] = {}; deepCopy(newObj[key], item); } else { // 简单数据类型,直接赋值 newObj[key] = item; } } } ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 04-JavaScript基础/31-对象的高级操作.md ================================================ --- title: 31-对象的高级操作 --- ## hasOwnProperty():判断对象中是否包含某个属性 hasOwnProperty() 是 Object 对象的一个方法,用于判断对象自身(即不包括从原型链继承来的属性)是否具有某个特定的属性。 语法: ```js obj.hasOwnProperty(prop); ``` 解释: - obj 是要检查的对象。 - prop 是一个字符串,表示要检查的属性名。 返回值:如果对象 obj 自身包含名为 prop 的属性,则返回 true。否则,返回 false。 举例: ```js const obj = {a: undefined, b: 2, c: 3}; console.log(obj.hasOwnProperty('a')); // true console.log(obj.hasOwnProperty('b')); // true console.log(obj.hasOwnProperty('d')); // false ``` ## Object.freeze() 冻结对象 Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。 代码举例: ```js const params = { name: 'qianguyihao'; port: '8899'; } Object.freeze(params); // 冻结对象 params params.port = '8080';// 修改无效 ``` 上方代码中,把 params 对象冻结后,如果想再改变 params 里面的属性值,是无效的。 ================================================ FILE: 04-JavaScript基础/32-原型链和原型继承(待更新).md ================================================ --- title: 31_2-原型链和原型继承(待更新) publish: false --- ## 前言 在 ES6 中,我们可以通过 ES6 引入的**类 Class** 来实现面向对象的编程(下一篇文章会讲到)。但是**在 ES6 之前,我们是通过构造函数和原型,来模拟类的实现机制**。 今天这篇文章,我们就来学习构造函数和原型。 ================================================ FILE: 04-JavaScript基础/33-类和构造继承(待更新).md ================================================ --- title: 31_3-类和构造继承(待更新) publish: false --- ================================================ FILE: 04-JavaScript基础/34-正则表达式.md ================================================ --- title: 34-正则表达式 publish: true --- ## 正则表达式简介 **定义**:正则表达式用于定义一些字符串的规则。 **作用**:计算机可以根据正则表达式,来检查一个字符串是否符合指定的规则;或者将字符串中符合规则的内容提取出来。 如果你想查看正则更多的内容,可以查阅官方文档关于 RegExp 这个内置对象的用法。 ## 创建正则表达式的对象 ### 方式一:使用构造函数创建正则表达式的对象 语法: ```javascript var 变量 = new RegExp("正则表达式"); // 注意,参数是字符串 var 变量 = new RegExp("正则表达式", "匹配模式"); // 注意,两个参数都是字符串 ``` 备注:`RegExp`的意思是 **Regular expression**。使用typeof检查正则对象,会返回object。 上面的语法中,既可以传一个参数,也可以传两个参数。 创建了正则表达式的对象后,该怎么使用呢?大致分为两个步骤: - (1)创建正则表达式的对象 reg。 - (2)使用 reg 的test() 方法,判断指定字符串是否符合规则。 **正则表达式的`test()`方法**:【重要】 ```javascript myReg.test(str); // 判断字符串 str 是否符合 指定的 myReg 这个正则表达式的规则 ``` 解释:使用`test()`这个方法可以用来检查一个字符串是否符合正则表达式的规则,**如果符合则返回true,否则返回false**。 我们来看看下面的例子。 **1、传一个参数时**: 构造函数 RegExp 中,可以只传一个参数。 代码举例: ```javascript var reg = new RegExp("a"); // 定义一个正则表达式:检查一个字符串中是否含有 a var str1 = "qianguyihao"; var str2 = "smyh"; // 通过 test()方法,判断字符串是否符合 上面定义的 reg 规则 console.log(reg.test(str1)); // 打印结果:true console.log(reg.test(str2)); // 打印结果:false ``` 注意,上面的例子中,我们是先定义了一个正则表达式的规则,然后通过正则表达式的`test()`方法来判断字符串是否符合之前定义的规则。 **2、传两个参数时**:匹配模式 【重要】 构造函数 RegExp 中,也可以传两个参数。我们可以传递一个**匹配模式**作为第二个参数。这个参数可以是: - `i` 忽略大小写。这里的 i 指的是 ignore。 - `g` 全局匹配模式。这里的 g 指的是 global。 代码举例: ```javascript var reg = new RegExp('A', 'i'); var str = 'qiangu'; console.log(reg.test(str)); // 打印结果:true ``` ### 方式二:使用字面量创建正则表达式 我们可以使用字面量来创建正则表达式。 语法: ```javascript var 变量 = /正则表达式/; // 注意,这个语法里没有引号 var 变量 = /正则表达式/匹配模式; // 注意,这个语法里没有引号 ``` 代码举例: ```javascript var reg = /A/i; // 定义正则表达式的规则:检查一个字符串中是否含有 a。忽略大小写。 var str = "qiangu"; console.log(typeof reg); // 打印结果:object console.log(reg.test(str)); // 打印结果:true ``` ### 以上两种方式的对比 - 方式一:使用构造函数创建时,更加灵活,因为参数中还可以传递变量。 - 方式二:使用字面量的方式创建,更加简单。 代码举例: ```javascript var reg = new RegExp("a", "i"); // 方式一 var reg = /a/i; // 方式二 ``` 上面这两行代码的作用是等价的。 ### 避坑指南:全局匹配 g 慎用test()方法 对于非全局匹配的正则表达式,`test()`只会检测**是否存在某个目标字符串**(只要存在就为 true),多次检测的结果都相同。例如: ```javascript const reg = /test/; const str = '_test_test'; reg.test(str) // true reg.test(str) // true reg.test(str) // true ``` 重点来了。 当设置全局标志 `/g` 时,一旦字符串中还存在匹配,test() 方法都将返回 true,同时匹配成功后将把 `lastIndex` 属性的值**设置为上次匹配成功结果之后的第一个字符所在的位置**,下次匹配将从 `lastIndex` 指示的位置开始;匹配不成功时返回 false,同时将 lastIndex 属性的值重置为 0。 举例:(很重要的例子,看仔细) ```javascript const reg = /test/g; const str = '_test_test'; console.log(reg.test(str)); // true console.log(reg.lastIndex); // 5 console.log(reg.test(str)); // true console.log(reg.lastIndex); // 10 console.log(reg.test(str)); // false console.log(reg.lastIndex); // 0 ``` **总结**: 全局匹配模式`g`一般用于 `exec()`、`match()`、`replace()`等方法。 全局匹配模式`g`如果用于test()方法会有问题。因为g模式会生成一个`lastindex`参数来存储匹配最后一次的位置。 参考链接: - [JS正则表达式全局匹配的那些坑](https://juejin.im/post/5de9bd5fe51d45582c27b6f3) - [javascript正则全局匹配g慎用test方法](https://blog.csdn.net/Leolu007/article/details/8576490) - [issues](https://github.com/qianguyihao/Web/issues/69) ## 正则表达式的简单语法 ### 检查一个字符串中是否包含 a或b **写法1**: ```javascript var reg = /a|b/; ``` 解释:使用 `|` 表示`或`的意思。 **写法2**: ```javascript var reg = /[ab]/; // 跟上面的那行语法,是等价的 ``` 解释:这里的`[]`也是表示`或`的意思。 `[]`这个符号在正则还是比较常用的。我们接下来看几个例子。 ### []表示:或 一些规则: - `/[ab]/` 等价于 `/a|b/`:检查一个字符串中是否包含 **a或b** - `/[a-z]/`:检查一个字符串那种是否包含**任意小写字母** - `/[A-Z]/`:任意大写字母 - `/[A-z]/`:任意字母 - `/[0-9]/`:任意数字 - `/a[bde]c/`:检查一个字符串中是否包含 abc 或 adc 或 aec ### [^ ] 表示:除了 举例1: ```javascript var reg = /[^ab]/; // 规则:字符串中,除了a、b之外,还有没有其他的字符内容? var str = "acb"; console.log(reg.test(str)); // 打印结果:true ``` 举例2:(可以用来验证某字符串是否为 纯数字) ```javascript var reg = /[^0-9]/; // 规则:字符串中,除了数字之外,还有没有其他的内容? var str1 = "1991"; var str2 = "199a1"; console.log(reg.test(str1)); // 打印结果:false (如果字符串是 纯数字,则返回 false) console.log(reg.test(str2)); // 打印结果:true ``` ## 支持正则表达式的 String 对象的方法 String对象的如下方法,是支持正则表达式的: | 方法 | 描述 | 备注 | |:-------------|:-------------|:-------------| | split() | 将字符串拆分成数组 | | | search() | 搜索字符串中是否含有指定内容,返回索引 index | | | match() | 根据正则表达式,从一个字符串中将符合条件的内容提取出来 | | | replace() | 将字符串中的指定内容,替换为新的内容并返回 | | 下面来分别介绍和举例。 ### split() `split()`:将一个字符串拆分成一个数组。可以接受一个正则表达式作为参数。 备注:关于`split()`更详细的用法,可以看之前的关于《内置对象:String》这篇文章。 **正则相关的举例**:根据任意字母,将字符串拆分成数组。 代码实现:(通过正则) ```javascript var str = "1a2b3c4d5e6f7g"; var result = str.split(/[A-z]/); // 参数是一个正则表达式:表示所有字母 console.log(result); ``` 打印结果: ```json ["1", "2", "3", "4", "5", "6", "7", ""] ``` ### search() `search()`:搜索字符串中是否含有指定内容。如果搜索到指定内容,则会返回第一次出现的索引;否则返回-1。 `search()`方法可以接受一个正则表达式作为参数,然后会根据正则表达式去检索字符串。`serach()`只会查找第一个,即使设置全局匹配也没用。 **举例**: ```javascript var str = "hello abc hello aec afc"; /* * 搜索字符串中是否含有abc 或 aec 或 afc */ result = str.search(/a[bef]c/); console.log(result); // 打印结果:6 ``` ### match() `match()`:根据正则表达式,从一个字符串中将符合条件的内容提取出来,封装到一个数组中返回(即使只查询到一个结果)。 **注意**:默认情况下,`match()`方法只会找到**第一个**符合要求的内容,找到以后就停止检索。我们可以设置正则表达式为**全局匹配**模式,这样就会匹配到所有的内容,并以**数组**的形式返回。 另外,我们可以为一个正则表达式设置多个匹配模式,且匹配模式的顺序无所谓。 **代码举例**: ```javascript var str = "1a2a3a4a5e6f7A8B9C"; var result1 = str.match(/[a-z]/); // 找到符合要求的第一个内容,然后返回 var result2 = str.match(/[a-z]/g); // 设置为“全局匹配”模式,匹配字符串中 所有的小写字母 var result3 = str.match(/[a-z]/gi); // 设置多个匹配模式,匹配字符串中 所有的字母(忽略大小写) console.log(result1); // 打印结果:["a"] console.log(result2); // 打印结果:["a", "a", "a", "a", "e", "f"] console.log(result3); // 打印结果:["a", "a", "a", "a", "e", "f", "A", "B", "C"] ``` **总结**: match()这个方法还是很实用的,可以在一个很长的字符串中,提取出**有规则**的内容。这不就是爬虫的时候经常会遇到的场景么? ### replace() `replace()`:将字符串中的指定内容,替换为新的内容并返回。不会修改原字符串。 语法: ```javascript 新的字符串 = str.replace(被替换的内容,新的内容); ``` 参数解释: - 被替换的内容:可以接受一个正则表达式作为参数。 - 新的内容:默认只会替换第一个。如果需要替换全部符合条件的内容,可以设置正则表达式为**全局匹配**模式。 代码举例: ```javascript //replace()方法:替换 var str2 = "Today is fine day,today is fine day !!!" console.log(str2); console.log(str2.replace("today","tomorrow")); //只能替换第一个today console.log(str2.replace(/today/gi,"tomorrow")); //这里用到了正则,且为“全局匹配”模式,才能替换所有的today ``` ## 常见正则表达式举例 ### 检查一个字符串是否是一个合法手机号 手机号的规则: - 以1开头(`^1` 表示1开头 , `[^1]`表示非1或除了1) - 第二位是3~9之间任意数字 - 三位以后任意9位数字 正则实现: ```javascript var phoneStr = "13067890123"; var phoneReg = /^1[3-9][0-9]{9}$/; console.log(phoneReg.test(phoneStr)); ``` **备注**:如果在正则表达式中同时使用`^`和`$`符号,则要求字符串必须完全符合正则表达式。 ### 去掉字符串开头和结尾的空格 正则实现: ```javascript str = str.replace(/^\s*|\s*$/g,""); ``` 解释如下: ```javascript str = str.replace(/^\s*/, ""); //去除开头的空格 str = str.replace(/\s*$/, ""); //去除结尾的空格 ``` ### 判断字符串是否为电子邮件 正则实现: ```javascript var emailReg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/; var email = "abchello@163.com"; console.log(emailReg.test(email)); ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/35-事件简介.md ================================================ --- title: 35-事件简介 publish: true --- ## 事件简介 事件:就是文档或浏览器窗口中发生的一些特定的交互瞬间。对于 Web 应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、关闭弹窗等等。 JavaScript 是以**事件驱动为核心**的一门语言。JavaScript 与 HTML 之间的交互是通过事件实现的。 ### 事件的三要素 **事件的三要素:事件源、事件、事件驱动程序**。 比如,我用手去按开关,灯亮了。这件事情里,事件源是:手。事件是:按开关。事件驱动程序是:灯开了或者关了。 再比如,网页上弹出一个广告,我点击右上角的`X`,广告就关闭了。这件事情里,事件源是:`X`。事件是:onclick。事件驱动程序是:广告关闭了。 于是我们可以总结出:谁引发的后续事件,谁就是事件源。 **总结如下:** - 事件源:引发后续事件的html标签。 - 事件:js已经定义好了(见下图)。 - 事件驱动程序:对样式和html的操作。也就是DOM。 也就是说,我们可以在时间对应的属性中写一些js代码,当事件被触发时,这些代码将会执行。 **代码书写步骤如下:**(重要) - (1)获取事件源:document.getElementById(“box”); // 类似于Android里面的findViewById - (2)绑定事件: 事件源box.事件onclick = function(){ 事件驱动程序 }; - (3)书写事件驱动程序:关于DOM的操作。 最简单的代码举例:(点击box1,然后弹框) ```html
    ``` 常见的事件如下: ![](http://img.smyhvae.com/20180126_1553.png) 下面针对这事件的三要素,进行分别介绍。 ### 1、获取事件源的方式(DOM节点的获取) 获取事件源的常见方式如下: ```javascript var div1 = document.getElementById("box1"); //方式一:通过id获取单个标签 var arr1 = document.getElementsByTagName("div"); //方式二:通过 标签名 获得 标签数组,所以有s var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获得 标签数组,所以有s ``` ### 2、绑定事件的方式 方式一:直接绑定匿名函数 ```html
    ``` 方式二:先单独定义函数,再绑定 ```html
    ``` 注意上方代码的注释。**绑定的时候,是写fn,不是写fn()**。fn代表的是整个函数,而fn()代表的是返回值。 方式三:行内绑定 ```html
    ``` 注意第一行代码,绑定时,是写的`"fn()"`,不是写的`"fn"`。因为绑定的这段代码不是写在js代码里的,而是被识别成了**字符串**。 ### 3、事件驱动程序 我们在上面是拿alert举例,不仅如此,我们还可以操作标签的属性和样式。举例如下: 点击鼠标时,原本粉色的div变大了,背景变红: ```html
    ``` 上方代码的注意事项: - 在js里写属性值时,要用引号 - 在js里写属性名时,是`backgroundColor`,不是CSS里面的`background-color`。 实现效果如下: ![](http://img.smyhvae.com/20180126_1720.gif) ### onload事件 > onload事件比较特殊,这里单独讲一下。 **当页面加载(文本和图片)完毕的时候,触发onload事件。** 举例: ```html ``` 有一点我们要知道:**js的加载是和html同步加载的**。因此,如果使用元素在定义元素之前,容易报错。这个时候,onload事件就能派上用场了,我们可以把使用元素的代码放在onload里,就能保证这段代码是最后执行。 建议是:整个页面上所有元素加载完毕再执行js内容。所以,window.onload可以预防使用标签在定义标签之前。 **备注**:关于 onLoad事件,在下一篇文章《DOM简介和DOM操作》中有更详细的讲解和示例。 ### 事件举例:京东顶部广告栏 ![](http://img.smyhvae.com/20180122_1020.png) 比如上面这张图,当鼠标点击右上角的`X`时,关掉整个广告栏,这就要用到事件。 代码实现如下: ```html
    ×
    ``` 注意最后一行代码,这种方式会替换旧类名,意思是,不管之前的类名叫什么,都会被修改。 ### 事件举例: 要求实现效果:当鼠标悬停在img上时,更换为另外一张图片;鼠标离开时,还原为本来的图片。 代码实现: ```html ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/36-DOM简介和DOM操作.md ================================================ --- title: 36-DOM简介和DOM操作 publish: true --- ## 常见概念 ### JavaScript的组成 JavaScript基础分为三个部分: - ECMAScript:JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。 - **DOM**:文档对象模型(Document object Model),操作**网页上的元素**的API。比如让盒子移动、变色、轮播图等。 - **BOM**:浏览器对象模型(Browser Object Model),操作**浏览器部分功能**的API。比如让浏览器自动滚动。 ### 节点 **节点**(Node):构成 HTML 网页的最基本单元。网页中的每一个部分都可以称为是一个节点,比如:html标签、属性、文本、注释、整个文档等都是一个节点。 虽然都是节点,但是实际上他们的具体类型是不同的。常见节点分为四类: - 文档节点(文档):整个 HTML 文档。整个 HTML 文档就是一个文档节点。 - 元素节点(标签):HTML标签。 - 属性节点(属性):元素的属性。 - 文本节点(文本):HTML标签中的文本内容(包括标签之间的空格、换行)。 节点的类型不同,属性和方法也都不尽相同。所有的节点都是Object。 ### 什么是DOM **DOM**:Document Object Model,文档对象模型。DOM 为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构。目的其实就是为了能让js操作html元素而制定的一个规范。 DOM就是由节点组成的。 **解析过程**: HTML加载完毕,渲染引擎会在内存中把HTML文档,生成一个DOM树,getElementById是获取内中DOM上的元素节点。然后操作的时候修改的是该元素的**属性**。 **DOM树**:(一切都是节点) DOM的数据结构如下: ![](http://img.smyhvae.com/20180126_2105.png) 上图可知,**在HTML当中,一切都是节点**(非常重要)。节点的分类,在上一段中,已经讲了。 整个html文档就是一个文档节点。所有的节点都是Object。 ### DOM可以做什么 - 找对象(元素节点) - 设置元素的属性值 - 设置元素的样式 - 动态创建和删除元素 - 事件的触发响应:事件源、事件、事件的驱动程序 ## 元素节点的获取 DOM节点的获取方式其实就是**获取事件源的方式**。关于事件,上一篇文章中已经讲到了。 想要操作元素节点,必须首先要找到该节点。有三种方式可以获取DOM节点: ```javascript var div1 = document.getElementById("box1"); //方式一:通过 id 获取 一个 元素节点(为什么是一个呢?因为 id 是唯一的) var arr1 = document.getElementsByTagName("div"); //方式二:通过 标签名 获取 元素节点数组,所以有s var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获取 元素节点数组,所以有s ``` 既然方式二、方式三获取的是标签数组,那么习惯性是**先遍历之后再使用**。 特殊情况:数组中的值只有1个。即便如此,这一个值也是包在数组里的。这个值的获取方式如下: ```javascript document.getElementsByTagName("div1")[0]; //取数组中的第一个元素 document.getElementsByClassName("hehe")[0]; //取数组中的第一个元素 ``` ## DOM访问关系的获取 DOM的节点并不是孤立的,因此可以通过DOM节点之间的相对关系对它们进行访问。如下: ![](http://img.smyhvae.com/20180126_2140.png) 节点的访问关系,是以**属性**的方式存在的。 JS中的**父子兄**访问关系: ![](http://img.smyhvae.com/20180126_2145.png) 这里我们要重点知道**parentNode**和**children**这两个属性的用法。下面分别介绍。 ### 获取父节点 调用者就是节点。一个节点只有一个父节点,调用方式就是 ```javascript 节点.parentNode ``` ### 获取兄弟节点 **1、下一个节点 | 下一个元素节点**: > Sibling的中文是**兄弟**。 (1)nextSibling: - 火狐、谷歌、IE9+版本:都指的是下一个节点(包括标签、空文档和换行节点)。 - IE678版本:指下一个元素节点(标签)。 (2)nextElementSibling: - 火狐、谷歌、IE9+版本:都指的是下一个元素节点(标签)。 **总结**:为了获取下一个**元素节点**,我们可以这样做:在IE678中用nextSibling,在火狐谷歌IE9+以后用nextElementSibling,于是,综合这两个属性,可以这样写: ```javascript 下一个兄弟节点 = 节点.nextElementSibling || 节点.nextSibling ``` **2、前一个节点 | 前一个元素节点**: > previous的中文是:前一个。 (1)previousSibling: - 火狐、谷歌、IE9+版本:都指的是前一个节点(包括标签、空文档和换行节点)。 - IE678版本:指前一个元素节点(标签)。 (2)previousElementSibling: - 火狐、谷歌、IE9+版本:都指的是前一个元素节点(标签)。 **总结**:为了获取前一个**元素节点**,我们可以这样做:在IE678中用previousSibling,在火狐谷歌IE9+以后用previousElementSibling,于是,综合这两个属性,可以这样写: ```javascript 前一个兄弟节点 = 节点.previousElementSibling || 节点.previousSibling ``` **3、补充**:获得任意一个兄弟节点: ```javascript 节点自己.parentNode.children[index]; //随意得到兄弟节点 ``` ### 获取单个的子节点 **1、第一个子节点 | 第一个子元素节点**: (1)firstChild: - 火狐、谷歌、IE9+版本:都指的是第一个子节点(包括标签、空文档和换行节点)。 - IE678版本:指第一个子元素节点(标签)。 (2)firstElementChild: - 火狐、谷歌、IE9+版本:都指的是第一个子元素节点(标签)。 **总结**:为了获取第一个**子元素节点**,我们可以这样做:在IE678中用firstChild,在火狐谷歌IE9+以后用firstElementChild,于是,综合这两个属性,可以这样写: ```javascript 第一个子元素节点 = 节点.firstElementChild || 节点.firstChild ``` **2、最后一个子节点 | 最后一个子元素节点**: (1)lastChild: - 火狐、谷歌、IE9+版本:都指的是最后一个子节点(包括标签、空文档和换行节点)。 - IE678版本:指最后一个子元素节点(标签)。 (2)lastElementChild: - 火狐、谷歌、IE9+版本:都指的是最后一个子元素节点(标签)。 **总结**:为了获取最后一个**子元素节点**,我们可以这样做:在IE678中用lastChild,在火狐谷歌IE9+以后用lastElementChild,于是,综合这两个属性,可以这样写: ```javascript 最后一个子元素节点 = 节点.lastElementChild || 节点.lastChild ``` ### 获取所有的子节点 (1)**childNodes**:标准属性。返回的是指定元素的**子节点**的集合(包括元素节点、所有属性、文本节点)。是W3C的亲儿子。 - 火狐 谷歌等高本版会把换行也看做是子节点。 用法: ```javascript 子节点数组 = 父节点.childNodes; //获取所有节点。 ``` (2)**children**:非标准属性。返回的是指定元素的**子元素节点**的集合。【重要】 - 它只返回HTML节点,甚至不返回文本节点。 - 在IE6/7/8中包含注释节点(在IE678中,注释节点不要写在里面)。 虽然不是标准的DOM属性,但它和innerHTML方法一样,得到了几乎所有浏览器的支持。 用法:(**用的最多**) ```javascript 子节点数组 = 父节点.children; //获取所有节点。用的最多。 ``` ## DOM节点的操作(重要) 上一段的内容:节点的**访问关系**都是**属性**。 本段的内容:节点的**操作**都是**函数**(方法)。 ### 创建节点 格式如下: ```javascript 新的标签(元素节点) = document.createElement("标签名"); ``` 比如,如果我们想创建一个li标签,或者是创建一个不存在的adbc标签,可以这样做: ```html ``` 打印结果: ![](http://img.smyhvae.com/20180127_1135.png) ### 插入节点 插入节点有两种方式,它们的含义是不同的。 方式1: ```javascript 父节点.appendChild(新的子节点); ``` 解释:父节点的最后插入一个新的子节点。 方式2: ```javascript 父节点.insertBefore(新的子节点,作为参考的子节点) ``` 解释: - 在参考节点前插入一个新的节点。 - 如果参考节点为null,那么他将在父节点里面的最后插入一个子节点。 ![](http://img.smyhvae.com/20180127_1257.png) 我们可以看到,li标签确实被插入到了box1标签的里面,和box2并列了。 方式2的举例: ![](http://img.smyhvae.com/20180127_1302.png) 我们可以看到,b1标签被插入到了box1标签的里面,和a1标签并列,在a1标签的前面。 **特别强调:** 关于方式1的appendChild方法,这里要强调一下。比如,现在有下面这样一个div结构: ```html
    生命壹号
    永不止步
    ``` 上方结构中,子盒子box12是在父亲box11里的,子盒子box22是在父亲box21里面的。现在,如果我调用方法`box11.appendChild(box22)`,**最后产生的结果是:box22会跑到box11中**(也就是说,box22不在box21里面了)。这是一个很神奇的事情: ![](http://img.smyhvae.com/20180129_2125.png) ### 删除节点 格式如下: ```javascript 父节点.removeChild(子节点); ``` 解释:**用父节点删除子节点**。必须要指定是删除哪个子节点。 如果我想删除自己这个节点,可以这么做: ```javascript node1.parentNode.removeChild(node1); ``` ### 复制节点(克隆节点) 格式如下: ```javascript 要复制的节点.cloneNode(); //括号里不带参数和带参数false,效果是一样的。 要复制的节点.cloneNode(true); ``` 括号里带不带参数,效果是不同的。解释如下: - 不带参数/带参数false:只复制节点本身,不复制子节点。 - 带参数true:既复制节点本身,也复制其所有的子节点。 ## 设置节点的属性 我们可以获取节点的属性值、设置节点的属性值、删除节点的属性。 我们就统一拿下面这个标签来举例: ```html 地铁一瞥 ``` 下面分别介绍。 ### 1、获取节点的属性值 **方式1**: ```javascript 元素节点.属性名; 元素节点[属性名]; ``` 举例:(获取节点的属性值) ```html 地铁一瞥 ``` 上方代码中的img标签,有各种属性,我们可以逐一获取,打印结果如下: ![](http://img.smyhvae.com/20180127_1340.png) **方式2**: ```javascript 元素节点.getAttribute("属性名称"); ``` 举例: ```javascript console.log(myNode.getAttribute("src")); console.log(myNode.getAttribute("class")); //注意是class,不是className console.log(myNode.getAttribute("title")); ``` 打印结果: ![](http://img.smyhvae.com/20180127_1345.png) 方式1和方式2的区别在于:前者是直接操作标签,后者是把标签作为DOM节点。推荐方式2。 ### 2、设置节点的属性值 方式1举例:(设置节点的属性值) ```javascript myNode.src = "images/2.jpg" //修改src的属性值 myNode.className = "image2-box"; //修改class的name ``` 方式2: ```javascript 元素节点.setAttribute("属性名", "新的属性值"); ``` 方式2举例:(设置节点的属性值) ```javascript myNode.setAttribute("src","images/3.jpg"); myNode.setAttribute("class","image3-box"); myNode.setAttribute("id","你好"); ``` ### 3、删除节点的属性 格式: ```javascript 元素节点.removeAttribute(属性名); ``` 举例:(删除节点的属性) ```javascript myNode.removeAttribute("class"); myNode.removeAttribute("id"); ``` ### 总结 获取节点的属性值和设置节点的属性值,都有两种方式。 **如果是节点的“原始属性”**(比如 普通标签的`class/className`属性、普通标签的`style`属性、普通标签的 title属性、img 标签的`src`属性、超链接的`href`属性等),**方式1和方式2是等价的**,可以混用。怎么理解混用呢?比如说:用 `div.title = '我是标题'`设置属性,用 `div.getAttribute('title')`获取属性,就是混用。 但如果是节点的“非原始属性”,比如: ```javascript div.aaa = 'qianguyihao'; div.setAttribute('bbb', 'qianguyihao'); ``` 上面的这个“非原始属性”,在使用这两种方式时,是有区别的。区别如下: - 方式1 的`元素节点.属性`和`元素节点[属性]`:绑定的属性值不会出现在标签上。 - 方式2 的`get/set/removeAttribut`:绑定的属性值会出现在标签上。 - **这两种方式不能交换使用**,get值和set值必须使用同一种方法。 举例: ```html
    我爱你中国
    ``` ## DOM对象的属性-补充 ### innerHTML和innerText的区别 - value:标签的value属性。 - **innerHTML**:双闭合标签里面的内容(包含标签)。 - **innerText**:双闭合标签里面的内容(不包含标签)。(老版本的火狐用textContent) **获取内容举例:** 如果我们想获取innerHTML和innerText里的内容,看看会如何:(innerHTML会获取到标签本身,而innerText则不会) ![](http://img.smyhvae.com/20180127_1652.png) **修改内容举例:**(innerHTML会修改标签本身,而innerText则不会) ![](http://img.smyhvae.com/20180127_1657.png) ### nodeType属性 这里讲一下nodeType属性。 - **nodeType == 1 表示的是元素节点**(标签) 。记住:在这里,元素就是标签。 - nodeType == 2 表示是属性节点。 - nodeType == 3 是文本节点。 ### nodeType、nodeName、nodeValue 我们那下面这个标签来举例: ```html
    生命壹号
    ``` 上面这个标签就包含了三种节点: - 元素节点(标签) - 属性节点 - 文本节点 获取这三个节点的方式如下: ```javascript var element = document.getElementById("box1"); //获取元素节点(标签) var attribute = element.getAttributeNode("id"); //获取box1的属性节点 var txt = element.firstChild; //获取box1的文本节点 var value = element.getAttribute("id"); //获取id的属性值 console.log(element); console.log("--------------"); console.log(attribute); console.log("--------------"); console.log(txt); console.log("--------------"); console.log(value); ``` 打印结果如下: ![](http://img.smyhvae.com/20180128_1935.png) 既然这三个都是节点,如果我想获取它们的nodeType、nodeName、nodeValue,代码如下: ```javascript var element = document.getElementById("box1"); //获取元素节点(标签) var attribute = element.getAttributeNode("id"); //获取box1的属性节点 var txt = element.firstChild; //获取box1的文本节点 //获取nodeType console.log(element.nodeType); //1 console.log(attribute.nodeType); //2 console.log(txt.nodeType); //3 console.log("--------------"); //获取nodeName console.log(element.nodeName); //DIV console.log(attribute.nodeName); //id console.log(txt.nodeName); //#text console.log("--------------"); //获取nodeValue console.log(element.nodeValue); //null console.log(attribute.nodeValue); //box1 console.log(txt.nodeValue); //生命壹号 ``` 打印结果如下: ![](http://img.smyhvae.com/20180128_1939.png) ## 文档的加载 浏览器在加载一个页面时,是按照自上向下的顺序加载的,读取到一行就运行一行。如果将script标签写到页面的上边,在代码执行时,页面还没有加载,页面没有加载DOM对象也没有加载,会导致无法获取到DOM对象。 **onload 事件**: onload 事件会在整个页面加载完成之后才触发。为 window 绑定一个onload事件,该事件对应的响应函数将会在页面加载完成之后执行,这样可以确保我们的代码执行时所有的DOM对象已经加载完毕了。 代码举例: ```html ``` 上方代码中,方式一和方式二均可以确保:在页面加载完毕后,再执行 js 代码。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/37-通过style对象获取和设置行内样式.md ================================================ --- title: 37-通过style对象获取和设置行内样式 publish: true --- ## style属性的获取和修改 在DOM当中,如果想设置样式,有两种形式: - className(针对内嵌样式) - style(针对行内样式) 这篇文章,我们就来讲一下style。 需要注意的是:style是一个对象,只能获取**行内样式**,不能获取内嵌的样式和外链的样式。例如: ```html Title
    ``` 打印结果: ![](http://img.smyhvae.com/20180129_1407.png) 上图显示,因为border属性不是行内样式,所以无法通过style对象获取。 ### 通过 js 读取元素的样式 语法:(方式一) ```javascript 元素.style.样式名 ``` 备注:我们通过style属性读取的样式都是**行内样式**。 语法:(方式二) ```javascript 元素.style["属性"]; //格式 box.style["width"]; //举例 ``` 方式二最大的优点是:可以给属性传递参数。 ### 通过 js 设置元素的样式 语法: ```javascript 元素.style.样式名 = 样式值; ``` 举例: ``` box1.style.width = "300px"; box1.style.backgroundColor = "red"; // 驼峰命名法 ``` 备注:我们通过style属性设置的样式都是**行内样式**,而行内样式有较高的优先级。但是如果在样式中的其他地方写了`!important`,则此时`!important`会有更高的优先级。 ### style属性的注意事项 style属性需要注意以下几点: (1)样式少的时候使用。 (2)style是对象。我们在上方已经打印出来,typeof的结果是Object。 (3)值是字符串,没有设置值是“”。 (4)命名规则,驼峰命名。 (5)只能获取行内样式,和内嵌和外链无关。 (6)box.style.cssText = “字符串形式的样式”。 `cssText`这个属性,其实就是把行内样式里面的值当做字符串来对待。在上方代码的基础之上,举例: ```html ``` 打印结果: ![](http://img.smyhvae.com/20180129_1410.png) ### style的常用属性 style的常用属性包括: - backgroundColor - backgroundImage - color - width - height - border - opacity 设置透明度 (IE8以前是filter: alpha(opacity=xx)) 注意DOM对象style的属性和标签中style内的值不一样,因为在JS中,`-`不能作为标识符。比如: - DOM中:backgroundColor - CSS中:background-color ## style属性的举例 我们针对上面列举的几个style的样式,来举几个例子: - 举例1、改变div的大小和透明度 - 举例2、当前输入的文本框高亮显示 - 举例3、高级隔行变色、高亮显示 下面来逐一实现。 ### 举例1:改变div的大小和透明度 代码举例: ```html
    ``` ### 举例2:当前输入的文本框高亮显示 代码实现: ```html
    ``` ### 举例3:高级隔行变色、高亮显示 ```html
    序号 姓名 课程 成绩
    1 生命壹号 语文 100
    2 生命贰号 日语 99
    3 生命叁号 营销学 98
    4 生命伍号 数学 90
    5 许嵩 英语 96
    6 vae 体育 90
    ``` 实现的效果如下: ![](http://img.smyhvae.com/20180129_1520.gif) 代码解释: 上方代码中,我们**用到了计数器myColor来记录每一行最原始的颜色**(赋值白色之前)。如果不用计数器,可能很多人以为代码是写的:(错误的代码) ```html ``` 这种错误的代码,实现的效果却是:(未达到效果) ![](http://img.smyhvae.com/20180129_1525.gif) ## 通过 js 获取元素当前显示的样式 我们在上面的内容中,通过`元素.style.className`的方式只能获取**行内样式**。但是,有些元素,也写了**内嵌样式或外链样式**。 既然样式有这么多种,那么,如何获取元素当前显示的样式(包括行内样式、内嵌样式、外链样式)呢?我们接下来看一看。 ### 获取元素当前正在显示的样式 (1)w3c的做法: ```javascript window.getComputedStyle("要获取样式的元素", "伪元素"); ``` 两个参数都是必须要有的。参数二中,如果没有伪元素就用 null 代替(一般都传null)。 (2)IE和opera的做法: ```javascript obj.currentStyle; ``` 注意: - 如果当前元素没有设置该样式,则获取它的默认值。 - 该方法会返回一个**对象**,对象中封装了当前元素对应的样式,可以通过`对象.样式名`来读取具体的某一个样式。 - 通过currentStyle和getComputedStyle()读取到的样式都是只读的,不能修改,如果要修改必须通过style属性。 综合上面两种写法,就有了一种兼容性的写法,同时将其封装。代码举例如下: ```html
    ``` 打印结果: ![](http://img.smyhvae.com/20180204_1425.png) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/38-offset相关属性和匀速动画(含轮播图的实现).md ================================================ --- title: 38-offset相关属性和匀速动画(含轮播图的实现) publish: true --- ## 前言 JS动画的主要内容如下: 1、三大家族和一个事件对象: - 三大家族:offset/scroll/client。也叫三大系列。 - 事件对象/event(事件被触动时,鼠标和键盘的状态)(通过属性控制)。 2、动画(闪现/匀速/缓动) 3、冒泡/兼容/封装 ## offset 家族的组成 我们知道,JS动画的三大家族包括:offset/scroll/client。今天来讲一下offset,以及与其相关的匀速动画。 > offset的中文是:偏移,补偿,位移。 js中有一套方便的**获取元素尺寸**的办法就是offset家族。offset家族包括: - offsetWidth - offsetHight - offsetLeft - offsetTop - offsetParent 下面分别介绍。 ### 1、offsetWidth 和 offsetHight `offsetWidth` 和 `offsetHight`:获取元素的**宽高 + padding + border**,不包括margin。如下: - offsetWidth = width + padding + border - offsetHeight = Height + padding + border 这两个属性,他们绑定在了所有的节点元素上。获取元素之后,只要调用这两个属性,我们就能够获取元素节点的宽和高。 举例如下: ```html
    ``` ### 2、offsetParent `offsetParent`:获取当前元素的**定位父元素**。 - 如果当前元素的父元素,**有CSS定位**(position为absolute、relative、fixed),那么 `offsetParent` 获取的是**最近的**那个父元素。 - 如果当前元素的父元素,**没有CSS定位**(position为absolute、relative、fixed),那么`offsetParent` 获取的是**body**。 举例: ```html
    ``` 打印结果: ![](http://img.smyhvae.com/20180202_1725.png) ### 3、offsetLeft 和 offsetTop `offsetLeft`:当前元素相对于其**定位父元素**的水平偏移量。 `offsetTop`:当前元素相对于其**定位父元素**的垂直偏移量。 备注:从父亲的 padding 开始算起,父亲的 border 不算在内。 举例: ```html
    ``` 在父盒子有定位的情况下,offsetLeft == style.left(去掉px之后)。注意,后者只识别行内样式。但区别不仅仅于此,下面会讲。 ### offsetLeft 和 style.left 区别 (1)最大区别在于: offsetLeft 可以返回无定位父元素的偏移量。如果父元素中都没有定位,则body为准。 style.left 只能获取行内样式,如果父元素中都没有设置定位,则返回""(意思是,返回空字符串); (2)offsetTop 返回的是数字,而 style.top 返回的是字符串,而且还带有单位:px。 比如: ```javascript div.offsetLeft = 100; div.style.left = "100px"; ``` (3)offsetLeft 和 offsetTop **只读**,而 style.left 和 style.top 可读写(只读是获取值,可写是修改值) 总结:我们一般的做法是:**用offsetLeft 和 offsetTop 获取值,用style.left 和 style.top 赋值**(比较方便)。理由如下: - style.left:只能获取行内式,获取的值可能为空,容易出现NaN。 - offsetLeft:获取值特别方便,而且是现成的number,方便计算。它是只读的,不能赋值。 ## 动画的种类 - 闪现(基本不用) - 匀速(本文重点) - 缓动(后续重点) 简单举例如下:(每间隔500ms,向右移动盒子100px) ```html
    ``` 效果如下: ![](http://img.smyhvae.com/20180202_1840.gif) ## 匀速动画的封装:每间隔30ms,移动盒子10px【重要】 代码如下: ```html
    ``` 实现的效果: ![](http://img.smyhvae.com/20180202_1910.gif) 上方代码中的方法封装,可以作为一个模板步骤,要记住。其实,这个封装的方法,写成下面这样,会更严谨,更容易理解:(将if语句进行了改进) ```javascript //【重要】方法的封装:每间隔30ms,将盒子向右移动10px function animate(ele, target) { //要用定时器,先清除定时器 //一个盒子只能有一个定时器,这样的话,不会和其他盒子出现定时器冲突 //我们可以把定时器本身,当成为盒子的一个属性 clearInterval(ele.timer); //我们要求盒子既能向前又能向后,那么我们的步长就得有正有负 //目标值如果大于当前值取正,目标值如果小于当前值取负 var speed = target > ele.offsetLeft ? 10 : -10; //speed指的是步长 ele.timer = setInterval(function () { //在执行之前就获取当前值和目标值之差 var val = target - ele.offsetLeft; //移动的过程中,如果目标值和当前值之差如果小于步长,那么就不能在前进了 //因为步长有正有负,所有转换成绝对值来比较 if (Math.abs(val) < Math.abs(speed)) { //如果val小于步长,则直接到达目的地;否则,每次移动一个步长 ele.style.left = target + "px"; clearInterval(ele.timer); } else { ele.style.left = ele.offsetLeft + speed + "px"; } }, 30) } ``` ## 代码举例:轮播图的实现 完整版代码如下:(注释已经比较详细) ```html 无标题文档
    < >
    ``` 实现效果: ![](http://img.smyhvae.com/20180202_2020.gif) 温馨提示:动图太大,可以把单独在浏览器中打开。 工程文件:[2018-02-02-JS动画实现轮播图.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-02-JS%E5%8A%A8%E7%94%BB%E5%AE%9E%E7%8E%B0%E8%BD%AE%E6%92%AD%E5%9B%BE.rar) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 04-JavaScript基础/39-scroll相关属性和缓动动画.md ================================================ --- title: 39-scroll相关属性和缓动动画 publish: true --- ## scroll 相关属性 ### window.onscroll() 方法 当我们用鼠标滚轮,滚动网页的时候,会触发 window.onscroll() 方法。效果如下:(注意看控制台的打印结果) ![](http://img.smyhvae.com/20180202_2258.gif) 如果你需要做滚动监听,可以使用这个方法。 我们来看看和 scroll 相关的有哪些属性。 ### 1、ScrollWidth 和 scrollHeight `ScrollWidth` 和 `scrollHeight`:获取元素**整个滚动区域**的宽、高。包括 width 和 padding,不包括 border和margin。 **注意**: `scrollHeight` 的特点是:如果内容超出了盒子,`scrollHeight`为内容的高(包括超出的内容);如果不超出,`scrollHeight`为盒子本身的高度。`ScrollWidth`同理。 ```html
    静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。
    ``` 打印结果: ![](http://img.smyhvae.com/20180203_1235.png) ### 2、scrollTop 和 scrollLeft - `scrollLeft`:获取水平滚动条滚动的距离。 - `scrollTop`:获取垂直滚动条滚动的距离。 **实战经验**: 当某个元素满足`scrollHeight - scrollTop == clientHeight`时,说明垂直滚动条滚动到底了。 当某个元素满足`scrollWidth - scrollLeft == clientWidth`时,说明水平滚动条滚动到底了。 这个实战经验非常有用,可以用来判断用户是否已经将内容滑动到底了。比如说,有些场景下,希望用户能够看完“长长的活动规则”,才允许触发接下来的表单操作。 ### scrollTop 的兼容性 如果要获取页面滚动的距离,scrollTop 这个属性的写法要注意兼容性,如下。 (1)如果文档没有 DTD 声明,写法为: ```javascript document.body.scrollTop ``` 在没有 DTD 声明的情况下,要求是这种写法,chrome浏览器才能认出来。 (2)如果文档有 DTD 声明,写法为: ```javascript document.documentElement.scrollTop ``` 在有 DTD 声明的情况下,要求是这种写法,IE6、7、8才能认出来。 综合上面这两个,就诞生了一种兼容性的写法: ```javascript document.body.scrollTop || document.documentElement.scrollTop //方式一 document.body.scrollTop + document.documentElement.scrollTop //方式二 ``` 另外还有一种兼容性的写法:`window.pageYOffset` 和 `window.pageXOffset`。这种写法无视DTD的声明。这种写法支持的浏览器版本是:火狐/谷歌/ie9+。 综合上面的几种写法,为了兼容,不管有没有DTD,**最终版的兼容性写法:** ```javascript window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; ``` ### 判断是否已经 DTD 声明 方法如下: ```javascript document.compatMode === "CSS1Compat" // 已声明 document.compatMode === "BackCompat" // 未声明 ``` ### 将 scrollTop 和 scrollLeft 进行封装 这里,我们将 scrollTop 和 scrollLeft 封装为一个方法,名叫scroll(),返回值为 一个对象。以后就直接调用`scroll().top` 和 `scroll().left`就好。 代码实现: ```html ``` 上方代码中,函数定义的那部分就是要封装的代码。 另外还有一种比较麻烦的封装方式:(仅供参考) ```javascript function scroll() { // 开始封装自己的scrollTop if(window.pageYOffset !== undefined) { // ie9+ 高版本浏览器 // 因为 window.pageYOffset 默认的是 0 所以这里需要判断 return { left: window.pageXOffset, top: window.pageYOffset } } else if(document.compatMode === "CSS1Compat") { // 标准浏览器 来判断有没有声明DTD return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { // 未声明 DTD left: document.body.scrollLeft, top: document.body.scrollTop } } ``` ## 获取 html 文档的方法 获取title、body、head、html标签的方法如下: - `document.title` 文档标题; - `document.head` 文档的头标签 - `document.body` 文档的body标签; - `document.documentElement` (这个很重要)。 `document.documentElement`表示文档的html标签。也就是说,基本结构当中的 `html 标签`而是通过`document.documentElement`访问的,并不是通过 document.html 去访问的。 ## scrollTop 举例:固定导航栏 完整版代码实现: (1)index.html: ```html
    ``` 上方代码中,有一个技巧: ```javascript main.style.paddingTop = middle.offsetHeight + "px"; ``` 仔细看注释就好。 (2)tools.js: ```javascript /** * Created by smyhvae on 2018/02/03. */ function scroll() { // 开始封装自己的scrollTop if (window.pageYOffset !== undefined) { // ie9+ 高版本浏览器 // 因为 window.pageYOffset 默认的是 0 所以这里需要判断 return { left: window.pageXOffset, top: window.pageYOffset } } else if (document.compatMode === "CSS1Compat") { // 标准浏览器 来判断有没有声明DTD return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { // 未声明 DTD left: document.body.scrollLeft, top: document.body.scrollTop } } ``` 实现效果: ![](http://img.smyhvae.com/20180203_1619.gif) ~工程文件~: - 2018-02-03-scrollTop固定导航栏.rar - 下载链接暂无。 ## 缓动动画 ### 三个函数 缓慢动画里,我们要用到三个函数,这里先列出来: - Math.ceil() 向上取整 - Math.floor() 向下取整 - Math.round(); 四舍五入 ### 缓动动画的原理 缓动动画的原理就是:在移动的过程中,步长越来越小。 设置步长为:**目标位置和盒子当前位置的十分之一**。用公式表达,即: ``` 盒子位置 = 盒子本身位置 + (目标位置 - 盒子本身位置)/ 10; ``` 代码举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180202_2046.gif) ### 缓慢动画的封装(解决四舍五入的问题) 我们发现一个问题,上图中的盒子最终并没有到达400px的位置,而是只到了396.04px就停住了: ![](http://img.smyhvae.com/20180202_2140.png) 原因是:JS在取整的运算时,进行了四舍五入。 我们把打印396.04px这个left值打印出来看看: ![](http://img.smyhvae.com/20180202_2150.png) 我么发现,通过`div.style.left`获取的值是精确的,通过`div.offsetLeft`获取的left值会进行四舍五入。 此时,我们就要用到取整的函数了。 通过对缓动动画进行封装,完整版的代码实现如下: ```html
    ``` 实现效果: ![](http://img.smyhvae.com/20180202_2239.gif) ## window.scrollTo()方法举例:返回到顶部小火箭 (1)index.html: ```html
    我是最顶端.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    生命壹号,永不止步.....
    ``` (2)tools.js: ```javascript /** * Created by smyhvae on 2015/12/8. */ //函数:获取scrollTop和scrollLeft的值 function scroll() { // 开始封装自己的scrollTop if (window.pageYOffset != null) { // ie9+ 高版本浏览器 // 因为 window.pageYOffset 默认的是 0 所以这里需要判断 return { left: window.pageXOffset, top: window.pageYOffset } } else if (document.compatMode === "CSS1Compat") { // 标准浏览器 来判断有没有声明DTD return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { // 未声明 DTD left: document.body.scrollLeft, top: document.body.scrollTop } } ``` 实现效果: ![](http://img.smyhvae.com/20180203_1710.gif) 小火箭的图片资源: ![](http://img.smyhvae.com/20180203-Top.jpg) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/40-client(可视区)相关属性.md ================================================ --- title: 40-client(可视区)相关属性 publish: true --- ## client 家族的组成 ### clientWidth 和 clientHeight 元素调用时: - clientWidth:获取元素的可见宽度(width + padding)。 - clientHeight:获取元素的可见高度(height + padding)。 body/html 调用时: - clientWidth:获取网页可视区域宽度。 - clientHeight:获取网页可视区域高度。 **声明**: - `clientWidth` 和 `clientHeight` 属性是只读的,不可修改。 - `clientWidth` 和 `clientHeight` 的值都是不带 px 的,返回的都是一个数字,可以直接进行计算。 ### clientX 和 clientY event调用: - clientX:鼠标距离可视区域左侧距离。 - clientY:鼠标距离可视区域上侧距离。 ### clientTop 和 clientLeft - clientTop:盒子的上border。 - clientLeft:盒子的左border。 ## 三大家族 offset/scroll/client 的区别 ### 区别1:宽高 - offsetWidth = width + padding + border - offsetHeight = height + padding + border - scrollWidth = 内容宽度(不包含border) - scrollHeight = 内容高度(不包含border) - clientWidth = width + padding - clientHeight = height + padding ### 区别2:上左 offsetTop/offsetLeft: - 调用者:任意元素。(盒子为主) - 作用:距离父系盒子中带有定位的距离。 scrollTop/scrollLeft: - 调用者:document.body.scrollTop(window调用)(盒子也可以调用,但必须有滚动条) - 作用:浏览器无法显示的部分(被卷去的部分)。 clientY/clientX: - 调用者:event - 作用:鼠标距离浏览器可视区域的距离(左、上)。 ## 函数封装:获取浏览器的宽高(可视区域) 函数封装如下: ```javascript //函数封装:获取屏幕可视区域的宽高 function client() { if (window.innerHeight !== undefined) { //ie9及其以上的版本的写法 return { "width": window.innerWidth, "height": window.innerHeight } } else if (document.compatMode === "CSS1Compat") { //标准模式的写法(有DTD时) return { "width": document.documentElement.clientWidth, "height": document.documentElement.clientHeight } } else { //没有DTD时的写法 return { "width": document.body.clientWidth, "height": document.body.clientHeight } } } ``` **案例:根据浏览器的可视宽度,给定不同的背景的色。** > PS:这个可以用来做响应式。 代码如下:(需要用到上面的封装好的方法) ```html ``` 上当代码中,`window.onresize`事件指的是:在窗口或框架被调整大小时发生。各个事件的解释如下: - window.onscroll 屏幕滑动 - window.onresize 浏览器大小变化 - window.onload 页面加载完毕 - div.onmousemove 鼠标在盒子上移动(注意:不是盒子移动) ## 获取显示器的分辨率 比如,我的电脑的显示器分辨率是:1920*1080。 获取显示器的分辨率: ```javascript window.onresize = function () { document.title = window.screen.width + " " + window.screen.height; } ``` 显示效果: ![](http://img.smyhvae.com/20180203_2155.png) 上图中,不管我如何改变浏览器的窗口大小,title栏显示的值永远都是我的显示器分辨率:1920*1080。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/41-事件的绑定和事件对象Event.md ================================================ --- title: 41-事件的绑定和事件对象Event --- ## 绑定事件的两种方式/DOM事件的级别 我们在之前的一篇文章《04-JavaScript/22-DOM简介和DOM操作》中已经讲过事件的概念。这里讲一下绑定(注册)事件的两种方式,我们以onclick事件为例。 ### DOM0的写法:onclick ```javascript element.onclick = function () { } ``` 举例: ```html ``` 点击按钮后,上方代码的打印结果: ```html 事件2 ``` 我们可以看到,`DOM对象.事件 = 函数`的这种绑定事件的方式:一个元素的一个事件只能绑定一个响应函数。如果绑定了多个响应函数,则后者会覆盖前者。 ### DOM2的写法:addEventListener(高版本浏览器) ```javascript element.addEventListener('click', function () { }, false); ``` 参数解释: - 参数1:事件名的字符串(注意,没有on) - 参数2:回调函数:当事件触发时,该函数会被执行 - 参数3:**true表示捕获阶段触发,false表示冒泡阶段触发(默认)**。如果不写,则默认为false。【重要】 举例: ```html ``` 点击按钮后,上方代码的打印结果: ```html 事件1 事件2 ``` 我们可以看到,`addEventListener()`这种绑定事件的方式: - 一个元素的一个事件,可以绑定多个响应函数。不存在响应函数被覆盖的情况。**执行顺序是**:事件被触发时,响应函数会按照函数的绑定顺序执行。 - addEventListener()中的this,是绑定事件的对象。 - `addEventListener()`不支持 IE8 及以下的浏览器。在IE8中可以使用`attachEvent`来绑定事件(详见下一小段)。 ### DOM2的写法:attachEvent(IE8及以下版本浏览器) ```javascript element.attachEvent('onclick', function () { }); ``` 参数解释: - 参数1:事件名的字符串(注意,有on) - 参数2:回调函数:当事件触发时,该函数会被执行 举例: ```html ``` 在低版本的IE浏览器上,点击按钮后,上方代码的打印结果: ```html 事件2 事件1 ``` 我们可以看到,`attachEvent()`这种绑定事件的方式: - 一个元素的一个事件,可以绑定多个响应函数。不存在响应函数被覆盖的情况。**注意**:执行顺序是,后绑定的先执行。 - attachEvent()中的this,是window ### 兼容性写法 上面的内容里,需要强调的是: - `addEventListener()`中的this,是绑定事件的对象。 - `attachEvent()`中的this,是window。 既然这两个写法的`this`不同,那么,有没有一种兼容性的写法可以确保这两种绑定方式的this是相同的呢?我们可以封装一下。代码如下: ```html ``` ## 事件对象 当事件的响应函数被触发时,会产生一个事件对象`event`。浏览器每次都会将这个事件`event`作为实参传进之前的响应函数。 这个对象中包含了与当前事件相关的一切信息。比如鼠标的坐标、键盘的哪个按键被按下、鼠标滚轮滚动的方向等。 ### 获取 event 对象(兼容性问题) 所有浏览器都支持event对象,但支持的方式不同。如下。 (1)普通浏览器的写法是 `event`。比如: ![](http://img.smyhvae.com/20180203_1735.png) (2)ie 678 的写法是 `window.event`。此时,事件对象 event 是作为window对象的属性保存的。 于是,我们可以采取一种兼容性的写法。如下: ```javascript event = event || window.event; // 兼容性写法 ``` 代码举例: ```html ``` ### event 属性 event 有很多属性,比如: ![](http://img.smyhvae.com/20180203_1739.png) 由于pageX 和 pageY的兼容性不好,我们可以这样做: - 鼠标在页面的位置 = 滚动条滚动的距离 + 可视区域的坐标。 ## Event举例 ### 举例1:使 div 跟随鼠标移动 代码实现: ```html
    ``` ### 举例2:获取鼠标距离所在盒子的距离 关键点: ``` 鼠标距离所在盒子的距离 = 鼠标在整个页面的位置 - 所在盒子在整个页面的位置 ``` 代码演示: ```html
    ``` 实现效果: ![](http://img.smyhvae.com/20180203_1828.gif) ### 举例3:商品放大镜 代码实现: (1)index.html: ```html
    ``` (2)tools.js: ```javascript /** * Created by smyhvae on 2018/02/03. */ //显示和隐藏 function show(ele) { ele.style.display = "block"; } function hide(ele) { ele.style.display = "none"; } function scroll() { // 开始封装自己的scrollTop if (window.pageYOffset != null) { // ie9+ 高版本浏览器 // 因为 window.pageYOffset 默认的是 0 所以这里需要判断 return { left: window.pageXOffset, top: window.pageYOffset } } else if (document.compatMode === "CSS1Compat") { // 标准浏览器 来判断有没有声明DTD return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { // 未声明 DTD left: document.body.scrollLeft, top: document.body.scrollTop } } ``` 效果演示: ![](http://img.smyhvae.com/20180203_1920.gif) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 04-JavaScript基础/42-事件的传播和事件冒泡.md ================================================ --- title: 42-事件的传播和事件冒泡 publish: true --- ## DOM事件流 事件传播的三个阶段是:事件捕获、事件冒泡和目标。 - 事件捕获阶段:事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。在这个过程中,默认情况下,事件相应的监听函数是不会被触发的。 - 事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。 - 事件冒泡阶段:事件从事件目标 target 开始,从子元素往冒泡祖先元素冒泡,直到页面的最上一级标签。 如下图所示: ![](http://img.smyhvae.com/20180204_1218.jpg) PS:这个概念类似于 Android 里的 **touch 事件传递**。 ### 事件捕获 addEventListener可以捕获事件: ```javascript box1.addEventListener("click", function () { alert("捕获 box3"); }, true); ``` 上面的方法中,参数为true,代表事件在捕获阶段执行。 代码演示: ```javascript //参数为true,代表事件在「捕获」阶段触发;参数为false或者不写参数,代表事件在「冒泡」阶段触发 box3.addEventListener("click", function () { alert("捕获 child"); }, true); box2.addEventListener("click", function () { alert("捕获 father"); }, true); box1.addEventListener("click", function () { alert("捕获 grandfather"); }, true); document.addEventListener("click", function () { alert("捕获 body"); }, true); ``` 效果演示: ![](http://img.smyhvae.com/20180204_1101.gif) (如果上面的图片打不开,请点击:) **重点**:捕获阶段,事件依次传递的顺序是:window --> document --> html--> body --> 父元素、子元素、目标元素。 这几个元素在事件捕获阶段的完整写法是: ```javascript window.addEventListener("click", function () { alert("捕获 window"); }, true); document.addEventListener("click", function () { alert("捕获 document"); }, true); document.documentElement.addEventListener("click", function () { alert("捕获 html"); }, true); document.body.addEventListener("click", function () { alert("捕获 body"); }, true); fatherBox.addEventListener("click", function () { alert("捕获 father"); }, true); childBox.addEventListener("click", function () { alert("捕获 child"); }, true); ``` 说明: (1)第一个接收到事件的对象是 **window**(有人会说body,有人会说html,这都是错误的)。 (2)JS中涉及到DOM对象时,有两个对象最常用:window、doucument。它们俩是最先获取到事件的。 **补充一个知识点:** 在 js中: - 如果想获取 `html`节点,方法是`document.documentElement`。 - 如果想获取 `body` 节点,方法是:`document.body`。 二者不要混淆了。 ### 事件冒泡 **事件冒泡**: 当一个元素上的事件被触发的时候(比如说鼠标点击了一个按钮),同样的事件将会在那个元素的所有**祖先元素**中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。 通俗来讲,冒泡指的是:**子元素的事件被触发时,父元素的同样的事件也会被触发**。取消冒泡就是取消这种机制。 代码演示: ```javascript //事件冒泡 box3.onclick = function () { alert("child"); } box2.onclick = function () { alert("father"); } box1.onclick = function () { alert("grandfather"); } document.onclick = function () { alert("body"); } ``` ![](http://img.smyhvae.com/20180204_1028.gif) (如果上面的图片打不开,请点击:) 上图显示,当我点击子元素 box3 的时候,它的父元素box2、box1、body都依次被触发了。即使我改变代码的顺序,也不会影响效果的顺序。 当然,上面的代码中,我们用 addEventListener 这种 DOM2的写法也是可以的,但是第三个参数要写 false,或者不写。 **冒泡顺序**: 一般的浏览器: (除IE6.0之外的浏览器) - div -> body -> html -> document -> window IE6.0: - div -> body -> html -> document ### 不是所有的事件都能冒泡 以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。意思是,事件不会往父元素那里传递。 我们检查一个元素是否会冒泡,可以通过事件的以下参数: ```javascript event.bubbles ``` 如果返回值为true,说明该事件会冒泡;反之则相反。 举例: ```javascript box1.onclick = function (event) { alert("冒泡 child"); event = event || window.event; console.log(event.bubbles); //打印结果:true。说明 onclick 事件是可以冒泡的 } ``` ## 阻止冒泡 大部分情况下,冒泡都是有益的。当然,如果你想阻止冒泡,也是可以的。可以按下面的方法阻止冒泡。 ### 阻止冒泡的方法 w3c的方法:(火狐、谷歌、IE11) ```javascript event.stopPropagation(); ``` IE10以下则是: ```javascript event.cancelBubble = true ``` 兼容代码如下: ```javascript box3.onclick = function (event) { alert("child"); //阻止冒泡 event = event || window.event; if (event && event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } } ``` 上方代码中,我们对box3进行了阻止冒泡,产生的效果是:事件不会继续传递到 father、grandfather、body了。 ### 阻止冒泡的举例 ```html
    ``` 关键地方可以看代码中的注释。 效果演示: ![](http://img.smyhvae.com/20191112_1650.gif) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/43-事件委托.md ================================================ --- title: 43-事件委托 publish: true --- ## 事件委托 事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素。 比如说有一个列表 ul,列表之中有大量的列表项 ``标签: ```html ``` 当我们的鼠标移到``标签上的时候,需要获取此``的相关信息并飘出悬浮窗以显示详细信息,或者当某个``被点击的时候需要触发相应的处理事件。我们通常的写法,是为每个``都绑定类似onMouseOver或者onClick之类的事件监听: ```javascript window.onload = function(){ var parentNode = document.getElementById("parent-list"); var aNodes = parentNode.getElementByTagName("a"); for(var i=0, l = aNodes.length; i < l; i++){ aNodes[i].onclick = function() { console.log('我是超链接 a 的单击相应函数'); } } } ``` 但是,上面的做法过于消耗内存和性能。**我们希望,只绑定一次事件,即可应用到多个元素上**,即使元素是后来添加的。 因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 `ul` 上,然后在执行事件函数的时候再去匹配判断目标元素。如下: ```html ``` 上方代码,为父节点注册 click 事件,当子节点被点击的时候,click事件会从子节点开始**向父节点冒泡**。**父节点捕获到事件**之后,开始执行方法体里的内容:通过判断 event.target 拿到了被点击的子节点``。从而可以获取到相应的信息,并作处理。 换而言之,参数为false,说明事件是在冒泡阶段触发(子元素向父元素传递事件)。而父节点注册了事件函数,子节点没有注册事件函数,此时,会在父节点中执行函数体里的代码。 **总结**:事件委托是利用了冒泡机制,减少了事件绑定的次数,减少内存消耗,提高性能。 事件委托的参考链接: - [荐 | JavaScript事件代理和委托(Delegation)](https://www.cnblogs.com/owenChen/archive/2013/02/18/2915521.html) - [JavaScript 事件委托详解](https://zhuanlan.zhihu.com/p/26536815) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/44-键盘事件.md ================================================ --- title: 44-键盘事件 --- ## 鼠标的拖拽事件 拖拽的流程: (1)`onmousedown`:当鼠标在被拖拽元素上按下时,开始拖拽; (2)`onmousemove`:当鼠标移动时被拖拽元素跟随鼠标移动; (3)`onmouseup`:当鼠标松开时,被拖拽元素固定在当前位置。 ## 鼠标的滚轮事件 `onmousewheel`:鼠标滚轮滚动的事件,会在滚轮滚动时触发。但是火狐不支持该属性。 `DOMMouseScroll`:在火狐中需要使用 DOMMouseScroll 来绑定滚动事件。注意该事件需要通过addEventListener()函数来绑定。 ## 键盘事件 ### 事件名 `onkeydown`:按键被按下。 `onkeyup`:按键被松开。 **注意**: - 如果一直按着某一个按键不松手,那么,`onkeydown`事件会一直触发。此时,松开键盘,`onkeyup`事件会执行一次。 - 当`onkeydown`连续触发时,第一次和第二次之间会间隔稍微长一点,后续的间隔会非常快。这种设计是为了防止误操作的发生。 键盘事件一般都会绑定给一些可以获取到焦点的对象或者是document。代码举例: ```html ``` ### 判断哪个键盘被按下 可以通过`event`事件对象的`keyCode`来获取按键的编码。 此外,`event`事件对象里面还提供了以下几个属性: - altKey - ctrlKey - shiftKey 上面这三个属性,可以用来判断`alt`、`ctrl`、和`shift`是否被按下。如果按下则返回true,否则返回false。代码举例: ```html ``` **举例**:input 文本框中,禁止输入数字。代码实现: ```html ``` ## 举例:通过键盘的方向键,移动盒子 代码实现: ```html
    ``` 上方代码,待改进的地方: (1)移动盒子时,如果要加速,需要先按`方向键`,再按`Ctrl键`。 (2)首次移动盒子时,动作较慢。后续如果学习了定时器相关的内容,可以再改进。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/45-BOM简介和navigator.userAgent&History&Location.md ================================================ --- title: 45-BOM简介和navigator.userAgent&History&Location --- ## 常见概念 ### JavaScript的组成 JavaScript基础分为三个部分: - ECMAScript:JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。 - **DOM**:文档对象模型(Document object Model),操作**网页上的元素**的API。比如让盒子移动、变色、轮播图等。 - **BOM**:浏览器对象模型(Browser Object Model),操作**浏览器部分功能**的API。比如让浏览器自动滚动。 ### 常见的 BOM 对象 BOM可以让我们通过JS来操作浏览器。BOM中为我们提供了一些对象,来完成对浏览器相关的操作。 常见的 BOM对象有: - Window:代表整个浏览器的窗口,同时 window 也是网页中的全局对象。 - Navigator:代表当前浏览器的信息,通过该对象可以识别不同的浏览器。 - Location:代表当前浏览器的地址栏信息,通过 Location 可以获取地址栏信息,或者操作浏览器跳转页面。 - History:代表浏览器的历史记录,通过该对象可以操作浏览器的历史记录。由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或向后翻页,而且该操作只在当次访问时有效。 - Screen:代表用户的屏幕信息,通过该对象可以获取用户的显示器的相关信息。 备注:这些 BOM 对象都是作为 window 对象的属性保存的,可以通过window对象来使用,也可以直接使用。比如说,我可以使用 `window.location.href`,也可以直接使用 `location.href`,二者是等价的。 备注2:不要忘了,之前学习过的`document`也是在`window`中保存的。 这篇文章,我们先来讲一下 几个常见的 BOM 对象。 ## Navigator 和 `navigator.userAgent` `Navigator`代表当前浏览器的信息,通过该对象可以识别不同的浏览器。 由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了。 **一般我们只会使用`navigator.userAgent`来获取浏览器的信息**。 userAgent 的值是一个字符串,简称 **UA**,这个字符串中包含了用来描述浏览器信息的内容,不同的浏览器会有不同的userAgent。 **代码举例**:(获取当前浏览器的UA) ```html Document ``` ### 在电脑上模拟移动端浏览器 不同浏览器(包括微信内置的浏览器)的 userAgent 信息,是不一样的,我们可以根据 `navigator.userAgent`属性来获取。 比如说,我们在电脑浏览器上,按F12,然后在控制台输入`navigator.userAgent`,如下: ![](http://img.smyhvae.com/20180425_1656.png) 上图显示,MacOS上的Chrome浏览器的 userAgent 是: ``` "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36" ``` 我们还可以在电脑浏览器的控制台里可以添加很多设备,通过这种方式,可以模拟移动端浏览器的场景,非常有用,请务必掌握。操作如下: (1)需要点击 edit,手动添加: ![](http://img.smyhvae.com/20191127_1903.png) (2)添加时,根据 User agent 来识别不同的浏览器: ![](http://img.smyhvae.com/20191127_1918.png) ### 不同浏览器的 userAgent iOS 版微信浏览器: ``` Mozilla/5.0 (iPhone; CPU iPhone OS 9_3 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13E233 MicroMessenger/6.3.15 NetType/WIFI Language/zh_CN ``` Android 版微信浏览器: ``` Mozilla/5.0 (Linux; Android 5.0.1; GT-I9502 Build/LRX22C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.121 Mobile Safari/537.36 MicroMessenger/6.1.0.78_r1129455.543 NetType/WIFI ``` iOS 版本QQ浏览器: ``` Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_2 like Mac OS X) AppleWebKit/604.4.7 (KHTML, like Gecko) Mobile/15C202 QQ/7.3.5.473 V1_IPH_SQ_7.3.5_1_APP_A Pixel/1125 Core/UIWebView Device/Apple(iPhone X) NetType/WIFI QBWebViewType/1 ``` Android 版 QQ浏览器: ``` Mozilla/5.0 (Linux; Android 4.4.2; PE-TL20 Build/HuaweiPE-TL20; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/043807 Mobile Safari/537.36 V1_AND_SQ_7.3.2_762_YYB_D QQ/7.3.2.3350 NetType/WIFI WebP/0.3.0 Pixel/1080 ``` **参考链接**: - [微信、QQ在Android和iOS的UserAgent](https://blog.csdn.net/taambernk520/article/details/80801574) - [判断微信内置浏览器的UserAgent](http://www.cnblogs.com/7z7chn/p/5370352.html) - [微信内置浏览器UserAgent的判断](https://gist.github.com/wjp2013/fff34c063cf0cf227d65) ## History 对象 History对象:可以用来操作浏览器的向前或向后翻页。 ### History对象的属性 ```javascript history.length ``` 解释:获取浏览器历史列表中的 url 数量。注意,只是统计当次的数量,如果浏览器关了,数量会重置为1。 ### History对象的方法 **方法1**: ``` history.back(); ``` 解释:用来回退到上一个页面,作用和浏览器的「回退按钮」一样。 **方法2**: ```javascript history.forward(); ``` 解释:用来跳转下一个页面,作用和浏览器的「前进按钮」一样。 **方法3**: ```javascript history.go( int n); // 需要整数作为参数 // 代码举例: history.go( 1 ); // 向前跳转一个页面,相当于 history.forward() history.go( 2 ); // 表示向前跳转两个页面 history.go( 0 ); // 刷新当前页面 history.go( -1 ); // 向后跳转一个页面,相当于 history.back() history.go( -2 ); // 向后跳转两个页面 ``` 解释:向前/向后跳转 n 个页面。 备注:浏览器的前进按钮、后退按钮,在这个位置: ![](http://img.smyhvae.com/20180201_2146.png) ## Location 对象 Location 对象:封装了浏览器地址栏的 URL 信息。 下面介绍一些常见的属性和方法。 ### Location 对象的属性:location.href ``` location.href location.href = 'https://xxx'; ``` 解释:获取当前页面的 url 路径(或者设置 url 路径);或者跳转到指定路径。 举例1: ```javascript console.log(location.href); // 获取当前页面的url 路径 ``` 举例2: ```javascript location.href = 'www.baidu.com'; // 跳转到指定的页面链接。通俗理解就是:跳转到其他的页面 ``` 从上方的**举例2**中可以看出:如果直接将`location.href`属性修改为一个绝对路径(或相对路径),则页面会自动跳转到该路径,并生成相应的历史记录。 **window.location.href 是异步代码:** 需要特别注意的是:window.location.href的赋值,并不会中断Javascript的执行立即进行页面跳转。因为 LocationChange 行为在浏览器内核中是起定时器异步执行的。异步执行的好处是为了防止代码调用过深,导致栈溢出,另外也是为了防止递归进入加载逻辑,导致状态紊乱,保证导航请求是顺序执行的。 解决办法:在 location.href 的下一行,加上 return 即可。意思是,执行了 location.href 之后,就不要再继续往下执行了。 参考链接:[location.href的异步机制](https://juejin.cn/post/6844904040518647815) ### Location 对象的方法 **方法1**: ```javascript location.assign(str); ``` 解释:用来跳转到其他的页面,作用和直接修改`location.href`一样。 **方法2**: ```javascript location.reload(); ``` 解释:用于重新加载当前页面,作用和刷新按钮一样。 代码举例: ```javascript location.reload(); // 重新加载当前页面。 location.reload(true); // 在方法的参数中传递一个true,则会强制清空缓存刷新页面。 ``` **方法3**: ```javascript location.replace(); ``` 解释:使用一个新的页面替换当前页面,调用完毕也会跳转页面。但不会生成历史记录,不能使用「后退按钮」后退。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20190101.png) ================================================ FILE: 04-JavaScript基础/46-定时器.md ================================================ --- title: 46-定时器 --- ## 定时器的常见方法 - setInterval():循环调用。将一段代码,**每隔一段时间**执行一次。(循环执行) - setTimeout():延时调用。将一段代码,等待一段时间之后**再执行**。(只执行一次) 备注:在实际开发中,二者是可以根据需要,互相替代的。 ## setInterval() 的使用 `setInterval()`:循环调用。将一段代码,**每隔一段时间**执行一次。(循环执行) **参数**: - 参数1:回调函数,该函数会每隔一段时间被调用一次。 - 参数2:每次调用的间隔时间,单位是毫秒。 **返回值**:返回一个Number类型的数据。这个数字用来作为定时器的**唯一标识**,方便用来清除定时器。 ### 定义定时器 **方式一**:匿名函数 每间隔一秒,将 数字 加1: ```javascript let num = 1; setInterval(function () { num ++; console.log(num); }, 1000); ``` **方式二:** 每间隔一秒,将 数字 加1: ```javascript setInterval(fn,1000); function fn() { num ++; console.log(num); } ``` ### 清除定时器 定时器的返回值是作为这个定时器的**唯一标识**,可以用来清除定时器。具体方法是:假设定时器setInterval()的返回值是`参数1`,那么`clearInterval(参数1)`就可以清除定时器。 setTimeout()的道理是一样的。 代码举例: ```html ``` ## setTimeout() 的使用 `setTimeout()`:延时调用。将一段代码,等待一段时间之后**再执行**。(只执行一次) **参数**: - 参数1:回调函数,该函数会每隔一段时间被调用一次。 - 参数2:每次调用的间隔时间,单位是毫秒。 **返回值**:返回一个Number类型的数据。这个数字用来作为定时器的**唯一标识**,方便用来清除定时器。 ### 定义和清除定时器 代码举例: ```javascript const timer = setTimeout(function() { console.log(1); // 3秒之后,再执行这段代码。 }, 3000); clearTimeout(timer); ``` 代码举例:(箭头函数写法) ```javascript setTimeout(() => { console.log(1); // 3秒之后,再执行这段代码。 }, 3000); ``` ### setTimeout() 举例:5秒后关闭网页两侧的广告栏 假设网页两侧的广告栏为两个img标签,它们的样式为: ```html ``` 5秒后关闭广告栏的js代码为: ```html ``` ================================================ FILE: 04-JavaScript基础/47-jQuery的介绍和选择器.md ================================================ --- title: 47-jQuery的介绍和选择器 --- ## jQuery 的介绍 ### 引入 jQuery 的原因 在用 js 写代码时,会遇到一些问题: - window.onload 事件有事件覆盖的问题,因此只能写一个事件。 - 代码容错性差。 - 浏览器兼容性问题。 - 书写很繁琐,代码量多。 - 代码很乱,各个页面到处都是。 - 动画效果很难实现。 如下图所示: ![](http://img.smyhvae.com/20180204_1710.png) jQuery的出现,可以解决以上问题。 ### 什么是 jQuery jQuery 是 js 的一个库,封装了我们开发过程中常用的一些功能,方便我们调用,提高开发效率。 js库是把我们常用的功能放到一个单独的文件中,我们用的时候,直接引用到页面里即可。 以下是jQuery的相关信息: - 官网: - 官网API文档: - 中文汉化API文档: ### 学习jQuery,主要是学什么 初期,主要学习如何使用jQuery操作DOM,其实就是学习jQuery封装好的那些API。 这些API的共同特点是:几乎全都是方法。所以,在使用jQuery的API时,都是方法调用,也就是说要加小括号(),小括号里面是相应的参数,参数不同,功能不同。 ### jQuery初体验 现在用原生 js 来写下面这一段代码: ```html
    ``` 如果用 jQuery 来写,保持其他的代码不变,` ``` ### jQuery 的两大特点 (1)**链式编程**:比如`.show()`和`.html()`可以连写成`.show().html()`。 链式编程原理:return this。 通常情况下,只有设置操作才能把链式编程延续下去。因为获取操作的时候,会返回获取到的相应的值,无法返回 this。 (2)**隐式迭代**:隐式 对应的是 显式。隐式迭代的意思是:在方法的内部会为匹配到的所有元素进行循环遍历,执行相应的方法;而不用我们再进行循环,简化我们的操作,方便我们调用。 如果获取的是多元素的值,大部分情况下返回的是第一个元素的值。 ## jQuery 的使用 ### 使用 jQuery 的基本步骤 (1)引包 (2)入口函数 (3)功能实现代码(事件处理) 如下图所示: ![](http://img.smyhvae.com/20180204_1940.png) 主要,导包的代码一定要放在js代码的最上面。 ### jQuery 的版本 jQuery 有两个大版本: - 1.x版本:最新版为 v1.11.3。 - 2.x版本:最新版为 v2.1.4(不再支持IE6、7、8)。 - 3.x版本。 PS:开发版本一般用1.10以上。 我们以 v1.11.1版本为例,下载下来后发现,里面有两个文件: ![](http://img.smyhvae.com/20180204_1950.png) 它们的区别是: - 第一个是未压缩版,第二个是压缩版。 - 平时开发过程中,可以使用任意一个版本;但是,项目上线的时候,推荐使用压缩版。 ## jQuery 的入口函数和 `$` 符号 ### 入口函数(重要) 原生 js 的入口函数指的是:`window.onload = function() {};` 如下: ```javascript //原生 js 的入口函数。页面上所有内容加载完毕,才执行。 //不仅要等文本加载完毕,而且要等图片也要加载完毕,才执行函数。 window.onload = function () { alert(1); } ``` 而 jQuery的入口函数,有以下几种写法: 写法一: ```javascript //1.文档加载完毕,图片不加载的时候,就可以执行这个函数。 $(document).ready(function () { alert(1); }) ``` 写法二:(写法一的简洁版) ```javascript //2.文档加载完毕,图片不加载的时候,就可以执行这个函数。 $(function () { alert(1); }); ``` 写法三: ```javascript //3.文档加载完毕,图片也加载完毕的时候,在执行这个函数。 $(window).ready(function () { alert(1); }) ``` **jQuery入口函数与js入口函数的区别**: 区别一:书写个数不同: - Js 的入口函数只能出现一次,出现多次会存在事件覆盖的问题。 - jQuery 的入口函数,可以出现任意多次,并不存在事件覆盖问题。 区别二:执行时机不同: - Js的入口函数是在**所有的文件资源加载**完成后,才执行。这些**文件资源**包括:页面文档、外部的js文件、外部的css文件、图片等。 - jQuery的入口函数,是在文档加载完成后,就执行。文档加载完成指的是:DOM树加载完成后,就可以操作DOM了,不用等到所有的**外部资源**都加载完成。 文档加载的顺序:从上往下,边解析边执行。 ### jQuery的`$`符号 jQuery 使用 `$` 符号原因:书写简洁、相对于其他字符与众不同、容易被记住。 jQuery占用了我们两个变量:`$` 和 jQuery。当我们在代码中打印它们俩的时候: ```html ``` 打印结果如下: ![](http://img.smyhvae.com/20180204_2014.png) 从打印结果可以看出,$ 代表的就是 jQuery。 那怎么理解jQuery里面的 `$` 符号呢? **`$` 实际上表示的是一个函数名** 如下: ```html $(); // 调用上面我们自定义的函数$ $(document).ready(function(){}); // 调用入口函数 $(function(){}); // 调用入口函数 $(“#btnShow”) // 获取id属性为btnShow的元素 $(“div”) // 获取所有的div标签元素 ``` 如上方所示,jQuery 里面的 `$` 函数,根据传入参数的不同,进行不同的调用,实现不同的功能。返回的是jQuery对象。 jQuery这个js库,除了` $` 之外,还提供了另外一个函数:jQuery。jQuery函数跟 `$` 函数的关系:`jQuery === $`。 ## js中的DOM对象 和 jQuery对象 比较(重点,难点) ### 二者的区别 通过 jQuery 获取的元素是一个**数组**,数组中包含着原生JS中的DOM对象。举例: 针对下面这样一个div结构: ```html
    ``` 通过原生 js 获取这些元素节点的方式是: ```javascript var myBox = document.getElementById("box"); //通过 id 获取单个元素 var boxArr = document.getElementsByClassName("box"); //通过 class 获取的是数组 var divArr = document.getElementsByTagName("div"); //通过标签获取的是数组 ``` 通过 jQuery 获取这些元素节点的方式是:(获取的都是数组) ```javascript //获取的是数组,里面包含着原生 JS 中的DOM对象。 var jqBox1 = $("#box"); var jqBox2 = $(".box"); var jqBox3 = $("div"); ``` 我们打印出来看看: ![](http://img.smyhvae.com/20180204_2045.png) 上图显示,由于JQuery 自带了 css()方法,我们还可以直接在代码中给 div 设置 css 属性。 **总结**:jQuery 就是把 DOM 对象重新包装了一下,让其具有了 jQuery 方法。 ### 二者的相互转换 **1、 DOM 对象 转为 jQuery对象**: ```javascript $(js对象); ``` 举例:(拿上一段的代码举例) ```javascript //转换。 jqBox1 = $(myBox); jqBox2 = $(boxArr); jqBox3 = $(divArr); ``` DOM 对象转换成了 jquery 对象之后,上面的功能可以直接调用。 **2、jQuery对象 转为 DOM 对象**: ```javascript jquery对象[index]; //方式1(推荐) jquery对象.get(index); //方式2 ``` jQuery对象转换成了 DOM 对象之后,可以直接调用 DOM 提供的一些功能。如: ```javascript //jquery对象转换成 DOM 对象之后 jqBox3[0].style.backgroundColor = "black"; jqBox3.get(4).style.backgroundColor = "pink"; ``` **总结**:如果想要用哪种方式设置属性或方法,必须转换成该类型。 ### 举例:隔行变色 代码如下: ```html
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    ``` 效果如下: ![](http://img.smyhvae.com/20180204_2111.png) ## jQuery 选择器 我们以前在CSS中学习的选择器有: ![](http://img.smyhvae.com/20180204_2122.png) 今天来学习一下jQuery 选择器。 jQuery选择器是jQuery强大的体现,它提供了一组方法,让我们更加方便的获取到页面中的元素。 ### 1、jQuery 的基本选择器 ![](http://img.smyhvae.com/20180204_2125.png) 解释如下: ![](http://img.smyhvae.com/20180204_2126.png) 举例: ```html
    ``` 效果如下: ![](http://img.smyhvae.com/20180204_2133.png) ### 2、层级选择器 ![](http://img.smyhvae.com/20180204_2138.png) 解释如下: ![](http://img.smyhvae.com/20180204_2139.png) 举例: ```html
    • 111
    • 222
    • 333
      1. aaa
      2. bbb
      3. ccc
    ``` 效果: ![](http://img.smyhvae.com/20180204_2145.png) ### 3、基本过滤选择器 ![](http://img.smyhvae.com/20180204_2150.png) 解释: ![](http://img.smyhvae.com/20180204_2151.png) 举例: ```html ``` ### 4、属性选择器 ![](http://img.smyhvae.com/20180204_2155.png) ### 5、筛选选择器 ![](http://img.smyhvae.com/20180204_2200.png) 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180204_2203.png) ## 举例 ### 举例1:鼠标悬停时,弹出下拉菜单【重要】 完整版代码: ```html ``` 上方代码中,我们可以看到,用 jQuery来操作,是非常方便的。 实现效果如下: ![](http://img.smyhvae.com/20180205_1030.gif) **this的用法:** 上方代码中,核心的一行代码是: ```javascript $(this).children("ul").show(); $(this).children("ul").hide(); ``` 如果我把这行代码中的this直接写成 DOM对象: ```javascript jqli.children("ul").show(); jqli.children("ul").hide(); ``` 产生的结果是:(不是我们期望的结果) ![](http://img.smyhvae.com/20180205_1050.gif) 两张图的对比,可以看出this的作用:谁正在调用函数,this就指的是谁。 ### 举例2:鼠标悬停时变色 完整版代码如下: ```html
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    • 生命壹号,永不止步
    ``` 效果如下: ![](http://img.smyhvae.com/20180205_1100.gif) ### 举例3:突出显示 要求:鼠标悬停时,突出显示这个li,让其他的li都半透明。 用 jQuery的选择起来实现,会发现非常方便。 完整版代码如下: ```html
    ``` 实现的效果: ![](http://img.smyhvae.com/20180205_1118_2.gif) 注意这里的css布局里,每一个图片都用一个li来存放。设置li的父亲的宽度之后,然后将li设置为浮动,即可自适应地排列成两排。 ~工程文件~: - 2018-02-05-突出显示.rar - 下载链接暂无 ### 举例4:手风琴效果 完整版代码: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180205_1120.gif) 注意这里的 选择器的用法:parent、next ### 举例5:淘宝精品服饰广告 完整版代码: ```html ``` 效果: ![](http://img.smyhvae.com/20180205_1135.gif) ~工程文件~: - [2018-02-05-淘宝精品服饰广告.rar]() - 下载链接暂无。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 04-JavaScript基础/48-jQuery动画详解.md ================================================ --- title: 48-jQuery动画详解 --- ## 前言 jQuery提供的一组网页中常见的动画效果,这些动画是标准的、有规律的效果;同时还提供给我们了自定义动画的功能。 ## 显示动画 方式一: ```javascript $("div").show(); ``` 解释:无参数,表示让指定的元素直接显示出来。其实这个方法的底层就是通过`display: block;`实现的。 方式二: ```javascript $("div").show(2000); ``` 解释:通过控制元素的宽高、透明度、display属性,逐渐显示,2秒后显示完毕。 效果如下: ![](http://img.smyhvae.com/20180205_1358.gif) 方式三: ```javascript $("div").show("slow"); ``` 参数可以是: - slow 慢:600ms - normal 正常:400ms - fast 快:200ms 解释:和方式二类似,也是通过控制元素的宽高、透明度、display属性,逐渐显示。 方式四: ```javascript //show(毫秒值,回调函数; $("div").show(5000,function () { alert("动画执行完毕!"); }); ``` 解释:动画执行完后,立即执行回调函数。 **总结:** 上面的四种方式几乎一致:参数可以有两个,第一个是动画的执行时长,第二个是动画结束后执行的回调函数。 ## 隐藏动画 方式参照上面的show()方法的方式。如下: ```javascript $(selector).hide(); $(selector).hide(1000); $(selector).hide("slow"); $(selector).hide(1000, function(){}); ``` **显示和隐藏的来回切换:** 显示和隐藏的来回切换采用的是toggle()方法:就是先执行show(),再执行hide()。 同样是四种方式: ```javascript $(selector).toggle(); ``` ## 滑入和滑出 **1、滑入动画效果**:(类似于生活中的卷帘门) ```javascript $(selector).slideDown(speed, 回调函数); ``` 解释:下拉动画,显示元素。 注意:省略参数或者传入不合法的字符串,那么则使用默认值:400毫秒(同样适用于fadeIn/slideDown/slideUp) **2 滑出动画效果:** ```javascript $(selector).slideUp(speed, 回调函数); ``` 解释:上拉动画,隐藏元素。 **3、滑入滑出切换动画效果:** ```javascript $(selector).slideToggle(speed, 回调函数); ``` 参数解释同show()方法。 举例: ```html
    ``` ![](http://img.smyhvae.com/20180205_1420.gif) ## 淡入淡出动画 1、淡入动画效果: ```javascript $(selector).fadeIn(speed, callback); ``` 作用:让元素以淡淡的进入视线的方式展示出来。 2、淡出动画效果: ```javascript $(selector).fadeOut(1000); ``` 作用:让元素以渐渐消失的方式隐藏起来 3、淡入淡出切换动画效果: ```javascript $(selector).fadeToggle('fast', callback); ``` 作用:通过改变透明度,切换匹配元素的显示或隐藏状态。 参数的含义同show()方法。 代码举例: ```html
    ``` ## 自定义动画 ```javascript $(selector).animate({params}, speed, callback); ``` 作用:执行一组CSS属性的自定义动画。 - 第一个参数表示:要执行动画的CSS属性(必选) - 第二个参数表示:执行动画时长(可选) - 第三个参数表示:动画执行完后,立即执行的回调函数(可选) 代码举例: ```html
    ``` ## 停止动画 ```javascript $(selector).stop(true, false); ``` **里面的两个参数,有不同的含义。** 第一个参数: - true:后续动画不执行。 - false:后续动画会执行。 第二个参数: - true:立即执行完成当前动画。 - false:立即停止当前动画。 PS:参数如果都不写,默认两个都是false。实际工作中,直接写stop()用的多。 **效果演示:** 当第二个参数为true时,效果如下: ![](http://img.smyhvae.com/20180205_1445.gif) 当第二个参数为false时,效果如下: ![](http://img.smyhvae.com/20180205_1450.gif) 这个**后续动画**我们要好好理解,来看个例子。 **案例:鼠标悬停时,弹出下拉菜单(下拉时带动画)** ```html ``` 效果如下: ![](http://img.smyhvae.com/20180205_1500.gif) 上方代码中,关键的地方在于,用了stop函数,再执行动画前,先停掉之前的动画。 如果去掉stop()函数,效果如下:(不是我们期望的效果) ![](http://img.smyhvae.com/20180205_1505.gif) ### stop方法的总结 当调用stop()方法后,队列里面的下一个动画将会立即开始。 但是,如果参数clearQueue被设置为true,那么队列面剩余的动画就被删除了,并且永远也不会执行。 如果参数jumpToEnd被设置为true,那么当前动画会停止,但是参与动画的每一个CSS属性将被立即设置为它们的目标值。比如:slideUp()方法,那么元素会立即隐藏掉。如果存在回调函数,那么回调函数也会立即执行。 注意:如果元素动画还没有执行完,此时调用stop()方法,那么动画将会停止。并且动画没有执行完成,那么回调函数也不会被执行。 ## 举例:右下角的弹出广告 代码实现: ```html
    我是内容
    ``` 效果如下: ![](http://img.smyhvae.com/20180205_2000.gif) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 04-JavaScript基础/49-jQuery操作DOM.md ================================================ --- title: 49-jQuery操作DOM --- ## 文本主要内容 - 样式和类操作 - 节点操作 ## 样式操作和类操作 作用:设置或获取元素的样式属性值。 ### 样式操作 **1、设置样式:** ```javascript //设置单个样式: css(属性,值); $("div").css("background-color","red"); //设置多个样式: css(json); $("div").css({"width":100,"height":100,"background-color":"pink"}); ``` **2、获取样式:** ```javascript //获取样式:css(属性); //获取的时候如果有很多个,那么获取jquery对象中的第一个 alert($("div").css("width")); ``` 举例如下: ![](http://img.smyhvae.com/20180205_1315.png) ### 类操作(className) **1、添加类样式:** ```javascript $(selector).addClass("liItem"); //为指定元素添加类className ``` 注意:此处类名不带点,所有类操作的方法类名都不带点。 **2、移除类样式:** ```javascript $(selector).removeClass("liItem"); //为指定元素移除类 className $(selector).removeClass(); //不指定参数,表示移除被选中元素的所有类 ``` **3、判断有没有类样式:** ```javascript $(selector).hasClass("liItem"); //判断指定元素是否包含类 className ``` 此时,会返回true或false。jquery对象中,只要有一个带有指定类名的就是true,所有都不带才是false。 举例: ```html
    ``` **4、切换类样式:** ```javascript $(selector).toggleClass(“liItem”); //为指定元素切换类 className,该元素有类则移除,没有指定类则添加。 ``` 解释:为指定元素切换类 className,该元素有类则移除,没有指定类则添加。 如果采用采用正常的思路实现上面这句话,代码是: ```javascript if($("div").hasClass("current")){ //如果有类名,那么删除 $("div").removeClass("current") }else{ //如果没有类名,那么添加 $("div").addClass("current") } ``` 现在有了toggleClass()方法,一行代码即可实现。 举例: ```html
    ``` 实现的效果: ![](http://img.smyhvae.com/20180205_1330.gif) ### 样式操作和类操作的比较 - 操作的样式非常少,那么可以通过`.css()`实现。 - 操作的样式很多,建议通过使用类 class 的方式来操作。 - 如果考虑以后维护方便(把CSS从js中分离出来)的话,推荐使用类的方式来操作。 **举例**:addClass+removeClass 代码实现: ```html
    • 国际大牌
    • 国妆名牌
    • 清洁用品
    • 男士精品
    ``` 上方代码中,我们注意,tab栏和下方图片栏的index值,一一对应,这里用得很巧妙。 效果: ![](http://img.smyhvae.com/20180205_1345.gif) ## jQuery 的节点操作 ### 动态创建元素 原生 js 有[三种动态创建元素](07-DOM操作练习:innerHTML的方式创建元素.md)的方式。这里我们学一下 jQuery 动态创建元素。**注意,创建的是 jQuery 对象**。 方式一: ```javascript var $spanNode1 = $("我是一个span元素"); // 返回的是 jQuery对象 ``` 此方法类似于 原生 js 中的`document.createElement("标签名");` 方式二:(推荐) ```javascript var node = $("#box").html("
  • 我是li
  • "); ``` 此方法类似于 原生 js 中的`innerHTML`。 举例: ```javascript //方式一: $("标签") :类比于js中的document.createElement("li"); console.log($("
  • 我是li标签
  • ")); //方式二: $("ul").html(""); :类比innerHTML属性。因为此属性,识别标签。 $("ul").html("
  • 我是html方法穿件出来的li标签
  • ") ``` ### 添加元素 jQuery 添加元素的方法非常多,最重要的方法是`append()`。格式如下: ```javascript // 方式一:在$(selector)中追加$node $(selector).append($node); //参数是 jQuery对象 // 方式二:在$(selector)中追加div元素, $(selector).append('
    '); //参数是 htmlString ``` 作用:在被选元素内部的最后一个子元素(或内容)后面插入内容(存在或者创建出来的元素都可以)。 通俗的解释:**在盒子里的最末尾添加元素**。 - 如果是页面中存在的元素,那调用append()后,会把这个元素放到相应的目标元素里面去;但是,原来的这个元素,就不存在了。 - 如果是给多个目标追加元素,那么方法的内部会复制多份这个元素,然后追加到多个目标里面去。 举例: ```html
    • 我是土著li
    ``` 效果: ![](http://img.smyhvae.com/20180205_2020.gif) **其他的添加元素的方法:** 方法2: ```javascript $(selector).appendTo(node); ``` 作用:同append(),只不过是反着写的。 方法3: ```javascript $(selector).prepend(node); ``` 作用:在元素的第一个子元素前面追加内容或节点。 方法4: ```javascript $(selector).after(node); ``` 作用:在被选元素之后,作为**兄弟元素**插入内容或节点。 **方法5:** ```javascript $(selector).before(node); ``` 作用:在被选元素之前,作为**兄弟元素**插入内容或节点。 ### 清空元素 方式一:没有参数 ```javascript $(selector).empty(); $(selector).html(""); ``` 解释:清空指定元素的所有子元素(光杆司令)。 方式二: // ```javascript $(selector).remove(); ``` 解释:“自杀” 。把自己以及所有的内部元素从文档中删除掉。 ### 复制元素 格式: ```javascript 复制的新元素 = $(selector).clone(); ``` 解释:复制$(selector)这个元素。是深层复制。 ### 总结 推荐使用 `html("")` 方法来创建元素或者 `html("")` 清空元素。 ### 案例:选择水果 完整版代码: ```html ``` 效果: ![](http://img.smyhvae.com/20180205_2040.gif) ### 案例:动态添加表格项 代码如下: ```html
    标题 地址 说明
    ``` 实现的效果: ![](http://img.smyhvae.com/20180205_2045.gif) 代码解释:每次生成字符串str之前,记得先把之前的str清空,不然每次点击按钮,都会继续添加表格项。 ### 将上一个案例进一步提升:点击按钮,添加数据 暂略。 ## jQuery 设置和获取属性 jQuery 无法直接操作节点的属性和src等,我们需要借助attr()方法。下面介绍。 ### 属性操作 **(1)设置属性:** ```javascript $(selector).attr("title", "千古壹号"); ``` 参数解释:第一个参数表示:要设置的属性名称。第二个参数表示:该属性名称对应的值。 **(2)获取属性:** ```javascript $(selector).attr("title"); ``` 参数为:要获取的属性的名称,返回指定属性对应的值。 **总结**:两个参数是给属性赋值,单个参数是获取属性值。 **(3)移除属性:** ```javascript $(selector).removeAttr("title"); ``` 参数为:要移除的属性的名称。 (4)form表单中的 `prop()`方法: 针对`checked、selected、disabled`属性,要使用 `prop()`方法,而不是其他的方法。 prop方法通常用来影响DOM元素的动态状态,而不是改变的HTML属性。例如:input和button的disabled特性,以及checkbox的checked特性。 总结:细节可以参考:。 以上四项的代码演示: ```html ``` 效果: ![](http://img.smyhvae.com/20180205_2115.gif) **案例:表格案例全选反选** 完整版代码: ```html
    课程名称 所属团队
    JavaScript 千古壹号
    css 千古壹号
    html 千古壹号
    jQuery 千古壹号
    ``` ### val()方法和 text()方法 ```javascript $(selector).val(); ``` 作用:设置或返回 form 表单元素的value值,例如:input、select、textarea 的值。 ```javascript $(selector).text(); ``` 作用:设置或获取匹配元素的文本内容。不带参数表示,会把所有匹配到的元素内容拼接为一个**字符串**,不同于其他获取操作。 ```javascript $(selector).text("我是内容"); ``` 作用:设置的内容包含html标签,那么text()方法会把他们当作**纯文本**内容输出。 总结: - text() 不识别标签。 - html() 识别标签。 举例: ```html
  • 你好
  • ``` 打印结果: ![](http://img.smyhvae.com/20180205_2139.png) ================================================ FILE: 04-JavaScript基础/50-jQuery的事件机制和其他知识.md ================================================ --- title: 50-jQuery的事件机制和其他知识 --- ## jQuery 设置宽度和高度 宽度操作: ```javascript $(selector).height(); //不带参数表示获取高度 $(selector).height(200); //带参数表示设置高度 ``` 宽度操作: ```javascript $(selector).width(); //不带参数表示获取宽度 $(selector).width(200); //带参数表示设置高宽度 ``` **问题**:jQuery的css()获取高度,和jQuery的height获取高度,二者的区别? 答案: ```javascript $("div").css(); //返回的是string类型,例如:30px $("div").height(); //返回得失number类型,例如:30。常用于数学计算。 ``` 如上方代码所示,`$("div").height();`返回的是number类型,常用于数学计算。 ## jQuery 的坐标操作 ### offset()方法 ```javascript $(selector).offset(); $(selector).offset({left:100, top: 150}); ``` 作用:获取或设置元素相对于 document 文档的位置。参数解释: - 无参数:表示获取。返回值为:{left:num, top:num}。返回值是相对于document的位置。 - 有参数:表示设置。参数建议使用 number 数值类型。 注意:设置offset后,如果元素没有定位(默认值:static),则被修改为relative。 ### position()方法 ```javascript $(selector).position(); ``` 作用:获取相对于其最近的**带有定位**的父元素的位置。返回值为对象:`{left:num, top:num}`。 注意:只能获取,不能设置。 ### scrollTop()方法 ```javascript scrollTop(); $(selector).scrollTop(100); ``` 作用:获取或者设置元素被卷去的头部的距离。参数解释: - 无参数:表示获取偏移。 - 有参数:表示设置偏移,参数为数值类型。 ### scrollLeft()方法 ```javascript scrollLeft(); $(selector).scrollLeft(100); ``` 作用:获取或者设置元素水平方向滚动的位置。参数解释: - 无参数:表示获取偏移。 - 有参数:表示设置偏移,参数为数值类型。 代码示范: ```html
    ``` ## jQuery的事件机制 ### 常见的事件绑定 - click(handler) 单击事件。 - blur(handler) 失去焦点事件。 - mouseenter(handler) 鼠标进入事件。 - mouseleave(handler) 鼠标离开事件。 - dbclick(handler) 双击事件。 - change(handler) 改变事件,如:文本框值改变,下拉列表值改变等。 - focus(handler) 获得焦点事件。 - keydown(handler) 键盘按下事件。 参考链接: ### on方式绑定事件 最早采用的是 bind、delegate等方式绑定的。jQuery 1.7版本后,jQuery用on统一了所有的事件处理的方法,此方法兼容zepto(移动端类似于jQuery的一个库)。 格式举例: ```javascript $(document).on("click mouseenter", ".box", {"name": 111}, function (event) { console.log(event.data); //event.data获取的就是第三个参数这个json。 console.log(event.data.name); //event.data.name获取的是name的值。 }); ``` 参数解释: - 第一个参数:events,绑定事件的名称可以是由空格分隔的多个事件(标准事件或者自定义事件)。上方代码绑定的是单击事件和鼠标进入事件。 - 第二个参数:selector, 执行事件的后代元素。 - 第三个参数:data,传递给事件处理函数的数据,事件触发的时候通过event.data来使用(也就是说,可以通过event拿到data) - 第四个参数:handler,事件处理函数。 简单点的写法: ```javascript $(document).on("click",".box", function () { alert(1); }); ``` ### off方式解绑事件 ```javascript $(selector).off(); // 解绑匹配元素的所有事件 $(selector).off("click"); // 解绑匹配元素的所有click事件 $(selector).off( "click", "**" ); // 解绑所有代理的click事件,元素本身的事件不会被解绑 ``` ## jQuery的事件对象 event.data 传递给事件处理程序的额外数据 event.currentTarget 等同于this,当前DOM对象 event.pageX 鼠标相对于文档左部边缘的位置 event.target 触发事件源,不一定===this event.stopPropagation(); 阻止事件冒泡 event.preventDefault(); 阻止默认行为 event.type 事件类型:click,dbclick… event.which 鼠标的按键类型:左1 中2 右3 event.keyCode 键盘按键代码 代码演示: ```html ``` 上方代码中,我们通过event参数,获取了点击事件的各种event里的属性。 单击网页后,打印结果为: ![](http://img.smyhvae.com/20180205_2338.png) **举例**:键盘上对的按键按下时,变色 这个时候就要用到event参数,因为要获取event.keyCode,才能知道按下的是键盘上的哪个按键。 代码实现: ```html

    按键改变颜色

    keyCode为: 80
    ``` ## jQuery 的两大特点 (1)**链式编程**:比如`.show()`和`.html()`可以连写成`.show().html()`。 链式编程原理:return this。 通常情况下,只有设置操作才能把链式编程延续下去。因为获取操作的时候,会返回获取到的相应的值,无法返回 this。 ```javascript end(); // 结束当前链最近的一次过滤操作,并且返回匹配元素之前的状态。 ``` (2)**隐式迭代**:隐式 对应的是 显式。隐式迭代的意思是:在方法的内部会为匹配到的所有元素进行循环遍历,执行相应的方法;而不用我们再进行循环,简化我们的操作,方便我们调用。 如果获取的是多元素的值,大部分情况下返回的是第一个元素的值。 **举例:**五角星评分 ```html 五角星评分案例
    ``` 上方代码中,注意end的用法,很巧妙。 实现效果: ![](http://img.smyhvae.com/20180206_1100.gif) ## each的用法 大部分情况下是不需要使用each方法的,因为jQuery的隐式迭代特性。 但是,如果要对每个元素做不同的处理,这时候就用到了each方法。 格式如下: ```javascript $(selector).each(function(index,element){}); ``` 参数解释: - 参数一:表示当前元素在所有匹配元素中的索引号 - 参数二:参数二表示当前元素(是js 中的DOM对象,而不是jQuery对象) 举例: ```html
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    ``` 效果如下: ![](http://img.smyhvae.com/20180206_1110.png) ## 多库共存 **多库共存**指的是:jQuery占用了 `$` 和 `jQuery` 这两个变量。当在同一个页面中引用了 jQuery 库以及其他的库(或者其他版本的jQuery库),恰好其他的库中也用到了 `$` 或者`jQuery`变量.那么,要保证每个库都能正常使用,就产生了多库共存的问题。 温馨提示:我们可以通过以下方式获取 jQuery 库的版本号。 ```javascript console.log($.fn.jquery); //打印 jQuery 库的版本号 ``` **办法一**:让 jQuery 放弃对 `$` 的使用权: ```javascript $.noConflict(); ``` 效果如下: ![](http://img.smyhvae.com/20180206_1126.png) 上图中,代码中同时包含了两个版本的库。1.11.1版本放弃了对 `$` 的使用权,交给了1.8.2版本;但是1.11.1版本并没有放弃对 `jQuery`关键字的使用权。 办法二:同时放弃放弃两个符号的使用权,并定义一个新的使用权(如果有三个库时,可以这样用) ```javascript $.noConflict(true); //返回值是新的关键字 ``` 效果如下: ![](http://img.smyhvae.com/20180206_1133.png) ## jQuery 的插件机制 jQuery 库,虽然功能强大,但也不是面面俱到。jQuery 是通过插件的方式,来扩展它的功能: - 当你需要某个插件的时候,你可以“安装”到jQuery上面,然后使用。 - 当你不再需要这个插件,那你就可以从jQuery上“卸载”它。 ### 插件之改变颜色 jQuery的自定义动画方法animate(),在执行动画时,是不支持设置背景色这个属性的。这个时候可以借助`jQuery.color.js`这个插件。 举例: ```html
    ``` 效果: ![](http://img.smyhvae.com/20180206_1400.gif) 上方代码中,因为加入了一行插件:(注意顺序是放在jQuery插件之后) ```html ``` 否则的话,在动画执行的过程中,是无法设置背景色的。 ### 插件之懒加载 懒加载:当打开一个网页时,只有当我看到某个部分,再加载那个部分;而不是一下子全部加载完毕。这样可以优化打开的速度。 比如说,我可以设置一张图片为懒加载,于是,这张图片会等我宠幸到它的时候,它再打开。 代码举例: ```html
    ``` ================================================ FILE: 04-JavaScript基础/51-Zepto入门.md ================================================ --- title: 51-Zepto入门 --- ## Zepto 的介绍 ### 什么是 Zepto zepto是轻量级的JavaScript库,专门为移动端定制的框架。 与jquery有着类似的API,俗称:会jquery就会用zepto ### zepto的特点 - 针对移动端 - 轻量级,压缩版本只有8kb左右 - 响应,执行快 - 语法、API大部分同jquery一样,学习难度低,上手快。 - 目前API完善的框架中体积最小的一个 ### 相关网址 - 官网: - GitHub: ## Zepto 与 jQuery 的前世今生 ### 相同点 - 都是优秀的js函数库 - 语法、API大部分都一样(zepto是按照jquery的思路来设计的) - Zepto 相当于 jQuery 的子集 - 同jQuery一样,都是以`$`符号为核心函数。 ### 不同点 ## Zepto 的初体验 (1)Zepto 库的下载: 我们去官网下载 Zepto的开发版本`zepto.js`: ![](http://img.smyhvae.com/20180414_2210.png) 官网里,还有这样一张图: ![](http://img.smyhvae.com/20180414_2215.png) 上图的意思是: - 最前面打钩的那五个api,已经包含在`zepto.js `文件里了; - 后面没有打钩的那些api,如果需要用它们,必须单独下载响应的文件。 比如说,移动端的 touch 事件是很常见的,我们可以将`touch.js`这个文件下载,稍后用。 (2)代码演示: ```html Document
    我是 div
    ``` 上方代码实现的效果是,当手在div上滑动时,就会弹出 alert窗。可以看出,这里面代码的写法和 jQuery 是一致的。 注意,我们要将浏览器切换到手机模式,才能看到`touchstart`事件的效果;否则,在浏览器上点来点去,是没有反应的。 ## Zepto 和 jQuery 相同的 api > 意思是,jQuery 和 Zepto 有哪些共同点。 ### jQuery 的主要特性 下面来讲一下 jQuery 的主要特性(jQuery 的核心函数`$`、jQuery 对象),它们对 Zepto 来说,同样适用。 **1、jQuery 的核心函数`$`**: 作为函数使用(参数): - function - html字符串 - DOM code - 选择器字符串 作为对象调用(方法): - $.ajax() $.get() $.post() - $.isArray() $.each() $.isFunction() $.trim() **2、jQuery 对象**: 概念:jquery核心函数$()调用返回的对象就是jquery对象的数组(可能有只有一个)。 使用列举: - addClass() - removeClass() - show() - find() ### 代码举例 1、`$.each()`方法举例:(遍历数组) ```html ``` 打印结果: ![](http://img.smyhvae.com/20180416_1145.png) 2、`append()`举例: ```html Document
    ``` 上方代码实现的效果是:每次,当手在box1上滑动时,会在 box1 中新添加一个元素。 4、`find()`方法举例: ```javascript $('.box1').on('touchstart', function () { console.log('touch'); $(this).find('p').css('background', 'red'); }); ``` 代码解释:找到 box1 中的 p 标签, 给 p 标签设置背景色。 注意,代码里的`$(this).find()`相当于`this.find`,只不过this没有find方法,而$有find方法。 5、`show()`方法举例: ```javascript $(`.box1`).on('touchstart', function () { $('.box2').show(); }); ``` 假设 box2 一开始是隐藏的,事件中,让 box2 显示出来。 ================================================ FILE: 04-JavaScript基础/BOM的常见内置方法和内置对象.md ================================================ --- publish: false --- > 本文最初发表于[博客园](http://www.cnblogs.com/smyhvae/p/8401662.html),并在[GitHub](https://github.com/qianguyihao/Web)上持续更新**前端的系列文章**。欢迎在GitHub上关注我,一起入门和进阶前端。 > 以下是正文。 ## BOM的介绍 ### JavaScript的组成 JavaScript基础分为三个部分: - ECMAScript:JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。 - **DOM**:文档对象模型,操作**网页上的元素**的API。比如让盒子移动、变色、轮播图等。 - **BOM**:浏览器对象模型,操作**浏览器部分功能**的API。比如让浏览器自动滚动。 ### 什么是BOM BOM:Browser Object Model,浏览器对象模型。 **BOM的结构图:** ![](http://img.smyhvae.com/20180201_2052.png) 从上图也可以看出: - **window对象是BOM的顶层(核心)对象**,所有对象都是通过它延伸出来的,也可以称为window的子对象。 - DOM越是BOM的一部分。 **window对象:** - **window对象是JavaScript中的顶级对象**。 - 全局变量、自定义函数也是window对象的属性和方法。 - window对象下的属性和方法调用时,可以省略window。 下面讲一下 **BOM 的常见内置方法和内置对象**。 ## 弹出系统对话框 比如说,`alert(1)`是`window.alert(1)`的简写,因为它是window的子方法。 系统对话框有三种: ```javascript alert(); //不同浏览器中的外观是不一样的 confirm(); //兼容不好 prompt(); //不推荐使用 ``` ## 打开窗口、关闭窗口 1、打开窗口: ``` window.open(url,target,param) ``` **参数解释:** - url:要打开的地址。 - target:新窗口的位置。可以是:`_blank` 、`_self`、 `_parent` 父框架。 - param:新窗口的一些设置。 - 返回值:新窗口的句柄。 **param**这个参数,可以填各种各样的参数(),比如: - name:新窗口的名称,可以为空 - features:属性控制字符串,在此控制窗口的各种属性,属性之间以逗号隔开。 - fullscreen= { yes/no/1/0 } 是否全屏,默认no - channelmode= { yes/no/1/0 } 是否显示频道栏,默认no - toolbar= { yes/no/1/0 } 是否显示工具条,默认no - location= { yes/no/1/0 } 是否显示地址栏,默认no。(有的浏览器不一定支持) - directories = { yes/no/1/0 } 是否显示转向按钮,默认no - status= { yes/no/1/0 } 是否显示窗口状态条,默认no - menubar= { yes/no/1/0 } 是否显示菜单,默认no - scrollbars= { yes/no/1/0 } 是否显示滚动条,默认yes - resizable= { yes/no/1/0 } 是否窗口可调整大小,默认no - width=number 窗口宽度(像素单位) - height=number 窗口高度(像素单位) - top=number 窗口离屏幕顶部距离(像素单位) - left=number 窗口离屏幕左边距离(像素单位) 各个参数之间用逗号隔开就行,但我们最好是把它们统一放到json里。 2、关闭窗口:window.close() (1)和(2)的代码举例: ```html 点击我打开一个新的页面 点击我关闭本页面 ``` 3、新窗口相关: - 新窗口.moveTo(5,5) - 新窗口.moveBy() - 新窗口.resizeTo() - window.resizeBy() 代码举例: ```javascript var newWin = window.open("demo.html", "_blank", json); newWin.moveTo(500, 500); ``` ## location对象 `window.location`可以简写成location。location相当于浏览器地址栏,可以将url解析成独立的片段。 ### location对象的属性 - **href**:跳转 - hash 返回url中#后面的内容,包含# - host 主机名,包括端口 - hostname 主机名 - pathname url中的路径部分 - protocol 协议 一般是http、https - search 查询字符串 **location.href属性举例**: **举例1:**点击盒子时,进行跳转。 ```html
    smyhvae
    ``` **举例2:5秒后自动跳转到百度**。 有时候,当我们访问一个不存在的网页时,会提示5秒后自动跳转到指定页面,此时就可以用到location。举例: ```html ``` ### location对象的方法 - location.assign():改变浏览器地址栏的地址,并记录到历史中 设置location.href 就会调用assign()。一般使用location.href 进行页面之间的跳转。 - location.replace():替换浏览器地址栏的地址,不会记录到历史中 - location.reload():重新加载 ## navigator对象 window.navigator 的一些属性可以获取客户端的一些信息。 - userAgent:系统,浏览器) - platform:浏览器支持的系统,win/mac/linux 举例: ```javascript console.log(navigator.userAgent); console.log(navigator.platform); ``` 效果如下: ![](http://img.smyhvae.com/20180201_2140.png) ================================================ FILE: 04-JavaScript基础/原型对象.md ================================================ --- publish: false --- > 在看本文之前,我们可以先复习上一篇文章:《03-JavaScript基础/12-对象的创建&构造函数.md》 ## 原型对象 ### 原型的引入 ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; //向对象中添加一个方法 this.sayName = function () { console.log("我是" + this.name); } } //创建一个Person的实例 var per = new Person("孙悟空", 18, "男"); var per2 = new Person("猪八戒", 28, "男"); per.sayName(); per2.sayName(); console.log(per.sayName == per2.sayName); //打印结果为false ``` **分析如下**: 上方代码中,我们的sayName方法是写在构造函数 Person 内部的,然后在两个实例中进行了调用。造成的结果是,**构造函数每执行一次,就会给每个实例创建一个新的 sayName 方法**。也就是说,每个实例的sayName都是唯一的。因此,最后一行代码的打印结果为false。 按照上面这种写法,假设创建10000个对象实例,就会创建10000个 sayName 方法。这种写法肯定是不合适的。我们为何不让所有的对象共享同一个方法呢? 还有一种方式是,将sayName方法在全局作用域中定义:(不建议。原因看注释) ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; //向对象中添加一个方法 this.sayName = fun; } //将sayName方法在全局作用域中定义 /* * 将函数定义在全局作用域,污染了全局作用域的命名空间 * 而且定义在全局作用域中也很不安全 */ function fun() { alert("Hello大家好,我是:" + this.name); }; ``` 比较好的方式是,在原型中添加sayName方法: ```javascript Person.prototype.sayName = function(){ alert("Hello大家好,我是:"+this.name); }; ``` 这也就引入了我们本文要讲的「原型」。 ### 原型prototype的概念 **认识1**: 我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype。这个属性对应着一个对象,这个对象就是我们所谓的原型对象。 如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的实例对象中都会有一个隐含的属性,指向该构造函数的原型,我们可以通过__proto__来访问该属性。 代码举例: ```javascript // 定义构造函数 function Person() {} var per1 = new Person(); var per2 = new Person(); console.log(Person.prototype); // 打印结果:[object object] console.log(per1.__proto__ == Person.prototype); // 打印结果:true ``` 上方代码的最后一行:打印结果表明,`实例.__proto__` 和 `构造函数.prototype`都指的是原型对象。 **认识2**: 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。 以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样就不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。 **认识3**: 使用 `in` 检查对象中是否含有某个属性时,如果对象中没有但是**原型中**有,也会返回true。 可以使用对象的`hasOwnProperty()`来检查**对象自身中**是否含有该属性。 ### 原型链 原型对象也是对象,所以它也有原型,当我们使用或访问一个对象的属性或方法时: - 它会先在对象自身中寻找,如果有则直接使用; - 如果没有则会去原型对象中寻找,如果找到则直接使用; - 如果没有则去原型的原型中寻找,直到找到Object对象的原型。 - Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回 null ### 总结 第一次接触「原型」和「原型链」的时候,会比较难理解。多接触几次,再回过头来看,就慢慢熟悉了。 ## 对象的 toString() 方法 我们先来看下面这段代码: ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } var per1 = new Person("vae", 26, "男"); console.log("per1 = " + per1); console.log("per1 = " + per1.toString()); ``` 打印结果: ``` per1 = [object Object] per1 = [object Object] ``` 上面的代码中,我们尝试打印实例 per1 的内部信息,但是发现,无论是打印 `per1` 还是打印 `per1.toString()`,结果都是`object`,这是为啥呢?分析如下: - 当我们直接在页面中打印一个对象时,其实是输出了对象的toString()方法的返回值。 - 如果我们希望在打印对象时,不输出[object Object],可以手动为对象添加一个toString()方法。意思是,重写 toString() 方法。 重写 toString() 方法,具体做法如下: ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } //方式一:重写 Person 原型的toString方法。针对 Person 的所有实例生效 Person.prototype.toString = function() { return ( "Person[name=" + this.name + ",age=" + this.age + ",gender=" + this.gender + "]" ); }; // 方式二:仅重写实例 per1 的 toString方法。但是这种写法,只对 per1 生效, 对 per2 无效 /* per1.toString = function() { return ( "Person[name=" + this.name + ",age=" + this.age + ",gender=" + this.gender + "]" ); }; */ var per1 = new Person("smyh", 26, "男"); var per2 = new Person("vae", 30, "男"); console.log("per1 = " + per1); console.log("per2 = " + per2.toString()); ``` 打印结果: ```javascript per1 = Person[name=smyh,age=26,gender=男] per2 = Person[name=vae,age=30,gender=男] ``` 代码分析: 上面的代码中,仔细看注释。我们重写了 Person 原型的 toString(),这样的话,可以保证对 Person 的所有实例生效。 从这个例子,我们可以看出 `prototype` 的作用。 ## JS的垃圾回收(GC)机制 程序运行过程中会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢。所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾。 当一个对象没有任何的变量或属性对它进行引用时,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。 上面这句话,也可以这样理解:如果堆内存中的对象,没有任何变量指向它时,这个堆内存里的对象就会成为垃圾。 JS拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁。我们不需要也不能进行垃圾回收的操作。我们仅仅需要做的是:如果你不再使用该对象,那么,将改对象的引用设置为 null 即可。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 04-JavaScript基础/原型链.md ================================================ --- publish: false --- ## 常见概念 - 构造函数 - 构造函数-扩展 - 原型规则和示例 - 原型链 - instanceof ## 构造函数 任何一个函数都可以被 new,new 了之后,就成了构造方法。 如下: ```javascript 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 多个实例对象 ``` 与普通函数相比,构造函数有以下明显特点: - 用 new 关键字调用。 - 不需要用 return 显式返回值的,默认会返回 this,也就是新的实例对象。 - 建议函数名的首字母大写,与普通函数区分开。 参考链接: - [JavaScript 中的普通函数与构造函数](http://www.cnblogs.com/SheilaSun/p/4398881.html) 当 new 之后,this 会先变成一个空对象,然后通过`this.name = name`来赋值。 ### 构造函数的扩展 ![](http://img.smyhvae.com/20180306_1633.png) 上图中发现,数组、对象、函数也有构造函数,它们的构造函数是 Array、Object、function。实际开发中,都推荐前面的书写方式。 ## 原型规则 原型规则是学习原型链的基础。原型规则有五条,下面来讲解。 ### 规则 1 所有的引用类型(数组、对象、函数),都具有对象特性,都可以**自由扩展属性**。null 除外。 举例: ![](http://img.smyhvae.com/20180306_1651.png) ### 规则 2 所有的**引用类型**(数组、对象、函数),都有一个`_proto_`属性,属性值是一个**普通的对象**。`_proto_`的含义是隐式原型。 ![](http://img.smyhvae.com/20180306_1656.png) 其实,规则 2 是规则 1 的特例,只不过,js 语法帮我们自动加了 规则 2。 ### 规则三 所有的**函数**(不包括数组、对象),都有一个`prototype`属性,属性值是一个**普通的对象**。`prototype`的含义是**显式原型**。(实例没有这个属性) ![](http://img.smyhvae.com/20180306_1659.png) ### 规则四 所有的**引用类型**(数组、对象、函数),`_proto_`属性指向它的**构造函数**的`prototype`值。 ![](http://img.smyhvae.com/20180306_1701.png) 总结:以上四条,要先理解清楚,然后再来看下面的第五条。 ### 规则五 当试图获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的`_proto_`中寻找(即它的构造函数的`prototype`)。 `举例代码1`: ```javascript //创建方法 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 ``` 上方代码中,虽然 alertName 不是 fn 自身的属性,但是会从它的构造函数的`prototype`里面找。 **扩展:**遍历循环对象自身的属性 我们知道,`for in`循环可以遍历对象。针对上面的那个 fn 对象,它自身有两个属性:`name`、`printName`,另外从原型中找到了第三个属性`alertName`。现在,如果我们对 fn 进行遍历,能遍历到两个属性还是三个属性呢? 答案:两个。因为,**高级浏览器中已经在 `for in`循环中屏蔽了来自原型的属性。但是,为了保证代码的健壮性,我们最好自己加上判断**,手动将第三个属性屏蔽掉: ```javascript for (var item in fn) { if (fn.hasOwnProperty(item)) { console.log(item); } } ``` ## 原型链 还是拿上面的`举例代码1`举例,如果此时在最后面加一行代码: ``` fn.toString(); //去 fn._proto_._proto_ 中查找 toString()方法 ``` 上面的代码中,fn 直接调用了 toString()方法,这是因为它通过**原型链**,去`_proto_`的`_proto_`里找到了`Object`,而`Object`是由`toString()`方法的。 ### instanceof 格式: ```javascript 对象 instanceof 构造函数; ``` `instanceof`的作用:用于判断**引用类型**属于哪个**构造函数**。 例 1:判断一个变量是否为数组: `变量 instanceof Array` 例 2: ```javascript function Person() {} //p--->Person.prototype--->Object.prototype--->null var p = new Person(); //构造函数的**原型**是否在 p 对象的原型链上! console.log(p instanceof Person); ``` 例 3: ```javascript fn instanceof Foo; ``` 上面这句话,判断逻辑是:**fn 的`_proto_`一层一层往上找,看能否对应到 Foo.prototype**。 原型链如下:(重要) ![](http://img.smyhvae.com/20180306_1853.png) 注意,Object 这个构造方法的显式原型是 null,这是一个特例。 issues 101 补充:通过原型链查找时,如果你找的是一个属性的话,则返回 undefined,如果你找的是一个方法,则报错。 ## 常见题目 - 如何准确判断一个变量是数组类型 - 写一个原型链继承的例子 - 描述 new 一个对象的过程 - zepto(或其他框架)源码中如何使用原型链 下面分别讲解。 ### 题目一:如何准确判断一个变量是数组类型 答案: ```javascript var arr1 = []; console.log(arr1 instanceof Array); //打印结果:true。 console.log(typeof arr1); //打印结果:object。提示:typeof 方法无法判断是否为数组 ``` 上方代码表明,只能通过 instanceof 来判断是否为数组。而 typeof 的打印结果是 object。 ### 题目二:写一个原型链继承的例子 来看个基础的代码: ![](http://img.smyhvae.com/20180306_1931.png) 上面这个例子是基础,但是,在回答面试官的问题时,不要写上面的例子。要写成下面这个例子:(更贴近实战) ```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('

    这是一段文字

    '); div1.on('click', function () { console.log('clicked'); }); ``` ### 题目三:描述 new 一个对象的过程 (1)创建一个新对象 (2)this 指向这个新对象 (3)执行代码(对 this 赋值) (4)返回 this 参考链接: - [原型、原型链、继承模式](https://my.oschina.net/u/2600761/blog/1524617) ================================================ FILE: 04-JavaScript基础/常见代码解读.md ================================================ --- publish: false --- ### `callback && callback()`的含义 ```javascript callback && callback() ``` 含义是:如果callback存在,则执行callback()函数。 这个 callback 通常作为函数的参数使用。举例: ```javascript function foo(callback) { { // do something } callback && callback() // 不传 callback 参数,则不会执行 callback() 函数 } foo(); // 只执行do something中的代码 foo(callback);//callback是另一个函数,将此函数传入 foo,将会执行callback() ``` ================================================ FILE: 05-JavaScript基础:ES6语法/01-ES5和ES6的介绍.md ================================================ --- title: 01-ES5和ES6的介绍 --- ## 前言 ### ECMAScript 简介 ES 的全称是 ECMAScript,它是由 ECMA 国际标准化组织 制定的一套**脚本语言的标准化规范**。 详细来说,ES 是由 ECMA 的第 39 号技术专家委员会(Technical Committee 39,简称 TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等公司。 简单来说,ECMAScript 是 JS 的语言标准。当然,ECMAScript 还包括其他脚本语言的语言标准。 ### ECMAScript 版本发布记录 - 1995 年:ECMAScript 诞生。 - 1997 年:ECMAScript 标准确立。ECMA 发布 ECMA-262 标准,推出浏览器标准语言 ECMAScript 1.0。 - 1999 年:发布 ES3;与此同时,IE5 风靡一时。 - **2009 年**:发布 ECMAScript 5.0(简称 **ES5**)。例如 foreach、Object.keys、Object.create 和 json 标准。 - 2011 年:发布 ECMAScript5.1,成为 ISO **国际标准**,从而推动所有浏览器都支持。 - **2015** 年 6 月:发布 ECMAScript 6(简称 **ES6**),即 ECMAScript 2015。(注意,**前者是按版本号区分,后者是按年份区分**。ES 的后续版本,请尽量用**年份**来命名。) - 2016 年 6 月:发布 ECMAScript 7,即 ECMAScript 2016。 - 2017 年 6 月:发布 ECMAScript 8,即 ECMAScript 2017。 - 2018 年 6 月:发布 ECMAScript 9,即 ECMAScript 2018。 - 2019 年 6 月:发布 ECMAScript 10,即 ECMAScript 2019。 - 2020 年 6 月:发布 ECMAScript 11,即 ECMAScript 2020。 - ...... * 此后,每年更新一版。 ### ECMAScript5.1简介 ECMAScript 5.1是ECMAScript标准的最新修正版本,所以这个版本非常重要。与ECMAScript 5.0 相比,ECMAScript 5.1的改进如下: - 对于此前不合理的地方进行了修正。 - 新增了一些新的方法。 - 新增了**严格模式**的语法。(我们将在下一篇文章讲严格模式) 推荐阅读链接: - [ECMAScript5.1规范中文版.pdf](https://yanhaijing.com/es5) - [张鑫旭翻译:ECMAScript 5.1简介](https://www.zhangxinxu.com/wordpress/2012/01/introducing-ecmascript-5-1/) ### ES6 简介 从上面的 ES 的版本记录可以看出:2015 年 6 月,ES6 正式发布。如果用年份来命名版本号,也可以称之为 ES2015。 ES6 是新的 JS 语法标准。**ES6 实际上是一个泛指,泛指 ES 2015 及后续的版本**。 很多人在做业务选型的时候,会倾向于选 jQuery。其实 jQuery 的语法是偏向于 ES3 的。而现在主流的框架 Vue.js 和 React.js 的默认语法,都是用的 ES6。 ES6 的改进如下: - ES6 之前的变量提升,会导致程序在运行时有一些不可预测性。而 ES6 中通过 let、const 变量优化了这一点。 - ES6 增加了很多功能,比如:**常量、作用域、对象代理、异步处理、类、继承**等。这些在 ES5 中想实现,比较复杂,但是 ES6 对它们进行了封装。 - ES6 之前的语法过于松散,实现相同的功能,不同的人可能会写出不同的代码。 ES6 的目标是:让 JS 语言可以编写复杂的大型应用程序,成为企业级开发语言。 推荐阅读链接: - 阮一峰 | ES6 入门教程:https://es6.ruanyifeng.com/ ### ES各个版本的浏览器兼容性情况 关于 ECMAScript各个版本的浏览器兼容性情况,可以看看 Juriy Zaytsev 统计的兼容性表格:https://kangax.github.io/compat-table/es5/ 这个网站很实用,而且还列出了每个版本里新增的主要API有哪些。 比如说,ES5的兼容性是比较好的: ![20211028_2115](https://img.smyhvae.com/20211028_2115.png) ES6在IE 11浏览器里就不兼容: ![20211028_2117](http://img.smyhvae.com/20211028_2117.png) 另外,如果我们想在ES5环境中支持ES6的API,可以通过 [ES5-shim](https://github.com/es-shims/es5-shim) 这样的工具来实现。 ## 将ES6的语法转为ES5(为了兼容 ES5) > 掌握 ES6 之后,如果你的业务需要考虑 ES5 的兼容性,则可以这样做:写 ES6 语法的 js 代码,然后通过 `Babel`将 ES6 转换为 ES5。如果没有这样的需要,那么下面的内容,了解即可。 babel 的作用是将 ES6 语法转为 ES5 语法,支持低端浏览器。 但是,在这之前,我们需要配置一下相关的环境。 ### 建立工程目录 (1)先建立一个空的工程目录 `ES6Demo`,并在目录下建立两个文件夹 `src`和 `dist`: - `src`:书写 ES6 代码,我们写的 js 程序都放在这里。 - `dist`:利用 Babel 编译生成的 ES5 代码。**我们在 HTML 页面需要引入 dist 里的 js 文件**。 (2)在 src 里新建文件 `index.html`: ```html Document ``` **注意**,上方代码中,我们引入的是`dist`目录下的 js 文件。 然后我们新建文件 `src/index.js`: ```javascript let a = 'smyhvae'; const b = 'qianguyihao'; console.log(a); console.log(b); ``` 这个文件是一个 ES6 语法 的 js 文件,稍后,我们尝试把这个 ES6 语法的 js 文件转化为 ES5 的 js 文件。 PS:我们在写代码时,能用单引号尽量用单引号,而不是双引号,前者在压缩之后,程序执行会更快。 ### 全局安装 Babel-cli (1)初始化项目: 在安装 Babel 之前,需要先用 npm init 先初始化我们的项目。打开终端或者通过 cmd 打开命令行工具,进入项目目录,输入如下命令: ```bash npm init -y ``` 上方代码中,`-y` 代表全部默认同意,就不用一次次按回车了(稍后再根据需要,在文件中手动修改)。命令执行完成后,会在项目的根目录下生成 package.json 文件: ```json { "name": "es6demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "smyhvae", "license": "ISC" } ``` PS:VS Code 里打开终端的快捷键是:`Contol + ~`。 (2)全局安装 Babel-cli: 在终端中输入以下命令: ```bash npm install -g babel-cli ``` ![](http://img.smyhvae.com/20180304_1305.png) 如果安装比较慢的话,Mac 下可以使用`cnpm`进行安装 ,windows 下可以使用`nrm`切换到 taobao 的镜像。 (3)本地安装 babel-preset-es2015 和 babel-cli: ```bash npm install --save-dev babel-preset-es2015 babel-cli ``` ![](http://img.smyhvae.com/20180304_1307.png) 安装完成后,会发现`package.json`文件,已经多了 devDependencies 选项: ![](https://img.smyhvae.com/20180304_1308.png) (4)新建.babelrc: 在根目录下新建文件`.babelrc`,输入如下内容: ``` { "presets":[ "es2015" ], "plugins":[] } ``` (5)开始转换: 现在,我们应该可以将 ES6 的文件转化为 ES5 的文件了,命令如下:(此命令略显复杂) ``` babel src/index.js -o dist/index.js ``` 我们可以将上面这个命令进行简化一下。操作如下: 在文件 `package.json` 中修改键 `scripts`中的内容: ```json "scripts": { "build": "babel src/index.js -o dist/index.js" }, ``` 修改后的效果如下: ![](https://img.smyhvae.com/20180304_1315.png) 目前为止,环境配置好了。以后,我们执行如下命令,即可将`src/index.js`这个 ES6 文件转化为 `dist/index.js`这个 ES5 文件: ```bash npm run build ``` 我们执行上面的命令之后,会发现, dist 目录下会生成 ES5 的 js 文件: index.js: ```javascript 'use strict'; var a = 'smyhvae'; var b = 'qianguyihao'; console.log(a); console.log(b); ``` 当我们打开网页后,就可以在浏览器的控制台,看到代码的输出结果。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/02-ES5中的严格模式.md ================================================ --- title: 02-ES5中的严格模式 --- > 为什么在讲ES6之前,我们需要先了解ES5?因为很多人就是在学习ES6的过程中,才接触到es5这个概念。 ## ES的几个重要版本 - ES 5 : 09年发布。 - ES 6(ES2015) : 2015年发布,也称为ECMA2015。 - ES 7(ES2016) : 2016年发布,也称为ECMA2016 (变化不大)。 ## 严格模式的理解 我们知道,JS的语法是非常灵活的,比如说,我们随便写一个变量`x`,这个变量其实是挂在 windows下面的。这种灵活性在有些情况下,反而是一种缺点,造成了全局污染。因此,ES5还引入了一种严格的运行模式:"严格模式"(strict mode)。 ### 概念 顾名思义,严格模式使得 Javascript 在更严格的语法条件下运行。限制性更强,也更安全。 **目的**: - 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为。 - 消除代码运行的一些不安全之处,为代码的安全运行保驾护航。 - 为未来新版本的Javascript做好铺垫 ### 使用 - 针对整个文件:将`use strict`放在文件的第一行,则整个文件将以严格模式运行。 - 针对单个函数:将`use strict`放在函数体的第一行,则整个函数以严格模式运行。 PS:如果浏览器不支持,则这句话只会被解析为一条简单的语句,没有任何副作用。 脚本文件的变通写法:因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。 ### 语法和行为改变 - 必须用var声明变量 - 禁止自定义的函数中的this指向window - 创建eval作用域 - 对象不能有重名的属性 ## 严格模式和普通模式的区别 > 下面列举几条严格模式的内容。 ### 全局变量显式声明 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。 ### 禁止this关键字指向全局对象: ```javascript var foo = function () { console.log(this); } foo(); ``` 上方代码中,普通模式打印的是window。严格模式下打印的是undefined。 ### 创设eval作用域 ### 禁止使用with语句 因为with语句无法在编译时就确定,属性到底归属哪个对象。 ### 构造函数必须通过new实例化对象 构造函数必须通过new实例化对象,否则报错。因为this为undefined,此时无法设置属性。 比如说: ``` var Cat = function (name) { this.name = name; } Cat('haha'); ``` 上方代码中,如果在严格模式下,则会报错。 ### 为了让代码更安全,禁止函数内部遍历调用栈 ### 严格模式下无法删除变量 ### 属性相关 普通模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。 严格模式下,对禁止扩展的对象添加新属性,会报错。 普通模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。 普通模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,多个重名的参数属于语法错误。 比如下面这样的代码: ```javascript var obj = { username: 'smyh'; username: 'vae' } ``` 上面的代码,在严格模式下属于语法错误,因为有重名的属性。 ### 函数必须声明在顶层 将来Javascript的新版本会引入"块级作用域"。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。 ### 新增关键字 为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。 ## 总结 至少要能答出四五条。 参考链接: - [阮一峰 | Javascript 严格模式详解](http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/03-ES5中的一些扩展.md ================================================ --- title: 03-ES5中的一些扩展 --- ## JSON 对象 1、js对象(数组) --> json对象(数组): ```javascript JSON.stringify(obj/arr) ``` 2、json对象(数组) --> js对象(数组): ```javascript JSON.parse(json) ``` 上面这两个方法是ES5中提供的。 我们要记住,我们通常说的“json字符串”,只有两种:**json对象、json数组**。 `typeof json字符串`的返回结果是string。 ## Object的扩展 ES5给Object扩展了一些静态方法,常用的有2个,我们接下来讲解。 ### 方法一 ```javascript Object.create(prototype, [descriptors]) ``` 作用: 以指定对象为原型,创建新的对象。同时,第二个参数可以为为新的对象添加新的属性,并对此属性进行描述。 **举例1**:(没有第二个参数时) ```javascript var obj1 = {username: 'smyhvae', age: 26}; var obj2 = {address:'shenzhen'}; obj2 = Object.create(obj1); console.log(obj2); ``` 打印结果: ![](http://img.smyhvae.com/20180401_2150.png) 我们发现,obj1成为了obj2的原型。 **举例2**:(有第二个参数时) 第二个参数可以给新的对象添加新的属性。我们修改上面的代码,尝试给obj2添加新属性`sex`: ```javascript var obj1 = {username: 'smyhvae', age: 26}; var obj2 = {address: 'shenzhen'}; obj2 = Object.create(obj1, { sex: {//给obj2添加新的属性`sex`。注意,这一行的冒号不要漏掉 value: '男', //通过value关键字设置sex的属性值 writable: false, configurable: true, enumerable: true } }); console.log(obj2); ``` 上方代码中,我们通过第5行的sex给obj2设置了一个新的属性`sex`,但是要通过`value`来设置属性值(第6行)。 设置完属性值后,这个属性值默认是不可修改的,要通过`writable`来设置。总而言之,这几个关键字的解释如下: - `value`:设置属性值。 - `writable`:标识当前属性值是否可修改。如果不写的话,默认为false,不可修改。 - `configurable`:标识当前属性是否可以被删除。默认为false,不可删除。 - `enumerable`:标识当前属性是否能用 for in 枚举。 默认为false,不可。 ### 方法二 > 这个方法有点难理解。 ```javascript Object.defineProperties(object, descriptors) ``` **作用**:为指定对象定义扩展多个属性。 代码举例: ```javascript var obj2 = { firstName : 'smyh', lastName : 'vae' }; Object.defineProperties(obj2, { fullName : { get : function () { return this.firstName + '-' + this.lastName }, set : function (data) { //监听扩展属性,当扩展属性发生变化的时候自动调用,自动调用后将变化的值作为实参注入到set函数 var names = data.split('-'); this.firstName = names[0]; this.lastName = names[1]; } } }); console.log(obj2.fullName); obj2.firstName = 'tim'; obj2.lastName = 'duncan'; console.log(obj2.fullName); obj2.fullName = 'kobe-bryant'; console.log(obj2.fullName); ``` - get :用来获取当前属性值的回调函数 - set :修改当前属性值得触发的回调函数,并且实参即为修改后的值 存取器属性:setter,getter一个用来存值,一个用来取值。 ## Object的扩展(二) obj对象本身就自带了两个方法。格式如下: ```javascript get 属性名(){} 用来得到当前属性值的回调函数 set 属性名(){} 用来监视当前属性值变化的回调函数 ``` 举例如下: ```javascript var obj = { firstName : 'kobe', lastName : 'bryant', get fullName(){ return this.firstName + ' ' + this.lastName }, set fullName(data){ var names = data.split(' '); this.firstName = names[0]; this.lastName = names[1]; } }; console.log(obj.fullName); obj.fullName = 'curry stephen'; console.log(obj.fullName); ``` ## 数组的扩展 > 下面讲的这几个方法,都是给数组的实例用的。 > 下面提到的数组的这五个方法,更详细的内容,可以看《03-JavaScript基础/15-数组的常见方法.md》 **方法1**: ```javascript Array.prototype.indexOf(value) ``` 作用:获取 value 在数组中的第一个下标。 **方法2**: ```javascript Array.prototype.lastIndexOf(value) ``` 作用:获取 value 在数组中的最后一个下标。 **方法3**:遍历数组 ```javascript Array.prototype.forEach(function(item, index){}) ``` **方法4**: ```javascript Array.prototype.map(function(item, index){}) ``` 作用:遍历数组返回一个新的数组,返回的是**加工之后**的新数组。 **方法5**: ```javascript Array.prototype.filter(function(item, index){}) ``` 作用:遍历过滤出一个新的子数组,返回条件为true的值。 ## 函数function的扩展:bind() > ES5中新增了`bind()`函数来改变this的指向。 ```javascript Function.prototype.bind(obj) ``` 作用:将函数内的this绑定为obj, 并将函数返回。 **面试题**: call()、apply()和bind()的区别: - 都能改变this的指向 - call()/apply()是**立即调用函数** - bind():绑定完this后,不会立即调用当前函数,而是**将函数返回**,因此后面还需要再加`()`才能调用。 PS:bind()传参的方式和call()一样。 **分析**: 为什么ES5中要加入bind()方法来改变this的指向呢?因为bind()不会立即调用当前函数。 bind()通常使用在回调函数中,因为回调函数并不会立即调用。如果你希望在回调函数中改变this,不妨使用bind()。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/04-ES6:变量 let、const 和块级作用域.md ================================================ --- title: 04-ES6:变量 let、const 和块级作用域 publish: true --- ## ES6 的变量声明 ES5 中,使用 `var` 定义变量( var 是 variable 的简写)。 ES6 中,新增了 let 和 const 来定义变量: - `let`:定义**变量**,替代 var。 - `const`:定义**常量**(定义后,不可修改)。 ### var:定义变量(ES5 知识回顾) 看下面的代码: ```javascript { var a = 1; } console.log(a); //这里的 a,指的是 区块 里的 a ``` 上方代码是可以输出结果的,输出结果为 1。因为 var 是全局声明的,所以,即使是在区块里声明,但仍然在全局起作用。 也就是说:**使用 var 声明的变量不具备块级作用域特性**。 再来看下面这段代码: ```javascript var a = 1; { var a = 2; } console.log(a); //这里的 a,指的是 区块 里的 a ``` 上方代码的输出结果为 2 ,因为 var 是全局声明的。 **总结:** ES5语法中,用 var 定义的变量,容易造成全局污染(污染整个 js 的作用域)。如果不考虑浏览器的兼容性,我们在今后的实战中,**尽量避免**使用 var 定义变量,尽量用接下来要讲的ES6语法。 ### 1、let:定义变量 举例 1: ```js { let a = 'hello'; } console.log(a); // 打印结果报错:Uncaught ReferenceError: a is not defined ``` 上方代码,打印报错。 举例 2: ```javascript var a = 2; { let a = 3; } console.log(a); // 打印结果:2 ``` 通过上面两个例子可以看出,**用块级作用域内, 用let 声明的变量,只在局部起作用**。 **经典面试题**: let 可以防止数据污染,我们来看下面这个 **for 循环**的经典面试题。 1、用 var 声明变量: ```javascript for (var i = 0; i < 10; i++) { console.log('循环体中:' + i); } console.log('循环体外:' + i); ``` 上方代码的最后一行可以正常打印结果,且最后一行的打印结果是 10。说明**循环体外**定义的变量 i,是**全局作用域**下的 i。 2、用 let 声明变量: ```javascript for (let i = 0; i < 10; i++) { console.log('循环体中:' + i); // // 每循环一次,就会在 { } 所在的块级作用域中,重新定义一个新的变量 i } console.log('循环体外:' + i); ``` 上方代码的关键在于:**每次循环都会产生一个块级作用域,每个块级作用域中会重新定义一个新的变量 i**。 另外,上方代码的最后一行,打印会报错。因为用 let 定义的变量 i,只在`{ }`这个**块级作用域**里生效。 **总结:**我们要习惯用 let 声明,减少 var 声明带来的**污染全局空间**。 为了进一步强调 let 不会带来污染,需要说明的是:当我们定义了`let a = 1`时,如果我们在同一个作用域内继续定义`let a = 2`,是会报错的。 ### 2、const:定义常量 在程序开发中,有些变量是希望声明后,在业务层就不再发生变化,此时可以用 const 来定义**常量**。常量就是值(内存地址)不能变化的量。 举例: ```javascript const name = 'smyhvae'; //定义常量 ``` 用 const 声明的常量,只在局部(块级作用域内)起作用;而且,用 const 声明常量时,必须赋值,否则报错。 ### let 和 const 的特点【重要】 - 不属于顶层对象 Window - 不允许重复声明 - 不存在变量提升 - 暂时性死区 - 支持块级作用域 相反, 用`var`声明的变量:存在变量提升、可以重复声明、**没有块级作用域**。 ### var/let/const 的共同点 - 全局作用域中定义的变量,可以在函数中使用。 - 函数中声明的变量,只能在函数及其子函数中使用,外部无法使用。 ### 总结 关于 let、const、var 更详细的介绍和区别,可以看本项目的另一篇文章《JavaScript 进阶/var、let、const 的区别》。 ## for 循环举例(经典案例) **代码 1**、我们先来看看如下代码:(用 var 定义变量 i) ```html Document ``` 上方代码中的运行效果如下: ![](http://img.smyhvae.com/20190904_1030.gif) 你可能会感到诧异,为何点击任何一个按钮,弹出的内容都是 4 呢?这是因为,我们用 var 定义的变量 i,是在全局作用域声明的。整个代码中,自始至终只有一个变量。 for 循环是同步代码,而 onclick 点击事件是异步代码。当我们还没点击按钮之前,同步代码已经执行完了,变量 i 已经循环到 4 了。 也就是说,上面的 for 循环,相当于如下代码: ```javascript var i = 0; myBtn[0].onclick = function () { alert(i); }; i++; myBtn[1].onclick = function () { alert(i); }; i++; myBtn[2].onclick = function () { alert(i); }; i++; myBtn[3].onclick = function () { alert(i); }; i++; // 到这里,i 的值已经是4了。因此,当我们点击按钮时,i的值一直都是4 ``` **代码 2**、上面的代码中,如果我们改为用 let 定义变量 i: ```html Document ``` 上方代码中的运行效果如下: ![](http://img.smyhvae.com/20190904_1040.gif) 上面这个运行结果,才是我们预期的效果。我们用 let 定义变量 i,在循环的过程中,每执行一次循环体,就会诞生一个新的 i。循环体执行 4 次,就会有四个 i。 ## 补充知识 ### 暂时性死区 DTC ES6 规定:使用 let/const 声明的变量,会使区块形成封闭的作用域。若在声明之前使用变量,就会报错。 也就是说,在使用 let/const 声明变量时,**变量需要先声明,再使用**(声明语句必须放在使用之前)。这在语法上,称为 “暂时性死区”( temporal dead zone,简称 TDZ)。 DTC 其实是一种保护机制,可以让我们养成良好的编程习惯。 代码举例: ```js const name = 'qianguyihao'; function foo() { console.log(name); const name = 'hello'; } foo(); // 执行函数后,控制台报错:Uncaught ReferenceError: Cannot access 'name' before initialization ``` ### ES5 中如何定义常量 ES5中有`Object.defineProperty`这样一个api,可以定义常量。这个API中接收三个参数。 代码举例: ```js // 定义常量 PI Object.defineProperty(window, 'PI', { value: 3.14, writable: false, }); console.log(PI); // 打印结果:3.14 PI = 6; //尝试修改常量 console.log(PI); //打印结果:3.14,说明修改失败 ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/05-ES6:变量的解构赋值.md ================================================ --- title: 05-ES6:变量的解构赋值 --- ## 解构赋值的概念 **解构赋值**:ES6 允许我们,按照一一对应的方式,从数组或者对象中**提取值**,再将提取出来的值赋值给变量。 解构:分解数据结构;赋值:给变量赋值。 解构赋值在实际开发中可以大量减少我们的代码量,并且让程序结构更清晰。 ## 数组的解构赋值 数组的结构赋值:将数组中的值按照**位置**提取出来,然后赋值给变量。 ### 语法 在 ES6 之前,当我们在为一组变量赋值时,一般是这样写: ```javascript var a = 1; var b = 2; var c = 3; ``` 或者是这样写: ```js var arr = [1, 2, 3]; var a = arr[0]; var b = arr[1]; var c = arr[2]; ``` 现在有了 ES6 之后,我们可以通过数组解构的方式进行赋值:(根据**位置**进行一一对应) ```javascript let [a, b, c] = [1, 2, 3]; ``` 二者的效果是一样的,但明显后者的代码更简洁优雅。 ### 未匹配到的情况 数据的结构赋值,是根据位置进行一一对应来赋值的。可如果左边的数量大于右边的数量时(也就是变量的数量大于值的数量时),多余的变量要怎么处理呢? 答案是:如果变量在一一对应时,没有找到对应的值,那么,**多余的变量会被赋值为 undefined**。 ### 解构时,左边允许有默认值 在解构赋值时,是允许使用默认值的。举例如下: ```javascript { //一个变量时 let [foo = true] = []; console.log(foo); //输出结果:true } { //两个变量时 let [a, b] = ['千古壹号']; //a 赋值为:千古壹号。b没有赋值 console.log(a + ',' + b); //输出结果:千古壹号,undefined } { //两个变量时 let [a, b = 'qianguyihao'] = ['千古壹号']; //a 赋值为:千古壹号。b 采用默认值 qianguyihao console.log(a + ',' + b); //输出结果:千古壹号,qianguyihao } ``` ### 将右边的 `undefined`和`null`赋值给变量 如果我们在赋值时,采用的是 `undefined`或者`null`,那会有什么区别呢? ```javascript { let [a, b = 'qianguyihao'] = ['千古壹号', undefined]; //b 虽然被赋值为 undefined,但是 b 会采用默认值 console.log(a + ',' + b); //输出结果:千古壹号,qianguyihao } { let [a, b = 'qianguyihao'] = ['千古壹号', null]; //b 被赋值为 null console.log(a + ',' + b); //输出结果:千古壹号,null } ``` 上方代码分析: - undefined:相当于什么都没有,此时 b 采用默认值。 - null:相当于有值,但值为 null。 ## 对象的解构赋值 对象的结构赋值:将对象中的值按照**属性匹配的方式**提取出来,然后赋值给变量。 ### 语法 在 ES6 之前,我们从接口拿到 json 数据后,一般这么赋值: ```javascript var name = json.name; var age = json.age; var sex = json.sex; ``` 上面这种写法,过于麻烦了。 现在,有了 ES6 之后,我们可以使用对象解构的方式进行赋值。举例如下: ```js const person = { name: 'qianguyihao', age: 28, sex: '男' }; let { name, age, sex } = person; // 对象的结构赋值 console.log(name); // 打印结果:qianguyihao console.log(age); // 打印结果:28 console.log(sex); // 打印结果:男 ``` 上方代码可以看出,对象的解构与数组的结构,有一个重要的区别:**数组**的元素是按次序排列的,变量的取值由它的**位置**决定;而**对象的属性没有次序**,是**根据键来取值**的。 ### 未匹配到的情况 对象的结构赋值,是根据属性名进行一一对应来赋值的。可如果左边的数量大于右边的数量时(也就是变量的数量大于值的数量时),多余的变量要怎么处理呢? 答案是:如果变量在一一对应时,没有找到对应的值,那么,**多余的变量会被赋值为 undefined**。 ### 给左边的变量自定义命名 对象的结构赋值里,左边的变量名一定要跟右边的属性名保持一致么?答案是不一定。我们可以单独给左边的变量自定义命名。 举例如下: ```js const person = { name: 'qianguyihao', age: 28 }; let { name: myName, age: myAge } = person; // 对象的结构赋值 console.log(myName); // 打印结果:qianguyihao console.log(myAge); // 打印结果:28 console.log(name); // 打印报错:Uncaught ReferenceError: name is not defined console.log(age); // 打印报错:Uncaught ReferenceError: age is not defined ``` 上方的第 2 行代码中:(请牢记) - 等号左边的属性名 name、age 是对应等号右边的属性名。 - 等号左边的 myName、myAge 是左边自定义的变量名。 或者,我们也可以理解为:将右边 name 的值赋值给左边的 myName 变量,将右边 age 的值赋值给左边的 myAge 变量。现在,你应该一目了然了吧? ### 圆括号的使用 如果变量 foo 在解构之前就已经定义了,此时你再去解构,就会出现问题。下面是错误的代码,编译会报错: ```javascript let foo = 'haha'; { foo } = { foo: 'smyhvae' }; console.log(foo); ``` 要解决报错,只要在解构的语句外边,加一个圆括号即可: ```javascript let foo = 'haha'; ({ foo } = { foo: 'smyhvae' }); console.log(foo); //输出结果:smyhvae ``` ## 字符串解构 字符串也可以解构,这是因为,此时字符串被转换成了一个类似数组的对象。举例如下: ```javascript const [a, b, c, d] = 'hello'; console.log(a); console.log(b); console.log(c); console.log(typeof a); //输出结果:string ``` 打印结果: ``` h e l string ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/06-ES6:箭头函数.md ================================================ --- title: 06-ES6:箭头函数 --- ## 前言 ES6 在**函数扩展**方面,新增了很多特性。例如: - 箭头函数 - 参数默认值 - 参数结构赋值 - 剩余参数 - 扩展运算符 - this 绑定 - 尾调用 今天这篇文章,我们讲一下箭头函数。 ## 箭头函数 ### 定义箭头函数的语法 语法: ```js (参数1, 参数2 ...) => { 函数体 } ``` 解释: - 如果有且仅有 1 个形参,则`()`可以省略 - 如果函数体内有且仅有 1 条语句,则`{}`可以省略,但前提是,这条语句必须是 return 语句。 需要强调的是,箭头函数是没有函数名的,既然如此,那要怎么调用箭头函数呢?你可以将箭头函数赋值给一个变量,通过变量名调用函数;也可以直接使用箭头函数。我们来看看下面的例子。 ### 举例 写法 1、定义和调用函数:(传统写法) ```javascript function fn1(a, b) { console.log('haha'); return a + b; } console.log(fn1(1, 2)); //输出结果:3 ``` 写法 2、定义和调用函数:(ES6 中的写法) ```javascript const fn2 = (a, b) => { console.log('haha'); return a + b; }; console.log(fn2(1, 2)); //输出结果:3 ``` 上面的两种写法,效果是一样的。 从上面的箭头函数中,我们可以很清晰地看到变量名、参数名、函数体。 另外,箭头函数的写法还可以精简一下,继续往下看。 【重要】在箭头函数中,如果方法体内只有一句话,且这句话是 return 语句,那就可以把 `{}`省略。写法如下: ```javascript const fn2 = (a, b) => a + b; console.log(fn2(1, 2)); //输出结果:3 ``` 在箭头函数中,如果形参只有一个参数,则可以把`()`省略。写法如下: ```js const fn2 = (a) => { console.log('haha'); return a + 1; }; console.log(fn2(1)); //输出结果:2 ``` ## 箭头函数的 this 的指向 > 箭头函数只是为了让函数写起来更简洁优雅吗?当然不只是这个原因,还有一个很大的作用是与 this 的指向有关。 ES6 之前的普通函数中:this 指向的是函数被调用的对象(也就是说,谁调用了函数,this 就指向谁)。 而 ES6 的箭头函数中:**箭头函数本身不绑定 this**,this 指向的是**箭头函数定义位置的 this**(也就是说,箭头函数在哪个位置定义的,this 就跟这个位置的 this 指向相同)。 代码举例: ```js const obj = { name: '千古壹号' }; function fn1() { console.log(this); // 第一个 this return () => { console.log(this); // 第二个 this }; } const fn2 = fn1.call(obj); fn2(); ``` 打印结果: ``` obj obj ``` 代码解释:(一定要好好理解下面这句话) 上面的代码中,箭头函数是在 fn1()函数里面定义的,所以第二个 this 跟 第一个 this 指向的是**同一个位置**。又因为,在执行 `fn1.call(obj)`之后,第一个 this 就指向了 obj,所以第二个 this 也是指向 了 obj。 ### 面试题:箭头函数的 this 指向 代码举例: ```js var name = '许嵩'; var obj = { name: '千古壹号', sayHello: () => { console.log(this.name); }, }; obj.sayHello(); ``` 上方代码的打印结果是什么?你可能很难想到。 正确答案的打印结果是`许嵩`。因为 `obj` 这个对象并不产生作用域, `sayHello()` 这个箭头函数实际仍然是定义在 window 当中的,所以 这里的 this 指向是 window。 ## 参数默认值 **传统写法**: ```javascript function fn(param) { let p = param || 'hello'; console.log(p); } ``` 上方代码中,函数体内的写法是:如果 param 不存在,就用 `hello`字符串做兜底。这样写比较啰嗦。 **ES6 写法**:(参数默认值的写法,很简洁) ```javascript function fn(param = 'hello') { console.log(param); } ``` 在 ES6 中定义方法时,我们可以给方法里的参数加一个**默认值**(缺省值): - 方法被调用时,如果没有给参数赋值,那就是用默认值; - 方法被调用时,如果给参数赋值了新的值,那就用新的值。 如下: ```javascript var fn2 = (a, b = 5) => { console.log('haha'); return a + b; }; console.log(fn2(1)); //第二个参数使用默认值 5。输出结果:6 console.log(fn2(1, 8)); //输出结果:9 ``` **提醒 1**:默认值的后面,不能再有**没有默认值的变量**。比如`(a,b,c)`这三个参数,如果我给 b 设置了默认值,那么就一定要给 c 设置默认值。 **提醒 2**: 我们来看下面这段代码: ```javascript let x = 'smyh'; function fn(x, y = x) { console.log(x, y); } fn('vae'); ``` 注意第二行代码,我们给 y 赋值为`x`,这里的`x`是括号里的第一个参数,并不是第一行代码里定义的`x`。打印结果:`vae vae`。 如果我把第一个参数改一下,改成: ```javascript let x = 'smyh'; function fn(z, y = x) { console.log(z, y); } fn('vae'); ``` 此时打印结果是:`vae smyh`。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/07-剩余参数和扩展运算符.md ================================================ --- title: 07-剩余参数和扩展运算符 --- ## 剩余参数 **剩余参数**允许我们将不确定数量的**剩余的元素**放到一个**数组**中。 比如说,当函数的实参个数大于形参个数时,我们可以将剩余的实参放到一个数组中。 **传统写法**: ES5 中,在定义方法时,参数要确定个数,如下:(程序会报错) ```javascript function fn(a, b, c) { console.log(a); console.log(b); console.log(c); console.log(d); } fn(1, 2, 3); ``` 上方代码中,因为方法的参数是三个,但使用时是用到了四个参数,所以会报错: ![](http://img.smyhvae.com/20180304_1638.png) **ES6 写法**: ES6 中,我们有了剩余参数,就不用担心报错的问题了。代码可以这样写: ```javascript const fn = (...args) => { //当不确定方法的参数时,可以使用剩余参数 console.log(args[0]); console.log(args[1]); console.log(args[2]); console.log(args[3]); }; fn(1, 2); fn(1, 2, 3); //方法的定义中了四个参数,但调用函数时只使用了三个参数,ES6 中并不会报错。 ``` 打印结果: ```bash 1 2 undefined undefined 1 2 3 undefined ``` 上方代码中注意,args 参数之后,不能再加别的参数,否则编译报错。 下面这段代码,也是利用到了剩余参数: ```js function fn1(first, ...args) { console.log(first); // 10 console.log(args); // 数组:[20, 30] } fn1(10, 20, 30); ``` ### 剩余参数的举例:参数求和 代码举例: ```js const sum = (...args) => { let total = 0; args.forEach(item => total += item); // 注意 forEach里面的代码,写得 很精简 return total; }; console.log(sum(10, 20, 30)); ``` 打印结果:60 ### 剩余参数和解构赋值配合使用 代码举例: ```js const students = ['张三', '李四', '王五']; let [s1, ...s2] = students; console.log(s1); // '张三' console.log(s2); // ['李四', '王五'] ``` ## 扩展运算符(展开语法) 扩展运算符和剩余参数是相反的。 剩余参数是将剩余的元素放到一个数组中;而扩展运算符是将数组或者对象拆分成逗号分隔的参数序列。 代码举例: ```js const arr = [10, 20, 30]; ...arr // 10, 20, 30 注意,这一行是伪代码,这里用到了扩展运算符 console.log(...arr); // 10 20 30 console.log(10, 20, 30); // 10 20 30 ``` 上面的代码要仔细看: `arr`是一个数组,而`...arr`则表示`10, 20, 30`这样的序列。 我们把`...arr` 打印出来,发现打印结果竟然是 `10 20 30`,为啥逗号不见了呢?因为逗号被当作了 console.log 的参数分隔符。如果你不信,可以直接打印 `console.log(10, 20, 30)` 看看。 接下来,我们看一下扩展运算符的应用。 ### 举例1:数组赋值 数组赋值的代码举例: ```js let arr2 = [...arr1]; // 将 arr1 赋值给 arr2 ``` 为了理解上面这行代码,我们先来分析一段代码:(将数组 arr1 赋值给 arr2) ```javascript let arr1 = ['www', 'smyhvae', 'com']; let arr2 = arr1; // 将 arr1 赋值给 arr2,其实是让 arr2 指向 arr1 的内存地址 console.log('arr1:' + arr1); console.log('arr2:' + arr2); console.log('---------------------'); arr2.push('你懂得'); //往 arr2 里添加一部分内容 console.log('arr1:' + arr1); console.log('arr2:' + arr2); ``` 运行结果: ![](http://img.smyhvae.com/20180304_1950.png) 上方代码中,我们往往 arr2 里添加了`你懂的`,却发现,arr1 里也有这个内容。原因是:`let arr2 = arr1;`其实是让 arr2 指向 arr1 的地址。也就是说,二者指向的是同一个内存地址。 如果不想让 arr1 和 arr2 指向同一个内存地址,我们可以借助**扩展运算符**来做: ```javascript let arr1 = ['www', 'smyhvae', 'com']; let arr2 = [...arr1]; //【重要代码】arr2 会重新开辟内存地址 console.log('arr1:' + arr1); console.log('arr2:' + arr2); console.log('---------------------'); arr2.push('你懂得'); //往arr2 里添加一部分内容 console.log('arr1:' + arr1); console.log('arr2:' + arr2); ``` 运行结果: ```bash arr1:www,smyhvae,com arr2:www,smyhvae,com --------------------- arr1:www,smyhvae,com arr2:www,smyhvae,com,你懂得 ``` 我们明白了这个例子,就可以避免开发中的很多业务逻辑上的 bug。 ### 举例2:合并数组 代码举例: ```js let arr1 = ['王一', '王二', '王三']; let arr2 = ['王四', '王五', '王六']; // ...arr1 // '王一','王二','王三' // ...arr2 // '王四','王五','王六' // 方法1 let arr3 = [...arr1, ...arr2]; console.log(arr3); // ["王一", "王二", "王三", "王四", "王五", "王六"] // 方法2 arr1.push(...arr2); console.log(arr1); // ["王一", "王二", "王三", "王四", "王五", "王六"] ``` ### 举例3:将伪数组或者可遍历对象转换为真正的数组 代码举例: ```js const myDivs = document.getElementsByClassName('div'); const divArr = [...myDivs]; // 利用扩展运算符,将伪数组转为真正的数组 ``` **补充**: 我们在《JavaScript基础/数组的常见方法》中也学过,还有一种方式,可以将伪数组(或者可遍历对象)转换为真正的数组。语法格式如下: ```js let arr2 = Array.from(arrayLike); ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/08-字符串、数组、对象的扩展.md ================================================ --- title: 08-字符串、数组、对象的扩展 publish: true --- ## 字符串的扩展 > 下面提到的字符串的几个方法,更详细的内容,可以看《04-JavaScript 基础/内置对象 String:字符串的常见方法.md》。 ES6 中的字符串扩展如下: - `includes(str)`:判断是否包含指定的字符串 - `startsWith(str)`:判断是否以指定字符串开头 - `endsWith(str)`:判断是否以指定字符串结尾 - `repeat(count)`:重复指定次数 举例如下: ```javascript let str = 'abcdefg'; console.log(str.includes('a')); //true console.log(str.includes('h')); //false //startsWith(str) : 判断是否以指定字符串开头 console.log(str.startsWith('a')); //true console.log(str.startsWith('d')); //false //endsWith(str) : 判断是否以指定字符串结尾 console.log(str.endsWith('g')); //true console.log(str.endsWith('d')); //false //repeat(count) : 重复指定次数a console.log(str.repeat(5)); ``` 打印结果: ![](http://img.smyhvae.com/20180402_1050.png) ## Number 的扩展 - 二进制与八进制数值表示法: 二进制用`0b`, 八进制用`0o`。 举例: ```javascript console.log(0b1010); //10 console.log(0o56); //46 ``` - `Number.isFinite(i)`:判断是否为有限大的数。比如`Infinity`这种无穷大的数,返回的就是 false。 - `Number.isNaN(i)`:判断是否为 NaN。 - `Number.isInteger(i)`:判断是否为整数。 - `Number.parseInt(str)`:将字符串转换为对应的数值。 - `Math.trunc(i)`:去除小数部分。 举例: ```javascript //Number.isFinite(i) : 判断是否是有限大的数 console.log(Number.isFinite(NaN)); //false console.log(Number.isFinite(5)); //true console.log(Number.isFinite(Infinity)); //false //Number.isNaN(i) : 判断是否是NaN console.log(Number.isNaN(NaN)); //true console.log(Number.isNaN(5)); //falsse //Number.isInteger(i) : 判断是否是整数 console.log(Number.isInteger(5.23)); //false console.log(Number.isInteger(5.0)); //true console.log(Number.isInteger(5)); //true //Number.parseInt(str) : 将字符串转换为对应的数值 console.log(Number.parseInt('123abc')); //123 console.log(Number.parseInt('a123abc')); //NaN // Math.trunc(i) : 直接去除小数部分 console.log(Math.trunc(13.123)); //13 ``` ## 数组的扩展 > 下面提到的数组的几个方法,更详细的内容,可以看《04-JavaScript 基础/数组的常见方法.md》。 - Array.from() - find() - findIndex() ## 对象的扩展 ### 扩展 1 ```javascript Object.is(v1, v2); ``` **作用:**判断两个数据是否完全相等。底层是通过**字符串**来判断的。 我们先来看下面这两行代码的打印结果: ```javascript console.log(0 == -0); console.log(NaN == NaN); ``` 打印结果: ``` true false ``` 上方代码中,第一行代码的打印结果为 true,这个很好理解。第二行代码的打印结果为 false,因为 NaN 和任何值都不相等。 但是,如果换成下面这种方式来比较: ```javascript console.log(Object.is(0, -0)); console.log(Object.is(NaN, NaN)); ``` 打印结果却是: ```bash false true ``` 代码解释:还是刚刚说的那样,`Object.is(v1, v2)`比较的是字符串是否相等。 ### Object.assign() Object.assign() 在实战开发中,使用到的频率非常高,一定要重视。关于它的内容,详见《04-JavaScript 基础/浅拷贝和深拷贝.md》。 ### 扩展 3:`__proto__`属性 举例: ```javascript let obj1 = { name: 'smyhvae' }; let obj2 = {}; obj2.__proto__ = obj1; console.log(obj1); console.log(obj2); console.log(obj2.name); ``` 打印结果: ![](http://img.smyhvae.com/20180404_2251.png) 上方代码中,obj2 本身是没有属性的,但是通过`__proto__`属性和 obj1 产生关联,于是就可以获得 obj1 里的属性。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/09-内置对象扩展:Set数据结构.md ================================================ --- title: 09-内置对象扩展:Set数据结构 publish: true --- ## Set 数据结构 ### Set 数据结构的介绍 ES6 提供了 新的数据结构 Set。Set 类似于**数组**,但成员的值都是**唯一**的,没有重复的值。 Set 的应用有很多。比如,在 H5 页面的搜索功能里,用户可能会多次搜索重复的关键字;但是在数据存储上,不需要存储重复的关键字。此时,我们就可以用 Set 来存储用户的搜索记录,Set 内部会自动判断值是否重复,如果重复,则不会进行存储,十分方便。 ### 生成 Set 数据结构 Set 本身就是一个构造函数,可通过 `new Set()` 生成一个 Set 的实例。 举例 1: ```js const set1 = new Set(); console.log(set1.size); // 打印结果:0 ``` **举例 2**、可以接收一个**数组**作为参数,实现**数组去重**: ```js const set2 = new Set(['张三', '李四', '王五', '张三']); // 注意,这个数组里有重复的值 // 注意,这里的 set2 并不是数组,而是一个单纯的 Set 数据结构 console.log(set2); // {"张三", "李四", "王五"} // 通过扩展运算符,拿到 set 中的元素(用逗号分隔的序列) // ...set2 // "张三", "李四", "王五" // 注意,到这一步,才获取到了真正的数组 console.log([...set2]); // ["张三", "李四", "王五"] ``` 注意上方的第一行代码,虽然参数里传递的是数组结构,但拿到的 `set2` 不是数组结构,而是 Set 结构,而且里面元素是去重了的。通过 `[...set2]`就可以拿到`set2`对应的数组。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 05-JavaScript基础:ES6语法/ES6:Symbol.md ================================================ --- publish: false --- ## Symbol ### 概述 背景:ES5中对象的属性名都是字符串,容易造成重名,污染环境。 **概念**:ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 **特点:** - Symbol属性对应的值是唯一的,解决**命名冲突问题** - Symbol值不能与其他数据进行计算,包括同字符串拼串 - for in、for of 遍历时不会遍历Symbol属性。 ### 创建Symbol属性值 Symbol是函数,但并不是构造函数。创建一个Symbol数据类型: ```javascript let mySymbol = Symbol(); console.log(typeof mySymbol); //打印结果:symbol console.log(mySymbol); //打印结果:Symbol() ``` 打印结果: ![](http://img.smyhvae.com/20180317_1134.png) 下面来讲一下Symbol的使用。 ### 1、将Symbol作为对象的属性值 ```javascript let mySymbol = Symbol(); let obj = { name: 'smyhvae', age: 26 }; //obj.mySymbol = 'male'; //错误:不能用 . 这个符号给对象添加 Symbol 属性。 obj[mySymbol] = 'hello'; //正确:通过**属性选择器**给对象添加 Symbol 属性。后面的属性值随便写。 console.log(obj); ``` 上面的代码中,我们尝试给obj添加一个Symbol类型的属性值,但是添加的时候,不能采用`.`这个符号,而是应该用`属性选择器`的方式。打印结果: ![](http://img.smyhvae.com/20180317_1134.png) 现在我们用for in尝试对上面的obj进行遍历: ```javascript let mySymbol = Symbol(); let obj = { name: 'smyhvae', age: 26 }; obj[mySymbol] = 'hello'; console.log(obj); //遍历obj for (let i in obj) { console.log(i); } ``` 打印结果: ![](http://img.smyhvae.com/20180317_1134.png) 从打印结果中可以看到:for in、for of 遍历时不会遍历Symbol属性。 ### 创建Symbol属性值时,传参作为标识 如果我通过 Symbol()函数创建了两个值,这两个值是不一样的: ```javascript let mySymbol1 = Symbol(); let mySymbol2 = Symbol(); console.log(mySymbol1 == mySymbol2); //打印结果:false console.log(mySymbol1); //打印结果:Symbol() console.log(mySymbol2); //打印结果:Symbol() ``` ![](http://img.smyhvae.com/20180317_1134.png) 上面代码中,倒数第三行的打印结果也就表明了,二者的值确实是不相等的。 最后两行的打印结果却发现,二者的打印输出,肉眼看到的却相同。那该怎么区分它们呢? 既然Symbol()是函数,函数就可以传入参数,我们可以通过参数的不同来作为**标识**。比如: ```javascript //在括号里加入参数,来标识不同的Symbol let mySymbol1 = Symbol('one'); let mySymbol2 = Symbol('two'); console.log(mySymbol1 == mySymbol2); //打印结果:false console.log(mySymbol1); //打印结果:Symbol(one) console.log(mySymbol2); //打印结果:Symbol(two)。颜色为红色。 console.log(mySymbol2.toString());//打印结果:Symbol(two)。颜色为黑色。 ``` 打印结果: ![](http://img.smyhvae.com/20180317_1134.png) ### 定义常量 Symbol 可以用来定义常量: ```javascript const MY_NAME = Symbol('my_name'); ``` ### 内置的 Symbol 值 除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。 - `Symbol.iterator`属性 对象的`Symbol.iterator`属性,指向该对象的默认遍历器方法。 ================================================ FILE: 06-JavaScript基础:异步编程/00-服务器分类及PHP入门.md ================================================ --- title: 00-服务器分类及PHP入门 publish: true --- ## C/S架构和B/S架构 ### C/S架构 是Client/Server这两个单词的首字母,指的是客户端,服务器。 优点: - 性能较高:可以将一部分的计算工作放在客户端上,这样服务器只需要处理数据即可。 - 界面酷炫:客户端可以使用更多系统提供的效果,做出更为炫目的效果。 缺点: - 更新软件:如果有新的功能,就要推出新的版本。 - 不同设备访问:如果使用其他的电脑,没有安装客户端的话就无法登陆软件。 ### B/S架构 是Browser/Server的这两个单词的首字母。指的是浏览器、服务器,是WEB兴起之后的一种架构。 现在所有的网站都是B/S架构,较为常见的例子有百度、知乎、网易云音乐Web等等,只需要通过浏览器即可使用. 优点: - 更新简洁:如果需要更新内容了,对开发人员而言需要更改服务器的内容,对用户而言只需要刷新浏览器即可。 - 多设备同步:所有数据都在网上,只要能够使用浏览器即可登录使用。 缺点: - 性能较低:相比于客户端应用性能较低,但是随着硬件性能的提升,这个差距在缩小。 - 浏览器兼容:处理低版本的浏览器显示问题一直是前端开发人员头痛的问题之一。移动设备兼容性较好,ie6已经越来越少人用了。 ## 服务器分类 项目开发时,有三套环境: - Development 开发环境 - Test 测试环境 - Production 生产环境 程序员平时干活儿用开发环境;开发完成后,部署到测试环境;测试完成后,产品上线,部署到生产环境。 三套环境意味着三个服务器。 ### 服务器类型 按类型分: - 文件服务器 - 数据库服务器 - 邮件服务器 - Web 服务器等 按软件分: - Apache 服务器 - Nginx 服务器 - IIS 服务器 - Tomcat 服务器 - Node 服务器等 按操作系统分: - Linux服务器 - Windows服务器等 ### 服务器软件 提供了某种服务的计算器,我们称之为服务器。那么这些赋予计算器各种服务功能的软件主要有哪一些呢? 常见的服务器软件有: - 文件服务器:Server-U、FileZilla、VsFTP等; - 数据库服务器:Oracle、MySQL、PostgreSQL、MSSQL等; - 邮件服务器:Postfix、Sendmail等; - HTTP 服务器:Apache(免费、开源)、Nginx、IIS(微软的.net服务器)、Tomcat(java编程的服务器)、NodeJS 等。 ## 使用 WampServer 搭建 HTTP服务 ### 集成环境的分类 - AMP:Apache + Mysql + PHP。 - WAMP:windows + Apache + Mysql + PHP。 - XAMPP:WAMP 是针对windows的,而 XAMPP 可以安装在Linux、Windows、MacOS、Solaris这些操作系统上面。 在windows平台下,如果想要一步到位安装好这些软件,可是使用软件 **WampServer**。 ### WampServer 的安装 去 WampServer 的[官网](http://www.wampserver.com/en/)下载软件。 ![](http://img.smyhvae.com/20180227_1936.png) 安装完成后进行安装。 ### 测试访问 打开浏览器输入 `127.0.0.1` 查看显示的内容,如果是第一次安装,默认显示的应该是如下图片: ![](http://img.smyhvae.com/20180227_2203.png) 127.0.0.1 是回送地址,指本地机,一般用来测试使用,如果想要让其他电脑也能够访问,需要进行如下配置: (1)关闭防火墙: ![](http://img.smyhvae.com/20180227_2207.gif) (2)修改httpd.conf文件: 因为 Apache 的配置默认不允许外部访问,我们需要修改配置。 打开文件`c:\wamp\bin\apache\Apache2.2.21\conf\httpd.conf`,通过搜索功能找到`onlineoffline tag - don't remove`这句话,在第234行的 `Allow from 127.0.0.1`的下面,加一行:`Allow from all`。 然后将第192行的`Deny from all`改为`Allow from all`。 保存,然后重启 wamp 即可。 ### 配置网站根目录 网站的根目录默认是在`D:\wamp\www`。如果想修改这个根目录,可以这样改: 打开 Apache的配置文件 `D:\wamp\bin\apache\Apache2.2.21\conf\http.conf`,如果是初次安装,找到178行的`DocumentRoot "d:/wamp/www/"`,以及205行的``,改这两个位置的路径即可。我们可以通过搜索关键字`documentRoot`来定位。 ## 静态网站和动态网站 静态网站: - 访问的是实实在在保存在服务器上的文件。静态资源包括:html页面、css文件、js文件、图片等。 - 当内容、图片、界面需要更新时,直接修改.html文件。 动态网站: - 当用户访问网站时,根据`某些逻辑`,动态生成对应的`HTML、CSS、JS`代码给用户(这也就是web服务器开发的本质)。 - 通过某种手段,当有新的消息时,**自动**的完成网站的更新。 总结: 由于静态网站在维护的局限性,所以产生了动态网站。 实现动态网站的技术:php/jsp/.net/python等。 动态网站的原理:浏览器请求动态网站的页面(比如*.php),php拼接数据并动态生成html页面,然后将新生成的页面返回给浏览器 php 之所以被称为最好的语言,是因为:基本上,我们能够想到的功能,它都帮助我们封装成了方法。十分方便。 ## PHP的常见语法 **PHP代码执行方式**: - 在服务器端执行,然后返回给用户结果。如果直接使用浏览器打开,就会解析为文本。 - 意思是说,需要浏览器通过 http请求,才能够执行php页面。 这里只列举常用的PHP语法,更为详细的语法教程可以查阅 [api 文档](http://www.w3school.com.cn/php/index.asp)。 ### 第一段 php 代码 将 WampServer 跑起来,在D:\wamp\www下新建一个`1.php`文件,代码如下: 1.php: ``` ``` 在浏览器中输入`http://127.0.0.1/2018-02-28/1.php`,效果如下: ![](http://img.smyhvae.com/20180228_0910.png) **代码的编写位置**: 上方代码中,注意php语言的格式,第一行和第三行的格式中,没有空格。代码的编写位置在``。 ### 注释 php 注释的写法跟 js 一致。 ``` ``` ### 变量 - 变量以`$`符号开头,其后是变量的名称。大小写敏感。 - 变量名称必须以字母或下划线开头。 举例: ``` $a1; $_abc; ``` ### 数据类型 PHP支持的数据类型包括: - 字符串 - 整数 - 浮点数 - 布尔 - 数组 - 对象 - NULLL 定义字符串时需要注意: - 单引号`` :内部的内容只是作为字符串。 - 双引号"" :如果内部是PHP的变量,那么会将该变量的值解析。如果内部是html代码,也会解析成html。 说白了,单引号里的内容,一定是字符串。双引号里的内容,可能会进行解析。 ``` echo ""; ``` 上面这个语句,就被会解析成按钮。 ``` // 字符串 $str = '123'; // 字符串拼接 $str2 = '123'.'哈哈哈'; // 整数 $numA = 1; //正数 $numB = -2;//负数 // 浮点数 $x = 1.1; // 布尔 $a = true; $b = false; // 普通数组:数组中可以放 数字、字符串、布尔值等,不限制类型。 $arr1 = array('123', 123); echo $arr1[0]; // 关系型数组:类似于json格式 $arr2 = $array(`name`=>`smyhvae`, `age`=>`26`); echo $arr2[`name`]; //获取时,通过 key 来获取 ``` 上方代码中注意,php 中字符串拼接的方式是 `.`。要注意哦。 ### 运算符 PHP 中的运算符跟 JavaScript 中的基本一致,用法也基本一致。 - 算数运算符:`+`、`-`、`/`、`*`、`%` - 赋值运算符:`x = y`、`x += y`,`x -= y`等。 举例: ```php ``` ### 函数的定义 语法格式: ```php function functionName() { //这里写代码 } ``` (1)有参数、无返回值的函数: ```php function sayName($name){ echo $name.'你好哦'; } // 调用 sayName('smyhvae'); ``` (2)有参数、参数有默认值的函数: ```php function sayFood($food='西兰花'){ echo $food.'好吃'; } // 调用 sayFood('西葫芦');// 如果传入参数,就使用传入的参数 sayFood();// 如果不传入参数,直接使用默认值 ``` (3)有参数、有返回值的函数: ```php function sum($a,$b){ return $a+$b } sum(1,2);// 返回值为1+2 = 3 ``` ### 类和对象 PHP中允许使用对象这种**自定义**的数据类型。必须先声明,实例化之后才能够使用。 定义最基础的类: ```php class Fox{ public $name = 'itcast'; public $age = 10; } $fox = new $fox; // 对象属性取值 $name = $fox->name; // 对象属性赋值 $fox->name = '小狐狸'; ``` 带构造函数的类: ```php class fox{ // 私有属性,外部无法访问 var $name = '小狐狸'; // 定义方法 用来获取属性 function Name(){ return $this->name; } // 构造函数,可以传入参数 function fox($name){ $this->name = $name } } // 定义了构造函数 需要使用构造函数初始化对象 $fox = new fox('小狐狸'); // 调用对象方法,获取对象名 $foxName = $fox->Name(); ``` ### 内容输出 - `echo`:输出字符串。 - `print_r()`:输出复杂数据类型。比如数组、对象。 - `var_dump()`:输出详细信息。 ```php $arr =array(1,2,'123'); echo'123'; //结果:123 print_r($arr); //结果:Array ( [0] => 1 [1] => 2 [2] => 123 ) var_dump($arr); /* 结果: array 0 => int 1 1 => int 2 2 => string '123' (length=3) */ ``` ### 循环语句 这里只列举了`foreach`、`for`循环。 for 循环: ```php for ($x=0; $x<=10; $x++) { echo "数字是:$x
    "; } ``` foreach 循环: ```php $colors = array("red","green","blue","yellow"); foreach ($colors as $value) { echo "$value
    "; } ``` 上方代码中,参数一:循环的对象。参数二:将对象的值挨个取出,直到最后。 如果循环的是对象,输出的是对象的属性的值。 输出结果: ```bash red green blue yellow ``` ## php中的header()函数 浏览器访问http服务器,接收到响应时,会根据响应**报文头**的内容进行一些具体的操作。在php中,我们可以根据 **header** 来设置这些内容。 **header()函数的作用**:用来向客户端(浏览器)发送报头。直接写在php代码的第一行就行。 下面列举几个常见的 header函数。 (1)设置编码格式: ```php header('content-type:text/html; charset= utf-8'); ``` 例如: ```php ``` (2)设置页面跳转: ```php header('location:http://www.baidu.com'); ``` 设置页面刷新的间隔: ```php header('refresh:3; url=http://www.xiaomi.com'); ``` ## php中的 get 请求和 post 请求 ### get 请求 可以通过`$_GET`对象来获取。 **举例**:下面是一个简单的表单代码,通过 get 请求将数据提交到01.php。 (1)index.html: ```html Title


    ``` (2)01.php: ```php php 的get 请求演示

    "; echo '用户名:'.$_GET['userName']; echo '
    '; echo '邮箱:'.$_GET['userEmail']; ?> ``` 上方代码可以看出,`$_GET`是关系型数组,可以通过 **$_GET[`key`]**获取值。这里的 key 是 form 标签中表单元素的 name 属性的值。 效果: ![](http://img.smyhvae.com/20180228_1140.gif) ### post 请求 可以通过`$_POST`对象来获取。 **举例**:下面是一个简单的表单代码,通过 post 请求将数据提交到02.php。 (1)index.html: ```html Title


    ``` (2)02.php: ```php php 的 post 请求演示"; echo '用户名:'.$_POST['userName']; echo '
    '; echo '邮箱:'.$_POST['userEmail']; ?> ``` 上方代码可以看出,`$_POST`是关系型数组,可以通过 **$_POST[`key`]**获取值。这里的 key 是 form 标签中表单元素的 name 属性的值。 效果演示: ![](http://img.smyhvae.com/20180228_1145.gif) 实际开发中,可能不会单独写一个php文件,常见的做法是:在 html 文件中嵌入 php 的代码。 比如说,原本 html 中有个 li 标签是存放用户名的: ```html
  • smyhvae
  • ``` 嵌入 php后,用户名就变成了动态获取的: ```php
  • ``` ## php 中文件相关的操作 ### 文件上传 `$_FILES` 上传文件时,需要在html代码中进行如下设置: (1)在html表单中,设置`enctype="multipart/form-data"`。该值是必须的。 (2)只能用 post 方式获取。 代码如下: (1)index.html: ```html

    ``` (2)在 php 文件中打印 file 的具体内容: ```php ``` 演示效果: ![](http://img.smyhvae.com/20180228_php_post_file.gif) 上方现象可以看出: - 点击提交后,服务器没有立即出现反应,而是休息了一会`sleep(5)`。 - 在`wamp/tmp`目录下面出现了一个`.tmp`文件。 - .tmp文件一会就被自动删除了。 - 服务器返回的内容中有文件的名字`[name] => computer.png`,以及上传文件保存的位置`D:\wamp\tmp\php3D70.tmp`。服务器返回的内容如下: ```bash Array ( [upFile] => Array ( [name] => yangyang.jpg [type] => image/jpeg [tmp_name] => D:\wamp\tmp\phpCC56.tmp [error] => 0 [size] => 18145 ) ) ``` ### 文件保存 我们尝试一下,把上面的例子中的`临时目录`下面的文件保存起来。这里需要用到 php 里的 `move_uploaded_file()`函数。[#](http://www.w3school.com.cn/php/func_filesystem_move_uploaded_file.asp) 格式如下: ```php move_uploaded_file($_FILES['photo']['tmp_name'], './images/test.jpg'); ``` 参数解释:参数一:移动的文件。参数二:目标路径。 (1)index.html:(这部分的代码保持不变) ```php

    ``` (2)PHP代码: 暂略。 ### WampServer 中修改上传文件的大小 (1)打开 WampServer的文件`php.ini`: ![](http://img.smyhvae.com/20180228_1454.png) (2)修改`php.ini`中的如下内容: 设置文件最大上传限制:(值的大小可以根据需求修改) ```php file_uploads = On; 是否允许上传文件 On/Off 默认是On upload_max_filesize = 32M; 设置 上传文件的最大限制 post_max_size = 32M; 设置 通过Post提交的最多数据 ``` 考虑网络传输快慢:这里修改一些参数: ```php max_execution_time = 30000 ; 脚本最长的执行时间 单位为秒 max_input_time = 600 ; 接收提交的数据的时间限制 单位为秒 memory_limit = 1024M ; 最大的内存消耗 ``` ## HTTP 协议 ### 请求 客户端发出的请求,主要由三个组成部分:请求行、请求头、请求主体。如下图所示: ![](https://img.smyhvae.com/20180228_1505.jpg) **1、请求行:** - 请求方法:GET or POST - 请求URL - HTTP协议版本 **2、请求头:** 常见的请求头如下: ```bash User-Agent:浏览器的具体类型  如:User-Agent:Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0 Accept:浏览器支持哪些数据类型  如:Accept: text/html,application/xhtml+xml,application/xml;q=0.9; Accept-Charset:浏览器采用的是哪种编码  如:Accept-Charset: ISO-8859-1 Accept-Encoding:浏览器支持解码的数据压缩格式  如:Accept-Encoding: gzip, deflate Accept-Language:浏览器的语言环境  如:Accept-Language zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。Host:www.baidu.com Connection:表示是否需要持久连接。 属性值可以是Keep-Alive/close,HTTP1.1默认是持久连接,它可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。 要实现这一点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。如:Connection: Keep-Alive Content-Length:表示请求消息正文的长度。对于POST请求来说Content-Length必须出现。 Content-Type:WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset='gb2312' Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip Content-Language:WEB服务器告诉浏览器自己响应的对象的语言。 Cookie:最常用的请求头,浏览器每次都会将cookie发送到服务器上,允许服务器在客户端存储少量数据。 Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。服务器能知道你是从哪个页面过来的。Referer: http://www.baidu.com/ ``` **3、请求体:** 指的是提交给服务器的数据。 需要注意的是,如果是往服务器提交数据,需要在请求头中设置`Content-Type: application/x-www-form-urlencoded`(在ajax中需要手动设置)。 ### 响应 响应报文是服务器返回给客户端的。组成部分有响应行、响应头、响应主体。 ![](http://img.smyhvae.com/20180228_1510.jpg) **1、状态行:** HTTP响应行:主要是设置响应状态等信息。 **2、响应头:** Cookie、缓存等信息就是在响应头的属性中设置的。 常见的响应头如下: ```bash Cache-Control 响应输出到客户端后,服务端通过该报文头属告诉客户端如何控制响应内容的缓存。 下面,的设置让客户端对响应内容缓存3600秒,也即在3600秒内,如果客户再次访问该资源,直接从客户端的缓存中返回内容给客户,不要再从服务端获取(当然,这个功能是靠客户端实现的,服务端只是通过这个属性提示客户端“应该这么做”,做不做,还是决定于客户端,如果是自己宣称支持HTTP的客户端,则就应该这样实现)。 Cache-Control: max-age=3600 ETag 一个代表响应服务端资源(如页面)版本的报文头属性,如果某个服务端资源发生变化了,这个ETag就会相应发生变化。它是Cache-Control的有益补充,可以让客户端“更智能”地处理什么时候要从服务端取资源,什么时候可以直接从缓存中返回响应。 ETag: "737060cd8c284d8af7ad3082f209582d" Location 我们在Asp.net中让页面Redirect到一个某个A页面中,其实是让客户端再发一个请求到A页面,这个需要Redirect到的A页面的URL,其实就是通过响应报文头的Location属性告知客户端的,如下的报文头属性,将使客户端redirect到iteye的首页中: Location: http://www.google.com.hk Set-Cookie 服务端可以设置客户端的Cookie,其原理就是通过这个响应报文头属性实现的。 Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 ``` **3、HTTP响应体:** 如果请求的是HTML页面,那么返回的就是HTML代码。如果是JS就是JS代码。 ### 抓包工具 常见的抓包工具有:whistle、Fiddler、Charles。 ================================================ FILE: 06-JavaScript基础:异步编程/01-单线程和异步任务.md ================================================ --- title: 01-单线程和异步任务 --- ## 单线程 ### JS 是单线程的 JavaScript 语言的执行是**单线程**的。即同一时间,只能处理一个任务。 具体来说,所谓单线程,是指 JS 引擎中负责解释和执行 JavaScript 代码的线程只有一个,即同一时间,只能处理一个任务。这个任务执行完后才能执行下一个。所有的任务都**需要排队**。 **JS 为何要被设计为单线程呢**?原因如下: - 首先是历史原因,在最初设计 JS 这门语言时,多进程、多线程的架构并不流行,硬件支持并不好。 - 其次是因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。 - 而且,如果多个线程同时操作同一个 DOM,在多线程不加锁的情况下,会产生冲突,最终会导致 DOM 渲染的结果不符预期。 所以,为了避免这些复杂问题的出现,JS 被设计成了单线程语言。 ### 浏览器是多进程、多线程的 JS代码在执行时有它的运行环境(也称之为“容器”),这个运行环境可以是浏览器,也可以是 Node.js 环境。 浏览器是多进程的,**每打开一个新的 tab 标签页就会开启一个新的进程**。每个进程之间是独立的,这是为了防止一个页面卡死而造成所有页面都无法响应,甚至整个浏览器强制退出。 **每个进程中有很多个线程**,其中有一个专门执行JS代码的线程,所以我们常说JS是单线程的,这没有说错。从JS语言的角度看,我们把这个线程称为“**主线程**”。 如果JS正在执行某个耗时的任务,则当前的线程会被阻塞,那应该怎么办呢? 实际上,**耗时的任务并不是在主线程中执行的**。因为浏览器的当前进程中有很多个线程,我们可以把耗时任务交给浏览器的其它线程来协助处理,然后在特定的时机通知主线程,该任务则会进入主线程同步完成。 比如,现在有一个三秒延迟的定时器任务。计时工作是交给浏览器的其他线程完成的,等三秒时间到了之后,通知JS主线程,该任务进入主线程进行同步执行。 ## 同步任务和异步任务 ### 定义 当前正在执行的任务,如果没有执行完成,它可能会**阻塞**其他正在排队的任务。为了解决这个问题,JS 在设计之初,将任务分成了两类:同步任务、异步任务。 - 同步任务:在**主线程**上排队执行的任务。只有当前任务执行完毕,才能执行下一个任务。当前任务在没有得到结果之前,不会继续后续操作。 - 异步任务:不进入主线程、而是进入**任务队列**(Event Queue)的任务,该任务无论有没有得到结果,都不会阻塞后续任务的执行。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 代码举例: ```js console.log('同步任务1'); setTimeout(() => { console.log('异步任务'); }, 1000); console.log('同步任务2'); ``` 打印结果是: ``` 同步任务1 同步任务2 异步任务 ``` 代码解释:第一行代码是同步任务,会**立即执行**;定时器里的回调函数是异步任务,需要等 1 秒后才会执行。假如定时器里的代码是同步任务,那需要等待1秒后,才能执行最后一行代码`console.log('同步任务2')`,也就是造成了主线程里的同步任务阻塞,这不是我们希望看到的。 比如说,网络图片的请求,就是一个异步任务。前端如果同时请求多张网络网络图片,谁先请求完成就让谁先显示出来。假如网络图片的请求做成同步任务,那就会出大问题,所有图片都得排队加载,如果第一张图片未加载完成,就得卡在那里,造成阻塞,导致其他图片都加载不出来。页面看上去也会很卡顿,这肯定是不能接受的。 ### 前端使用异步编程的场景 什么时候需要**等待**,就什么时候用异步。常见的异步场景如下: - 1、事件监听(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步) - 2、回调函数: - 2.1、定时器:setTimeout(定时炸弹)、setInterval(循环执行) - 2.2、ajax请求。 - 2.3、Node.js:FS文件读写、数据库操作。 - 3、ES6 中的 Promise、Generator、async/await 现在的大部分软件项目,都是前后端分离的。后端生成接口,前端请求接口。前端发送 ajax 请求,向后端请求数据,然后**等待一段时间**后,才能拿到数据。这个请求过程就是异步任务。 ### 接口调用的方式 js 中常见的接口调用方式,有以下几种: - 原生 ajax、基于 jQuery 的 ajax - Promise - Fetch - axios 后续文章,我们会重点讲一下接口调用里的 Ajax,然后在 ES6 语法中学习 **Promise**。在这之前,我们需要先了解同步任务、异步任务的事件循环机制。 ### 多次异步调用的顺序 - 多次异步调用的结果,顺序可能不同步。 - 异步调用的结果如果**存在依赖**,则需要通过回调函数进行嵌套。 ## 定时器:代码示例 掌握了上面的事件循环原理之后,我们来看几个例子。 ### 举例 1 ```js console.log(1); setTimeout(() => { console.log(2); }, 1000); console.log(3); console.log(4); ``` 打印结果: ``` 1 3 4 2 ``` 解释:先等同步任务执行完成后,再执行异步任务。 ### 举例 2(重要) 如果我把上面的等待时间,从 1 秒改成 0 秒,你看看打印结果会是什么。 ```js console.log(1); setTimeout(() => { console.log(2); }, 0); console.log(3); console.log(4); ``` 打印结果: ``` 1 3 4 2 ``` 可以看到,打印结果没有任何变化,这个题目在面试中经常出现,考的就是 `setTimeout(()=> {}, 0)`会在什么时候执行。这就需要我们了解同步任务、异步任务的执行顺序,即前面讲到的**事件循环机制**。 解释:先等同步任务执行完成后,再执行异步任务。 同理,我们再来看看下面这段伪代码: ```js setTimeout(() => { console.log('异步任务'); }, 2000); // 伪代码 sleep(5000); //表示很耗时的同步任务 ``` 上面的代码中,异步任务不是 2 秒之后执行,而是等耗时的同步任务执行完毕之后,才执行。那这个异步任务,是在 5 秒后执行?还是在 7 秒后执行?这个作业,留给读者你来思考~ ### 举例 3(较真系列) ```js setTimeout(() => { console.log('异步任务'); }, 1000); ``` 上面的代码中,等到 1 秒之后,真的会执行异步任务吗?其实不是。 在浏览器中, setTimeout()/ setInterval() 的每调用一次定时器的最小时间间隔是**4毫秒**,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的 setInterval 的回调函数阻塞导致的。 上面的案例中,异步任务需要等待 1004 毫秒之后,才会从 Event Table 进入到 Event Queue。这在面试中也经常被问到。 ## 异步任务举例 ### 例 1:加载图片 ```js // 加载图片的异步任务 function loadImage(file, success, fail) { const img = new Image(); img.src = file; img.onload = () => { // 图片加载成功 success(img); }; img.onerror = () => { // 图片加载失败 fail(new Error('img load fail')); }; } loadImage( 'images/qia nguyihao.png', (img) => { console.log('图片加载成功'); document.body.appendChild(img); img.style.border = 'solid 2px red'; }, (error) => { console.log('图片加载失败'); console.log(error); } ); ``` ### 例 2:定时器计时,移动 DOM 元素 ```js // 函数封装:定义一个定时器,每间隔 delay 毫秒之后,执行 callback 函数 function myInterval(callback, delay = 100) { let timeId = setInterval(() => callback(timeId), delay); } myInterval((timeId) => { // 每间隔 500毫秒之后,向右移动 .box 元素 const myBox = document.getElementsByClassName('box')[0]; const left = parseInt(window.getComputedStyle(myBox).left); myBox.style.left = left + 20 + 'px'; if (left > 300) { clearInterval(timeId); // 每间隔 10 毫秒之后,将 .box 元素的宽度逐渐缩小,直到消失 myInterval((timeId2) => { const width = parseInt(window.getComputedStyle(myBox).width); myBox.style.width = width - 1 + 'px'; if (width <= 0) clearInterval(timeId2); }, 10); } }, 200); ``` ## 参考链接 - [JS-同步任务,异步任务,微任务,和宏任务](https://github.com/PleaseStartYourPerformance/javaScript/issues/34) - [JS 同步异步宏任务微任务](https://juejin.cn/post/6875605533127081992)、[JavaScript 中事件循环的理解](https://zhuanlan.zhihu.com/p/364475433)、[javascript 事件循环机制](https://github.com/reng99/blogs/issues/34) - [如何实现比 setTimeout 快 80 倍的定时器?](https://mp.weixin.qq.com/s/NqzWkeOhqAU85XPkJu_wCA) ================================================ FILE: 06-JavaScript基础:异步编程/02-Ajax入门和发送http请求.md ================================================ --- title: 02-Ajax入门和发送http请求 publish: true --- ## 同步和异步回顾 ### 同步和异步的简单理解 - 同步:必须等待前面的任务完成,才能继续后面的任务。 - 异步:不受当前任务的影响。 拿排队举例: - 同步:在银行排队时,只有等到你了,才能够去处理业务。 - 异步:在排队的时候,可以玩手机。 ### 异步更新网站 我们在访问一个普通的网站时,当浏览器加载完`HTML、CSS、JS`以后,网站的内容就固定了。如果想让网站内容发生更改,就必须**刷新**页面才能够看到更新的内容。 可如果用到**异步更新**,情况就大为改观了。比如,我们在访问新浪微博时,看到一大半了,点击底部的**加载更多**,会自动帮我们加载更多的微博,同时页面并不会整体刷新。 试想一下,如果没有异步刷新的话,每次点击“加载更多”,网页都要重新刷新,体验就太糟糕了。 web 前端里的异步更新,就要用到 Ajax。很多人说,如果没有 Ajax,就没有互联网的今天。 关于同步和异步的更详细介绍,可以参考本项目的另外一篇文章:《05-JavaScript 基础:异步编程和 Ajax/01-单线程和异步》 ## Ajax ### Ajax 的概念 在浏览器中,我们可以在不刷新页面的情况下,通过 Ajax 的方式去获取一些新的内容。 Ajax:Asynchronous Javascript And XML(异步 JavaScript 和 XML)。它并不是凭空出现的新技术,而是对于现有技术的结合。Ajax 的核心是 js 对象:**XMLHttpRequest**。 ### Ajax 原理(发送 Ajax 请求的五个步骤) > 其实也就是 使用 XMLHttpRequest 对象的五个步骤。 我们先回忆一下,一个完整的 HTTP 请求需要的是: - 请求的网址、请求方法 get/post。 - 提交请求的内容数据、请求主体等。 - 接收响应回来的内容。 发送 Ajax 请求的五个步骤: (1)创建异步对象,即 XMLHttpRequest 对象。 (2)使用 open 方法设置请求参数。`open(method, url, async)`。参数解释:请求的方法、请求的 url、是否异步。第三个参数如果不写,则默认为 true。 (3)发送请求:`send()`。 (4)注册事件:注册 onreadystatechange 事件,状态改变时就会调用。 如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。 (5)服务端响应,获取返回的数据。 ## XMLHttpRequest 对象详解 我们在上一段讲解了使用 XMLHttpRequest 对象的五个步骤。本段,我们讲一下注意事项。 ### 发送请求 发送请求的方法: ```javascript open(method, url, async); ``` 参数解释: - method:请求的类型;GET 或 POST - url:文件在服务器上的位置 - async:true(异步)或 false(同步) 另外还有个方法:(仅用于 POST 请求) ```javascript send(string); ``` ### POST 请求时注意 如果想让 像 form 表单提交数据那样使用 POST 请求,就需要使用 XMLHttpRequest 对象的 setRequestHeader()方法 来添加 HTTP 头。然后在 send() 方法中添加想要发送的数据: ```javascript xmlhttp.open('POST', 'ajax_test.php', true); xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xmlhttp.send('name=smyhvae&age=27'); ``` ### onreadystatechange 事件 注册 onreadystatechange 事件后,每当 readyState 属性改变时,就会调用 onreadystatechange 函数。 readyState:(存有 XMLHttpRequest 的状态。从 0 到 4 发生变化) - 0: 请求未初始化 - 1: 服务器连接已建立 - 2: 请求已接收 - 3: 请求处理中 - 4: 请求已完成,且响应已就绪 status: - 200: "OK"。 - 404: 未找到页面。 在 onreadystatechange 事件中,**当 readyState 等于 4,且状态码为 200 时,表示响应已就绪**。 ### 服务器响应的内容 - responseText:获得字符串形式的响应数据。 - responseXML:获得 XML 形式的响应数据。 如果响应的是普通字符串,就使用 responseText;如果响应的是 XML,使用 responseXML。 ## 手写 Ajax ### 手写第一个 Ajax 请求 get 请求: ```js //【发送ajax请求需要五步】 //(1)创建XMLHttpRequest对象 var xmlhttp = new XMLHttpRequest(); //(2)设置请求的参数。包括:请求的方法、请求的url。 xmlhttp.open('get', '02-ajax.php'); //(3)发送请求 xmlhttp.send(); //(4)注册事件。 onreadystatechange事件,状态改变时就会调用。 //如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。 xmlhttp.onreadystatechange = function () { // 为了保证 数据 完整返回,我们一般会判断 两个值 if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { //(5)服务端相应:如果能够进入这个判断,说明数据请求成功了 console.log('数据返回成功:' + JSON.stringify(xmlhttp.responseText)); // 伪代码:按业务需要,将接口返回的内容显示在页面上 // document.querySelector('h1').innerHTML = xmlhttp.responseText; } }; ``` post 请求: ```js //(1)异步对象 var xmlhttp = new XMLHttpRequest(); //(2)设置请求参数。包括:请求的方法、请求的url。 xmlhttp.open('post', '02.post.php'); // 如果想要使用post提交数据,必须添加此行 xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); //(3)发送请求 xmlhttp.send('name=fox&age=18'); //(4)注册事件 xmlhttp.onreadystatechange = function () { //(5)服务端相应 if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { alert(xmlhttp.responseText); } }; ``` ### 封装 Ajax 请求(重要) 上面的代码,执行顺序很好理解,但在实战开发中,是不会这么写的。假如你的页面中,需要调十次接口,那岂不是要手写十遍 Ajax 请求?这样会导致大量的重复代码。 所以,我们需要把重复代码封装成一个公共函数,然后通过**回调函数**处理成功和失败的逻辑。 封装 Ajax 请求的代码如下:(get 请求为例) ```js // 封装 Ajax为公共函数:传入回调函数 success 和 fail function myAjax(url, success, fail) { // 1、创建XMLHttpRequest对象 var xmlhttp; if (window.XMLHttpRequest) { xmlhttp = new XMLHttpRequest(); } else { // 兼容IE5、IE6浏览器。不写也没关系 xmlhttp = new ActiveXObject('Microsoft.XMLHTTP'); } // 2、发送请求 xmlhttp.open('GET', url, true); xmlhttp.send(); // 3、服务端响应 xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { var obj = JSON.parse(xmlhttp.responseText); console.log('数据返回成功:' + obj); success && success(xmlhttp.responseText); } else { // 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。 fail && fail(new Error('接口请求失败')); } }; } // 单次调用 ajax myAjax('a.json', (res) => { console.log(res); }); // 多次调用 ajax。接口请求顺序:a --> b --> c myAjax('a.json', (res) => { console.log(res); myAjax('b.json', (res) => { console.log(res); myAjax('c.json', (res) => { console.log(res); }); }); }); ``` 学会了封装 get 请求之后,封装 post请求也是类似的写法。 ### Ajax 请求:get 请求举例 (1)index.html: ```html Document

    Ajax 发送 get 请求

    ``` (2)02-ajax.php: ```php ``` 效果如下: ![](http://img.smyhvae.com/20180228_1605.gif) ### Ajax 多个接口的嵌套请求 我们在做异步任务的时候,经常会涉及到多个接口的嵌套请求。比如说,接口 1 请求完成后,需要根据接口 1 的数据请求接口 2;接口 2 请求完成后,需要根据接口 3 的数据请求接口 3,以此类推。 需求描述: - 请求接口 1,根据用户名获取用户 id - 请求接口 2,根据用户 id 获取用户的年龄、性别等信息。 代码实现思路: ```js myAjax('http://localhost:8888/php/user.php?name=千古', (userInfo) => { // 根据第一个接口返回的 userInfo.id,继续请求第二个接口 myAjax(`http://localhost:8888/php/info.php?id=${userInfo['id']}`, (res) => { console.log(response); }); }); ``` 我们在实战开发中,经常会涉及到接口请求之间的**依赖**:需要上一个接口请求返回的数据,来发送本次请求。这种场景经常遇到,需要记住。 但这种层层嵌套的代码,会导致**回调地域**的问题,也不利于维护。我们在后续的 ES6 章节中,会讲解 Promise,它是一种更优雅的异步任务解决方案。 ## jQuery 中的 Ajax JQuery 作为最受欢迎的 js 框架之一,常见的 Ajax 已经帮助我们封装好了,只需要调用即可。更为详细的 api 文档可以查阅:[w3cSchool_JQueryAjax](http://www.w3school.com.cn/jquery/jquery_ref_ajax.asp) 格式举例: ```javascript $.ajax({ url: 'https://xxx.com/getUserInfo.php', // 接口的请求地址 data: 'name=fox&age=18', // 请求参数 type: 'GET', //请求的方式 success: function (argument) { // 接口请求成功时调用 console.log('接口请求成功'); }, beforeSend: function (argument) {}, // 在发送请求之前调用,可以做一些验证之类的处理 error: function (argument) { // 接口请求失败时调用 console.log('接口请求失败'); }, }); ``` 代码举例: (1)index.html ```html jquery-ajax
    ``` (2)data.php: ```php ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ``` ``` ================================================ FILE: 06-JavaScript基础:异步编程/03-Ajax传输json和XML.md ================================================ --- title: 03-Ajax传输json和XML publish: true --- ## Ajax 传输 JSON ### JSON 的语法 JSON(JavaScript Object Notation):是 ECMAScript 的子集。作用是进行数据的交换。语法更为简洁,网络传输、机器解析都更为迅速。 语法规则: - 数据在键值对中 - 数据由逗号分隔 - 花括号保存对象 - 方括号保存数组 数据类型: - 数字(整数或浮点数) - 字符串(在双引号中) - 逻辑值(true 或 false) - 数组(在方括号中) - 对象(在花括号中) - null 示例: ```json // 对象 { "name":"fox", "age":"18", "sex":"true", "car":null } // 数组 [ { "name":"小小胡", "age":"1" }, { "name":"小二胡", "age":"2" } ] ``` ### JavaScript 中:json 字符串 <--> js 对象 基本上,所有的语言都有**将 json 字符串转化为该语言对象**的语法。 比如在 js 中: - JSON.parse():将 JSON 字符串转化为 js 对象。例如: ```javascript // 将 JSON 字符串格式化为 js 对象 var jsObj = JSON.parse(ajax.responseText); ``` - JSON.stringify():将 JS 对象转化为 JSON 字符串。例如: ```javascript var Obj = { name: 'fox', age: 18, skill: '撩妹', }; console.log(Obj); // 将 js 对象格式化为 JSON 字符串 var jsonStr = JSON.stringify(Obj); ``` ### PHP 中:json 字符串 <--> js 对象 - **json_decode()**方法:将`json`字符串转化为变量。 - **json_encode()**方法:将变量转化为`json`字符串。 代码举例: ```php "; // php数组 $arrayName = array('name' =>'littleFox' ,'age' => 13 ); // php对象 转化为 json字符串 print_r(json_encode($arrayName)); ?> ``` 输出结果: ```bash stdClass Object ( [name] => itcast [age] => 54 [skill] => 歌神 ) {"name":"littleFox","age":13} ``` ### ajax 请求解析 json(举例) (1)Person.json: ```json { "name": "小强", "skill": "砍树", "friend": "老板" } ``` (2)myJson.php: ```php ``` (3)getJson.html: ```html Document

    获取 json 数据

    ``` 演示效果: ![](http://img.smyhvae.com/20180228_1740.gif) ## Ajax 传输 XML ### XML 语法 XML(Extensible Markup Language):可扩展标记语言。详细语法可以查看:[#](http://www.w3school.com.cn/xml/index.asp)。 **1、XML 声明:** ```xml ``` 第一行的声明,指定了 XML 版本(1.0)以及使用的编码。 **2、自定义标签:** XML 中没有默认的标签,所有的标签都是我们自己已定义的。例如: ```xml ``` XML 中没有单标签,都是双标签。 **3、根节点:** XML 中必须要有一个根节点,所有的子节点都放置在根节点下。例如: ```xml ``` ### XML 解析 因为 XML 就是标签,所以我们可以直接用**解析 Dom 元素**的方法解析 XML。 **解析过程:** (1)html 部分:(包含 xml ) ```html Document fox 18 小花花 ``` (2)解析 xml: ```html ``` ### ajax 请求解析 xml(举例) (1)get_xml.php:(里面包含了 xml 文件) ```php ``` 上方代码解释: - php 自带了 读取 xml 文件的方法。 - 在 php 中,如果要使用 xml 传输数据,则需要使用 header()设置返回的内容为 xml。 (2)get_xml.html:(Ajax 请求,获取并解析 xml) ```html Document ``` ================================================ FILE: 06-JavaScript基础:异步编程/04-同源和跨域.md ================================================ --- title: 04-同源和跨域 publish: true --- ## 同源和跨域 ## 同源 同源策略是浏览器的一种安全策略,所谓同源是指,域名,协议,端口完全相同。 ## 跨域问题的解决方案 从我自己的网站访问别人网站的内容,就叫跨域。 ![](http://img.smyhvae.com/20180228_2231.png) 出于安全性考虑,浏览器不允许ajax跨域获取数据。 - iframe:处于安全性考虑,浏览器的开发厂商已经禁止了这种方式。 - JSONP:script 标签的 src 属性传递数据。 ## JSONP JSONP(JSON with Padding):带补丁的 json,本质是利用了 ``标签具有可跨域的特性,由服务端返回一个预先定义好的JS函数的调用,并且将服务器数据以该函数参数的形式传递过来。此方法需要前后端配合完成。 我们知道, html标签的 src 属性是支持跨域的: ```html ``` jsonp 就是利用这个特性实现的跨域,但用的是 script 标签。如下: ```html Document ``` 上方那一行的代码,意思是:刷新A服务器上的index页面后,会去请求 B 服务器上的 `myData.php` 这个页面。而且请求的方式是 get 请求。 但是 B 服务器上的页面不是你想请求就可以请求的,大家一起配合才可以。 **具体实现步骤:** 需要首先声明的是,jsonp 只能通过 GET 方式进行请求。 (1)A客户端的代码: ```html Document ``` 我们来分析上方代码中的最后一行的那个url:A 客户端请求的是 B服务器上的 `01.php`页面。url里有个`callback1=fn`,意思是:callback1是A和B 之间的约定,约定后,将执行方法 fn。 其实,fn方法已经在最后一行代码中执行了。只不过,fn方法里的data数据,是从 B 服务器中获取的。 (2)B服务器端的代码: ```php ``` 代码解释: 第一行的`callback1` 是A和B之间的约定,二者必须一致。 echo语句中输出的内容,即要返回给A客户端的内容,此内容会保存在 A 客户端的fn方法的data里。 data[0]指的是 zhangsan。 `json_encode`指的是,将php对象转化为 json。 刷新A页面,输出结果为: ``` mycallBack(["zhangsan","lisi","zhaoliu"]) ``` ## jQuery 中的 JSONP 我们知道,jQuery 中发送 Ajax 请求,格式是: ```javascript $("#btn").click(function(){ $.ajax({ url:"./data.php?callback1=fn", dataType:"jsonp", type:"get", //jsonp:"callback1", //传递给B服务器的回调函数的名字(默认为 callback) //jsonCallBack:"fn" //自定义的函数名称。默认为 jQuery 自动生成的随机函数名 success:function(data){ alert(data); //$("#showInfo").html(data); }, error:function(e){ console.log(e); } }); }); ``` 那如果数据是 JSONP,上方代码则改为: ```javascript $("#btn").click(function(){ $.ajax({ url:"./data.php?fn", dataType:"text", type:"get", success:function(data){ alert(data); //$("#showInfo").html(data); }, error:function(e){ console.log(e); } }); }); ``` ## 参考链接 参考链接:https://www.cnblogs.com/2050/p/3191744.html ================================================ FILE: 06-JavaScript基础:异步编程/05-回调函数.md ================================================ --- title: 05-回调函数 --- 我们在前面的文章《JavaScript 基础:异步编程/单线程和异步》中讲过,Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时,⼤部分情况都是通过回调函数来进⾏。 (如果你还不了解单线程和异步的概念,可以先去回顾上一篇文章。) ## 回调函数的定义 把函数 A 传给另一个函数 B 调用,那么函数 A 就是回调函数。 例如在浏览器中发送 ajax 网络请求,或者在定时器中执行异步任务,就是最常⻅的异步场景。发送请求后,需要等待一段时间,等服务端响应之后我们才能拿到结果。如果我们希望**等待异步任务结束之后再执⾏想要的操作**,就只能通过**回调函数**这样的⽅式进⾏处理。 ```js const dynamicFunc = function (callback) { setTimeout(function () { console.log("一开始在这里执行异步任务 task1,延迟3秒执行"); // task1: total 计数 let total = 0; for (let i = 0; i < 10; i++) { total += i; } // 等待异步任务 task1 执行完成后,通过回调传入的 callback() 函数,通知外面的调用者,可以开始做后续任务 task2 了 // 如果有需要的话,可以把 task1 的执行结果 total 传给外面。 callback && callback(total); }, 3000); }; // 执行同步任务 task2。需要先等 异步任务 task1做完。 dynamicFunc(function (value) { console.log("外面监听到,异步任务 task1已经完成了,并且还能拿到 task1的执行结果 value"); console.log("task1的返回值value:" + value); // task2:将task1的执行结果乘以2 const result = value * 2; console.log("result:" + result); }); ``` 上⾯的例⼦中,dynamicFunc() 函数里面的 setTimeout()就是⼀个异步函数,在里面执行了一些异步任务,延迟3秒执行。dynamicFunc() 的参数 callback() 就是一个回调函数。这段代码的诉求是:**先等待 异步任务 task1 做完,再做 同步任务task2**。我们来分析一下。 已知异步任务 task1 需要3秒才能做完。**3秒结束后,通知 dynamicFunc 函数的调用者,里面的异步任务 task1 已经做完了,外面可以开始做后续的任务 task2 了**。那要怎么通知呢?在ES5中,最常见的做法就是**需要回调传入的 callback 函数**(也就是回调函数), 通知外面的调用者。并且,如果有需要的话,外面还可以拿到异步任务task1的执行结果 total(详见代码注释)。 (注:`callback`这个单词并不是关键字,可以自由命名,我们通常习惯性地用“回调”这个词的英文名 callback 代表回调函数。) ## 回调函数的异常处理 实际开发中,为什么会经常存在异步任务呢?这是因为,有很多函数在执行时无法立即完成,我们也不知道它什么时候能完成。但是,我们需要等待它完成后,才能做接下来的事情。换句话说,我们接下来要做的事情,需要依赖此前的异步任务。 比如, ajax 网络请求就是典型的异步任务。在渲染一个页面时,我们需要请求接口,获取页面所需要的数据。等接口请求完成、数据准备好之后,前端就可以对数据进行处理,并将数据渲染到页面了。前端做的这部分事情,就是在回调函数里面做。 当然,异步任务在执行时可能出现异常、错误信息、执行失败等等。当出现异常时,往往导致后续的回调函数无法执行。这就需要在异步任务中将异常信息通知给外部。 代码举例如下: ```js // 封装异步任务 const dynamicFunc = function (number, successCallback, failureCallback) { setTimeout(function () { console.log('一开始在这里执行异步任务 task1,延迟3秒执行'); let total = 0; for (let i = 0; i < 10; i++) { total += i; } if (number > 0) { // 异步任务执行成功 successCallback && successCallback(total); } else { // 异步任务执行鼠标 failureCallback && failureCallback('异步任务执行失败'); } }, 3000); }; // 执行异步任务:等待 异步任务 执行完成后,再执行回调函数。 dynamicFunc( 100, (value) => { console.log('异步函数调用成功:' + value); // task2:将task1的执行结果乘以2 const result = value * 2; console.log('result:' + result); }, (err) => { console.log('异步函数调用失败:', err); } ); ``` ## 处理异步任务的基本模型 我们以“发送网络请求”为例,通过回调函数处理异步任务时,既有请求成功的情况,也有请求失败的情况。其基本处理模型如下: (1)调用一个异步函数,在这个函数中发送网络请求(也可以用定时器来模拟异步任务)。 (2)如果网络请求成功,则告知调用者请求成功,并将接口返回的数据传递出去。 (3) 如果网络请求失败,则告知调用者发送失败,并将错误信息传递出去。 ES5中,回调函数处理异步任务的基本代码结构如下: ```js // ES5中,使用传统的回调函数,处理异步任务的基本模型 // 封装异步任务 function requestData(url, successCallback, failureCallback) { const res = { retCode: 0, data: 'qiangu yihao`s data', errMsg: 'network is error', }; setTimeout(() => { if (res.retCode == 0) { // 网络请求成功 successCallback(res.data); } else { // 网络请求失败 failureCallback(res.errMsg); } }, 1000); } // 调用(请求)异步任务 requestData( 'www.qianguyihao.com/xxx', // 成功监听 res => { console.log('异步任务执行成功:', res); }, // 失败监听 err => { console.log('异步任务执行失败:', err); } ); ``` 我们一定要记住这个处理模型,它我们学习异步编程的范式之一。如果前端接下来要做的事情需要依赖这个异步任务、需要等待这个异步任务做完之后才能继续,那就符合上面的处理模型。 ## ES5中,回调的缺点(异步代码的困境) 上面的回调函数的写法,都是ES5的写法。ES5中回调的写法比较直观,不需要 return,层层嵌套即可。但也存在两个问题: - 1. 如果嵌套过深,则会出现**回调地狱**的问题。 - 2. 不同的异步函数,回调的参数,在写法上可能不一致,导致不规范、且需要**单独记忆**。 我们来具体看看这两个问题。 ### 1、回调地狱的问题 如果多个异步任务存在依赖关系(比如,需要等第一个异步任务执行完成后,才能执行第二个异步函数;等第二个异步任务执行完毕后,才能执行第三个异步任务),就需要多个异步任务进⾏层层嵌套,⾮常不利于后续的维护,而且会导致**回调地狱**(callback hell)的问题。 简而言之,当一个回调函数嵌套另一个回调函数时,就会出现一个嵌套结构。如果嵌套次数过多,就会出现回调地狱的情况。像下面这样: ![img](https://img.smyhvae.com/callback-hell.jpeg) 关于回调地狱,我们来举一个形象的例子: > 假设买菜、做饭、洗碗、倒厨余垃圾都是异步的。 > 但真实的场景中,实际的操作流程是:买菜成功之后,才能开始做饭。做饭成功后,才能开始洗碗。洗碗完成后, 再倒厨余垃圾。这里的一系列动作就涉及到了多层嵌套调用,也就是回调地狱。 关于回调地狱,我们来看看几段代码举例。 1.1、定时器的代码举例:(回调地狱) ```js setTimeout(function () { console.log('qiangu1'); setTimeout(function () { console.log('qiangu2'); setTimeout(function () { console.log('qiangu3'); }, 3000); }, 2000); }, 1000); ``` 1.2、Node.js 读取文件的代码举例:(回调地狱) ```js fs.readFile(A, 'utf-8', function (err, data) { fs.readFile(B, 'utf-8', function (err, data) { fs.readFile(C, 'utf-8', function (err, data) { fs.readFile(D, 'utf-8', function (err, data) { console.log('qianguyihao:' + data); }); }); }); }); ``` 上面代码的逻辑为:先读取 A 文本内容,再根据 A 文本内容读取 B,然后再根据 B 的内容读取 C。为了实现这个业务逻辑,上面的代码就很容易形成回调地狱。 1.3、ajax 请求的代码举例:(回调地狱) ```js // 伪代码 ajax('a.json', (res1) => { console.log(res1); ajax('b.json', (res2) => { console.log(res2); ajax('c.json', (res3) => { console.log(res3); }); }); }); ``` ### 2、回调写法不一致的问题 我们需要自己去设计回调函数,包括回调函数的参数格式 、调用方式等等。 ```js // Node.js 读取文件时,成功回调和失败回调,是通过 error参数来区分 readFile('d:\\readme.text', function (err, data) { if (error) { console.log('文件读取失败'); } else { console.log('文件读取成功'); } }); // jQuery的 ajax 写法中,成功回调和失败回调,是通过两个回调函数来区分 $.ajax({ url: '/ajax.json', success: function (response) { console.log('文件读取成功'); }, error: function (err) { console.log('文件读取失败'); }, }); ``` 我们可以看到,上面的回调函数的代码中,成功回调和失败回调,**参数的写法不一致**。在实战开发中,**封装异步函数的人和调用异步函数的人,往往不是同一个人**。甚至可能出现的极端的情况是,回调函数里需要传很多参数,**参数的顺序也不一致**,各有各的风格,每个人写得都不一样。因为这种回调参数的写法**不一致、不规范**的问题,所以需要单独记忆,导致在调用时需要小心翼翼,很容易出错。 ### 小结 按照上面的分析,在 ES5 中处理异步任务时,产生的这两个问题,ES6 中的 Promise 就可以解决。当然, Promise 的强大功能,不止于此。我们去下一篇文章一探究竟。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/06-Promise入门详解.md ================================================ --- title: 06-Promise入门详解 --- ## 前言 Promise 是 JavaScript 中特有的语法。可以毫不夸张得说,Promise 是ES6中最重要的语法,没有之一。初学者可能对 Promise 的概念有些陌生,但是不用担心。大多数情况下,使用 Promise 的语法是比较固定的。我们可以先把这些固定语法和结构记下来,多默写几遍;然后在实战开发中逐渐去学习和领悟 Promise 的原理、底层逻辑以及细节知识点,自然就慢慢掌握了。 在了解 Promise 之前,必须要知道什么是回调函数,这是必不可少的前置知识。关于回调函数的知识,已经在上一篇文章中做了讲解。 ## Promise 的介绍和优点(为什么需要 Promise?) Promise 是异步编程的一种**新的解决方案和规范**。ES6将其写进了语言标准,统一了用法,原生提供了 Promise 对象。 Promise 对象, 可以**用同步的表现形式来书写异步代码**(也就是说,代码看起来是同步的,但本质上的运行过程是异步的)。使用 Promise 主要有以下优点: - 1、可以很好地解决ES5中的**回调地狱**的问题(避免了层层嵌套的回调函数)。 - 2、统一规范、语法简洁、可读性和和可维护性强。 - 3、Promise 对象提供了简洁的 API,使得管理异步任务更方便、更灵活。 从语法上讲,Promise 是一个构造函数。从功能上来说,Promise 对象用于封装一个异步操作,并获取其成功/ 失败的结果值。 从写法规范上讲,**Promise 本质上是处理异步任务的一种编写规范**,要求每个人都按照这种规范来写。异步任务成功了该怎么写、异步任务失败了该怎么写、成功或者失败之后怎么通知调用者,这些都有规定的写法。Promise 的目的就是要让每个使用ES6的人都遵守这种写法规范。 Promise 的伪代码结构,大概是这样的: ```js // 伪代码1 myPromise() .then( function () {}, function () {} ) .then( function () {}, function () {} ) .then( function () {}, function () {} ); // 伪代码2 是时候展现真正的厨艺了().然后(买菜).然后(做饭).然后(洗碗); ``` 上面的伪代码可以看出,业务逻辑上层层递进,但是代码写法上却十分优雅,没有过多的嵌套。 ## Promise 的基本使用 ES5中,使用传统的回调函数处理异步任务时,其基本模型的写法已在上一篇内容“回调函数”里讲过。 ES6中,有了 Promise之后,我们可以对那段代码进行改进(基本模型不变)。你会发现,代码简洁规范了许多。 使用 Promise 处理异步任务的**基本代码结构**如下,我们先来认识一下: ```js // 使用 Promise 处理异步任务的基本模型 // 封装异步任务 function requestData(url) { // resolve 和 reject 这两个单词是形参,可以自由命名。大家的习惯写法是写成 resolve 和 reject const promise = new Promise((resolve, reject) => { const res = { retCode: 0, data: 'qiangu yihao`s data', errMsg: 'not login', }; setTimeout(() => { if (res.retCode == 0) { // 网络请求成功 resolve(res.data); } else { // 网络请求失败 reject(res.errMsg); } }, 1000); }); return promise; } // 调用异步任务 requestData('www.qianguyihao.com/index1').then(data => { console.log('异步任务执行成功:', data); }).catch(err=> { console.log('异步任务执行失败:', err); }) // 再次调用异步任务 requestData('www.qianguyihao.com/index2').then(data => { console.log('异步任务再次执行成功:', data); }).catch(err=> { console.log('异步任务再次执行失败:', err); }) // 调用异步任务(写法2) /* 这段代码的写法比较啰嗦。一般推荐上面的写法。 const myPromise = requestData('www.qianguyihao.com/index1'); myPromise.then(data => { console.log('异步任务执行成功:', data); }); myPromise.catch(err => { console.log('异步任务执行失败:', err); }); const myPromise2 = requestData('www.qianguyihao.com/index2'); myPromise2.then(data => { console.log('异步任务执行成功:', data); }); myPromise2.catch(err => { console.log('异步任务执行失败:', err); }); */ ``` 在日常开发中使用Promise时,80%以上的场景都符合上面的代码结构。你说它重不重要?我们暂且先记下,默写十遍,形成肌肉记忆,然后继续往下边学习边理解。 ## Promise 的状态和回调函数 Promise的三种状态是我们需要学习的第一个概念,也是最重要的概念,理解它才能理解 Promise的用法。 ### Promise 对象的 3 种状态 在使用 Promise 时,我们可以将它划分为三种状态: - `pending`:等待中。属于初始状态,既没有被兑现,也没有被拒绝。 - `fulfilled`:已兑现/已解决/成功。执行了`resolve()` 时,立即处于该状态,表示 Promise已经被**解决**,任务**执行成功**。 - `rejected`:已拒绝/失败。执行了 `reject()`时,立即处于该状态,表示 Promise已经被**拒绝**,任务**执行失败**。 具体解释: 1、Promise 的中文名翻译为“承诺”(一般不称呼中文名)。resolve 的中文翻译为“解决”,reject 的中文翻译为“拒绝”。 2、当 new Promise()执行之后,promise 对象的状态会被初始化为`pending`,这个是初始状态。`new Promise()`这行代码,括号里的内容是同步执行的。括号里可以再定义一 异步任务的 function,function 有两个参数:resolve 和 reject。如下: - 如果异步任务成功了,请执行 resolve(),此时,promise 的状态会自动变为 fulfilled。 - 如果异步任务失败了,请执行 reject(),此时,promise 的状态会自动变为 rejected。 3、什么时候算成功,什么时候算失败呢?这是**你自己定**的,需要结合具体需求和业务逻辑灵活决定。 关于 promise 的状态改变,以及如何处理状态改变,伪代码及详细注释如下: ```javascript // 创建 promise 实例 const promise = new Promise((resolve, reject) => { //进来之后,promise 的状态为 pending console.log('同步代码'); //这行代码是同步的 //开始执行异步操作(这里开始,根据具体需求写异步的代码,比如ajax请求 or 开启定时器) if (异步的ajax请求成功) { console.log('233'); // 如果请求成功了,请写resolve(),此时,promise的状态会自动变为fulfilled(成功状态) resolve('请求成功,并传参'); } else { // 如果请求失败了,请写reject(),此时,promise的状态会被自动变为rejected(失败状态) reject('请求失败,并传参'); } }); console.log('qianguyihao'); //调用promise的then():开始处理成功和失败 promise.then( successValue => { // 处理 promise 的成功状态:如果promise的状态为fulfilled,则执行这里的代码 console.log(successValue, '回调成功了'); // 这里的 successMsg 是前面的 resolve('请求成功,并传参') 传过来的参数 }, errorMsg => { //处理 promise 的失败状态:如果promise的状态为rejected,则执行这里的代码 console.log(errorMsg, '回调失败了'); // 这里的 errorMsg 是前面的 reject('请求失败,并传参') 传过来的参数 } ); ``` 上面的注释要多看几遍。 ### Promise 的回调函数 Promise的回调函数,伪代码如下: ```js const promise = new Promise(executor); // 【划重点】下面这两行代码是等价的,选其中一种写法即可。这两种写法没有区别,只是写法形式上的区别 promise.then(onFulfilled, onRejected); promise.then(onFulfilled).catch(onRejected); ``` Promise是一个类,通过 `new Promise()` 进行**实例化**,构造出一个 Promise 实例对象。 1、Promise 的构造函数中需要传入一个参数,这个参数是一个回调函数,常用于处理异步任务。这个回调函数有一个专有名词叫 **executor**(执行器),因为在 `new Promise()` 时,这个函数会**立即执行**。 可以在该回调函数中传入两个参数:resolve 和 reject。我们可以在适当的时机执行 resolve()、reject(),用于改变当前 Promise 实例的状态到**成功**或**失败**。 (2)当Promise状态变为成功时,会触发 then() 方法里的回调函数的执行,对成功的返回结果进行处理。 (3)当Promise状态变为失败时,会触发 catch() 方法里的回调函数的执行,,对失败的返回结果进行处理。 2、`then()`方法的括号里面有两个参数,分别代表两个回调函数 **onFulfilled** 和 **onRejected**,这两个函数一直处于**监听状态**: - 参数1:**成功的回调函数**。如果 Promise 的状态为 fulfilled(意思是:任务执行成功),则触发 onFulfilled 函数的执行。 - 参数2:**失败的回调函数**。如果 Promise 的状态为 rejected(意思是,任务执行失败),则触发 onRejected 函数的执行。 3、**只有 Promise 的状态被改变之后,才会走到 then() 或者 catch()**。也就是说,在 new Promise() 时,如果没有写 resolve(),则 promise.then() 不执行;如果没有写 reject(),则 promise.catch() 不执行。 4、resolve()和 reject()这两个方法,可以给 promise.then()、promise.catch()传递参数。 5、then() 可以被多次调用,会按照顺序执行。比如: ```js const promise = new Promise(executor); // then() 可以被多次调用 promise.then(onFulfilled, onRejected); promise.then(onFulfilled, onRejected); ``` ### Promise的状态图 ![image-20230624100254023](https://img.smyhvae.com/image-20230624100254023.png) 上面的Promise状态图很经典,需要反复研读,了然于胸。 ### Promise 的状态一旦改变,就不能再变 Promise 的状态一旦改变,就确定下来了,不能再变。也不能再次执行 resolve()或者 reject()来改变状态。Promise 的状态改变,是不可逆的。 代码举例: ```js const p = new Promise((resolve, reject) => { resolve(1); // 代码执行到这里时, promise状态是 fulfilled resolve(111); // 这行重复代码写了没用,等于没写 reject(2); // 尝试修改状态为 rejected,是不行的。因为状态执行到上面的 resolve(1)时,已经被改变了。 }); p.then((res) => { console.log(res); }).catch((err) => { console.log(err); }); ``` 上方代码的打印结果是 1,而不是 2,详见注释。 ### new Promise() 是同步代码 `new Promise()`这行代码本身是同步的。promise 如果没有使用 resolve 或 reject 更改状态时,状态为 pending,里面的代码是同步代码。 **举例 1**:(重要) ```js // 会立即创建 Promise 实例 const promise1 = new Promise((resolve, reject) => { // 这行代码会立即执行 console.log('qianguyihao1'); }) console.log(promise1); // 此时 promise1 的状态为 pending(准备阶段) // 需要调用 promise2函数,才会创建 Promise 实例 function promise2() { return new Promise((resolve, reject) => { // 这行代码不会立即执行 console.log('qianguyihao2'); }) } ``` 上面的代码中,我既没有写 reslove(),也没有写 reject()。那么,Promise 一直处于准备阶段。 此外,需要特别注意的是,promise1 中的 console.log() 会**立即执行**,因为**Promise的执行器函数在创建 Promise 实例时就会被调用,并立即开始执行其中的代码逻辑**。 **举例 2**: ```js new Promise((resolve, reject) => { console.log('promise1'); // 这行代码是同步代码,会立即执行 }).then((res) => { console.log('promise then:' + res); // 这行代码不会执行,因为前面没有写 resolve(),所以走不到 .then }); ``` 打印结果: ``` promise1 ``` 上方代码,仔细看注释:如果前面没有写 `resolve()`,那么后面的 `.then`是不会执行的。 **举例 3**: ```js new Promise((resolve, reject) => { resolve(); console.log('promise1'); // 代码1:同步任务,会立即执行 }).then(res => { console.log('promise then'); // 代码2:异步任务中的微任务 }); console.log('千古壹号'); // 代码3:同步任务 ``` 打印结果: ``` promise1 千古壹号 promise then ``` 代码解释: 当完成异步任务之后,状态分为成功或失败,此时我们就可以用 reslove() 和 reject() 来修改 promise 的状态。 代码 1 是同步代码,所以最先执行。代码 2 是**微任务**里面的代码,所以要先等同步任务(代码 3)先执行完。当写完`resolve();`之后,就会立刻把 `.then()`里面的代码加入到微任务队列当中。 补充知识:异步任务分为“宏任务”、“微任务”两种。我们到后续的章节中再详细讲。 ## Promise 封装定时器 ### 传统写法 写法 1: ```js // 定义一个异步的延迟函数:异步函数结束1秒之后,再执行cb回调函数 function fun1(cb) { setTimeout(function () { console.log('即将执行cb回调函数'); cb(); }, 1000); } // 先执行异步函数 fun1,再执行回调函数 myCallback fun1(myCallback); // 定义回调函数 function myCallback() { console.log('我是延迟执行的cb回调函数'); } ``` 写法 2:(精简版,更常见) ```js // 定义一个异步的延迟函数:异步函数结束1秒之后,再执行cb回调函数 function fun1(cb) { setTimeout(cb, 1000); } // 先执行异步函数fun1,再执行回调函数 fun1(function () { console.log('我是延迟执行的cb回调函数'); }); ``` 上⾯的例⼦就是最传统的写法,在异步结束后通过传入回调函数的方式执⾏函数。 学习 Promise 之后,我们可以将这个异步函数封装为 Promise,如下。 ### Promise 写法 ```js function myPromise() { return new Promise((resolve) => { setTimeout(resolve, 1000); }); } /* 【重要】上面的 myPromise 也可以写成: function myPromise() { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }); } */ // 先执行异步函数 myPromise,再执行回调函数 myPromise().then(() => { console.log('我是延迟执行的回调函数'); }); ``` ## Promise 封装 Ajax 请求 ### 传统写法 ```js // 封装 ajax 请求:传入回调函数 success 和 fail function ajax(url, success, fail) { var xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', url); xmlhttp.send(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { success && success(xmlhttp.responseText); } else { // 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。 fail && fail(new Error('接口请求失败')); } }; } // 执行 ajax 请求 ajax( '/a.json', (res) => { console.log('qianguyihao 第一个接口请求成功:' + JSON.stringify(res)); }, (err) => { console.log('qianguyihao 请求失败:' + JSON.stringify(err)); } ); ``` 上面的传统写法里,定义和执行 ajax 时需要传⼊ success 和 fail 这两个回调函数,进而执行回调函数。 注意看注释,`callback && callback()`这种格式的写法,很常见。 ### Promise 写法 有了 Promise 之后,我们不需要传入回调函数,而是: - 先将 promise 实例化; - 然后在原来执行回调函数的地方,改为执行对应的改变 promise 状态的函数; - 并通过 then ... catch 或者 then ...then 等写法,实现链式调用,提高代码可读性。 和传统写法相比,promise 在写法上的大致区别是:定义异步函数的时候,将 callback 改为 resolve 和 reject,待状态改变之后,我们在外面控制具体执行哪些函数。 写法 1: ```js // 封装 ajax 请求:传入回调函数 success 和 fail function ajax(url, success, fail) { var xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', url); xmlhttp.send(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { success && success(xmlhttp.responseText); } else { // 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。 fail && fail(new Error('接口请求失败')); } }; } // 第一步:model层的接口封装 function promiseA() { return new Promise((resolve, reject) => { ajax('xxx_a.json', (res) => { // 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。 if (res.retCode == 0) { // 接口请求成功时调用 resolve('request success' + res); } else { // 接口请求失败时调用 reject({ retCode: -1, msg: 'network error' }); } }); }); } // 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据 promiseA() .then((res) => { // 从 resolve 获取正常结果:接口请求成功后,打印接口的返回结果 console.log(res); }) .catch((err) => { // 从 reject 获取异常结果 console.log(err); }); ``` 上方代码中,当从接口返回的数据`data.retCode`的值(接口返回码)不同时,可能会走 resolve,也可能会走 reject,这个由你自己的业务决定。 接口返回的数据,一般是`{ retCode: 0, msg: 'qianguyihao' }` 这种 json 格式, retCode 为 0 代表请求接口成功,所以前端对应会写`if (res.retCode == 0) `这样的逻辑。 另外,上面的写法中,是将 promise 实例定义成了一个**函数** `promiseA`。我们也可以将 promise 实例定义成一个**变量** `promiseB`,达到的效果和上面的代码是一模一样的。写法如下:(写法上略有区别) 写法 2: ```js // 第一步:model层的接口封装 const promiseB = new Promise((resolve, reject) => { ajax('xxx_a.json', (res) => { // 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。 if (res.retCode == 0) { // 接口请求成功时调用 resolve('request success' + res); } else { // 接口请求失败时调用 reject({ retCode: -1, msg: 'network error' }); } }); }); // 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据 promiseB .then((res) => { // 从 resolve 获取正常结果 console.log(res); }) .catch((err) => { // 从 reject 获取异常结果 console.log(err); }); ``` 注意,如果你用的是写法 1(将 promise 实例定义为函数),则调用 promise 的时候是`promiseA().then()`,如果你用的是写法 2(将 promise 实例定位为函数),则调用的时候用的是`promiseB.then()`。写法 1 多了个括号,不要搞混了。 ## resolve() 传入的参数(重要) 执行 resolve()之后,Promise 的状态一定会变成 fulfilled 吗?这是不一定的。 严格来说,在我们调用 resolve 时,如果 resolve()的参数中传入的值**本身不是一个Promise**,那么会将该 promise 的状态变成 fulfilled。 resolve()的参数中,可以传入哪些值,Promise会进入哪种状态呢?具体情况如下: - 情况1:如果resolve()中传入**普通的值或者普通对象**(包括 undefined),那么Promise 的状态为fulfilled。这个值会作为then()回调的参数。这是最常见的情况。 - 情况2:如果resolve()中传入的是**另外一个新的 Promise**,那么原 Promise 的状态将**交给新的 Promise 决定**。 - 情况3:如果resolve()中传入的是一个对象,并且这个对象里有实现then()方法(这种对象称为 **thenable** 对象),那就会执行该then()方法,并且根据**then()方法的结果来决定Promise的状态**。 情况3中,我们通常称这个对象为 thenable 对象。thenable 的意思是,在某个对象或者函数中定义了一个 then() 方法,我们就称其为 thenable 对象/thenable函数。注意,thenable对象里面的那个单词只能写 then,不能写其他的单词;如果写其他的单词,就不是 thenable 对象了,就不符合情况3,而是符合情况1。 扩展一下:reject()的参数中可以传入什么值呢?无论传入什么值,Promise 都会直接进入 rejected 状态,并触发 catch() 方法的执行。 情况1的代码已经在前面反复出现过了,接下来重点讲一下情况2 和情况3。 ### resolve() 中传入新的 Promise 代码举例: ```js const promise1 = new Promise((resolve, reject) => { resolve(promise2); }); const promise2 = new Promise((resolve, reject) => { reject('promise2 的 reject'); }); promise1 .then(res => { console.log('qianguyihao then'); console.log(res); }) .catch(err => { console.log('qianguyihao catch'); console.log(err); }); ``` 打印结果: ``` qianguyihao catch promise2 的 reject ``` 代码解释: promise1 在执行resolve时,传入的是 promise2。那么,promise1接下来的状态将交给 promise2 处理。因为 promise2 执行的是 reject(),所以 promise1 的状态进入 rejected,执行 catch() 方法。 上方代码中,如果把 promise1 和 promise2 的顺序换一下的话, 代码会报错: ```js const promise1 = new Promise((resolve, reject) => { resolve(promise2); }); const promise2 = new Promise((resolve, reject) => { reject('promise2 的 reject'); }); promise1 .then(res => { console.log('qianguyihao then'); }) .catch(err => { console.log('qianguyihao catch'); console.log(err); }); ``` 报错如下: ![image-20230520224902096](https://img.smyhvae.com/image-20230520224902096.png) ### resolve()中传入 thenable 对象 代码举例: ```js const promise1 = new Promise((resolve, reject) => { // resolve 里传入了一个 thenable 对象,里面有一个 then()方法,then()方法里执行的是 reject() resolve({ name: 'qianguyihao', then: (resolve, reject) => { // 可以执行 resolve,也可以执行 reject,这里以 reject 为例 reject('thenable reject'); }, }); }); promise1 .then(res => { console.log('qianguyihao then'); console.log(res); }) .catch(err => { console.log('qianguyihao catch'); console.log(err); }); ``` 打印结果: ``` qianguyihao catch thenable reject ``` 代码解释: promise1 在执行resolve时,传入的是一个 thenable 对象。thenable 对象里有一个 then()方法。那么,promise1接下来的状态将由 thenable 对象 里的 then() 方法决定。当前的代码中, then() 里执行的是 reject(),所以 promise1 的状态进入 rejected,执行 catch() 方法。 上方代码中,如果把 thenable 对象里的单词`then`改成`then1`会怎么样呢?那它就不是 thenable 对象,只是一个普通的对象,代码如下: ```js const promise1 = new Promise((resolve, reject) => { // resolve 里传入了一个 thenable 对象,里面有一个 then()方法,then()方法里执行的是 reject() resolve({ name: 'qianguyihao', // 把 单词 then 改成 then1,就不符合 thenable 对象 的特征了 then1: (resolve, reject) => { reject('thenable resolve'); }, }); }); promise1 .then(res => { console.log('qianguyihao then'); console.log(JSON.stringify(res)); }) .catch(err => { console.log('qianguyihao catch'); console.log(err); }); ``` 打印结果: ``` qianguyihao then {"name":"qianguyihao"} ``` ## 小结 学习这些内容之后, 相信你已经对 Promise 有了基本了解。下一篇文章,我们来讲一讲 Promise 在实战开发的常见用法。 ## 参考链接 - [Promise 注册微任务和执行过程](https://juejin.cn/post/6844903987183894535) - [深入分析 Promise 实现细节](https://juejin.cn/post/6953452438300917790) - [当面试官问你 Promise 的时候,他究竟想听到什么?](https://zhuanlan.zhihu.com/p/29235579) - [手写一个 Promise/A+,完美通过官方 872 个测试用例](https://www.cnblogs.com/dennisj/p/12660388.html) - [从 Promises/A+ 看异步流控制 - Liu Bowen](https://set.sh/post/200215-how-promise-works) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/07-Promise实例的方法.md ================================================ --- title: 07-Promise实例的方法 --- ## Promise 实例的方法简介 Promise 的 API 分为两种: - Promise 实例的方法(也称为:Promis的实例方法) - Promise 类的方法(也称为:Promise的静态方法) Promise **实例**的方法:我们需要实例化 Promise,也就是先 new 一个 Promise 实例对象,然后通过 Promise 实例去调用 `then`、`catch`、`finally`等方法。这几个方法就是 Promise 的实例方法。 Promise 实例提供了如下方法: - promise.then():异步任务成功的回调函数。 - promise.catch():异步任务失败的回调函数。 - promise.finaly():异步任务无论成功与否,都会执行的回调函数。 我们在上一篇文章《Promise入门详解》中,用到的都是Promise实例的方法。本篇文章,我们来把这三个实例方法详细学习一下。 ## Promise 实例的 then()方法 then()方法是 Promise实例上的一个方法。它其实是放在Promise的原型上的 `Promise.prototype.then`。 ### then()方法的参数 then()方法可以接收一个参数,也可以接收两个参数。两个参数时,分别代表两个回调函数,这两个函数一直处于**监听状态**: - 参数1:当 Promise 的状态变为 fulfilled(意思是:任务执行成功)时会立即执行的回调函数。 - 参数2:当 Promise 的状态为 rejected(任务执行失败)时会立即执行的回调函数。 下面这两种写法是等价的。处理 rejected 失败状态的回调函数,既可以放在 then() 方法的第二个参数里,也可以单独放在 catch() 方法的参数里。 写法1: ```js const promise = new Promise((resolve, reject) => { reject('qianguyihao'); }); promise.then( res => { console.log('res:', res); }, err => { console.log('err:', err); } ); ``` 写法2: ```js const promise = new Promise((resolve, reject) => { reject('qianguyihao'); }); promise .then(res => { console.log('res:', res); }) .catch(err => { console.log('err:', err); }); ``` ### then()方法可以被多次调用 一个 Promise 的 then() 方法可以被多次调用。每次调用时我们都可以传入对应fulfilled状态的回调函数。当 Promise 的状态变为 fulfilled 时,这些回调函数都会被执行。 then被调用多次的伪代码: ```js const myPromise = new Promise(); myPromise.then(); myPromise.then(); myPromise.then(); ``` 代码举例: ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao'); }); myPromise.then(res => { console.log('成功回调1'); console.log('res1:', res); }); myPromise.then(res => { console.log('成功回调2'); console.log('res2:', res); }); myPromise.then(res => { console.log('成功回调3'); console.log('res3', res); }); ``` 打印结果: ``` 成功回调1 res1: qianguyihao 成功回调2 res2: qianguyihao 成功回调3 res3 qianguyihao ``` 代码解释: 当 myPromise 状态为 fulfilled 时,下面的四个 then() 方法**都在监听**,所以这四个 then() 方法都会收到状态确定的通知,进而都会执行。 此外,then() 被调用多次还有一种**链式调用**的写法,它的打印结果与上面的打印结果不同,想要了解 Promise 的链式调用,需要先学习 then() 方法的返回值,我们继续往下看。 ## then() 方法的返回值(重要) > 这一段的知识点略有难度,但是非常重要,是我们学习 Promise 链式调用的理论基础。 then()方法本身是有返回值的,它会返回一个**新的Promise对象**。因为 then()方法的返回值永远是一个 Promise 对象,所以我们才可以对它进行**链式调用**。 Promise 链式调用的伪代码: ```js // 伪代码 myPromise.then().then().catch() ``` 上方代码中,因为 myPromise.then() 的返回值本身就是一个 Promise,所以才可以继续调用 then()、继续调用 catch()。 那么,**then()方法返回的 Promise 对象处于什么状态呢**?then()方法的参数里,是一个回调函数。这取决于回调函数的返回值是什么。情况如下: 1、当then()方法中的回调函数在执行时,那么Promise 处于pending状态。 2、当 then()方法中的回调函数中,手动 return 一个返回值时,那么 Promise 的状态取决于返回值的类型。当返回值这行代码执行完毕后, Promise 会立即决议,进入确定状态(成功 or 失败)。具体情况如下: - 情况1:如果没有返回值(相当于 return undefined),或者返回值是**普通值/普通对象**,那么 Promise 的状态为fulfilled。这个值会作为fulfilled 状态的回调函数的参数值。 - 情况2:如果返回值是**另外一个新的 Promise**,那么原 Promise 的状态将**交给新的 Promise 决定**,这两个Promise 的状态一致。 - 情况3:如果返回值是一个对象,并且这个对象里有实现then()方法(这种对象称为 **thenable** 对象),那就会执行该then()方法,并且根据**then()方法的结果来决定Promise的状态**。 还有一种特殊情况: - 情况4:当then()方法传入的回调函数遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态,并将抛出的错误作为 rejected 状态的回调函数的参数值。 **小结**:then()方法里,我们可以通过 return **传递结果和状态**给下一个新的Promise。 ### 默认返回值 如果then()方法的回调函数里没写返回值(相当于 return undefined),那么then()方法的返回值是一个新的Promise。新 Promise 的状态为fulfilled,其then()方法里,res的值为 undefined。 then() 链式调用的代码举例: ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao'); }); myPromise .then(res => { console.log('成功回调1'); console.log('res1:', res); /* 这里虽然什么都没写,底层默认写了如下代码: return new Promise((resolve, reject) => { resolve(); // resolve() 的参数是空,相当于 resolve(undefined) }) */ }) .then(res => { console.log('成功回调2'); console.log('res2:', res); }) .then(res => { console.log('成功回调3'); console.log('res3', res); }); ``` 打印结果: ``` 成功回调1 res1: qianguyihao 成功回调2 res2: undefined 成功回调3 res3:undefined ``` 代码解释: 第一个 then()里的回调,是由 myPromise 进行决议。第二个then()、第三个then() 也在**等待决议**。 但是,**第二个 then() 的回调是由第一个 then()传入的回调函数,返回的 Promise 进行决议**;第三个 then() 的回调是由第二个 then()传入的回调函数,返回的 Promise 进行决议,以此类推。所以,这两个then()里面的打印参数的结果是 undefined,并没有打印 myPromise 的决议结果。 换句话说,第一个 then() 在等待 myPromise 的决议结果,有决议结果后执行;第二个 then() 在等待第一个 then()参数里返回的新 Promise的决议结果,有决议结果后执行;第三个 then() 在等待第二个 then()参数里返回的新 Promise的决议结果,有决议结果后执行。 ### 返回普通值:通过 return 传递数据结果 我们也可以在 then()方法的回调函数里,手动 return 自己想要的数据,比如一个普通值 value1。这个普通值就可以传递给下一个新的Promise。新 Promise 的状态为fulfilled,其then()方法里,res的值为 value1。 代码举例: ```js const myPromise = new Promise((resolve, reject) => { resolve('1号'); }); myPromise .then(res => { console.log('res1:', res); // return一个普通值,把这个值传递给下一个Promise return '2号'; /* 上面这行 return,相当于: return new Promise((resolve, reject)=> { resolve('2号'); }) */ }) .then(res => { // res可以接收到上一个 Promise 传递的值 console.log('res2:', res); }) .then(res => { console.log('res3:', res); }); ``` 返回结果: ``` res1: 1号 res2: 2号 res3: undefined ``` ### 返回新的 Promise 情况1、在 then() 方法的回调函数中 return 一个成功的新 Promise,那么,then()返回的Promise 也是成功状态。相当于把新Promise的成功结果传递出去。代码举例: ```js const promise1 = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 1'); }); const promise2 = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 2'); }); promise1 .then(res => { console.log('res1:', res); return promise2; }) .then(res => { // 监听 promise2 的成功状态 console.log('res2:', res); }) .then(res => { console.log('res3', res); }); ``` 打印结果: ``` res1: qianguyihao fulfilled 1 res2: qianguyihao fulfilled 2 res3 undefined ``` 情况2、在 then() 方法的回调函数中 return 一个失败的新 Promise,那么,then()返回的Promise 也是失败状态。再继续往下走,会怎么样?相当于把新Promise 的失败原因传递出去。代码举例: ```js const promise1 = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 1'); }); const promise2 = new Promise((resolve, reject) => { reject('qianguyihao rejected 2'); }); promise1 .then(res => { console.log('res1:', res); // return 一个 失败的 Promise return promise2; }) .then(res => { console.log('res2:', res); }, err => { // 如果 promise2 为失败状态,可以通过 then() 的第二个参数(即失败的回调函数)捕获异常,然后就可以继续往下执行其他的代码 console.log('err2:', err); // 这里相当于 return undefined }) .then(res => { console.log('res3', res); }, err => { console.log('err3:', err); }); ``` 打印结果: ``` res1: qianguyihao fulfilled 1 err2: qianguyihao rejected 2 res3: undefined ``` 上方代码可以看到,第二个Promise走的是失败回调,这很容易理解。重点是,最后一个 Promise 走的是成功回调,这很出人意料。我们稍后学习 catch()方法的返回值后,就能看懂。**这例子很经典,一定要记住**。 情况3:在 then() 方法的回调函数中 return 一个 pending 状态的新 Promise,那么 then() 返回的Promise状态也是 pending。 ### 返回 thenable 对象 代码举例: ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 1'); }); myPromise .then(res => { console.log('res1:', res); return { then: (resolve, reject) => { resolve('thenable fulfilled'); }, }; }) .then(res => { console.log('res2:', res); }) .then(res => { console.log('res3', res); }); ``` 打印结果: ``` res1: qianguyihao fulfilled 1 res2: thenable fulfilled res3 undefined ``` ### then() 中抛出异常 当then()方法传入的回调函数遇到异常或者手动抛出异常时,那么,then()所返回的**新的 Promise 会进入rejected 状态**,进而触发新Promise 的 catch() 方法的执行,做异常捕获。 这方面的内容,我们在后续的文章《异常处理方案》中会详细讲解。 ### 特殊情况:then() 中传入非函数时,会发生值穿透 在Promise的`then()`方法中,如果传入一个非函数作为参数,JS 会将其忽略,并且将前一个 Promise 的结果值传递给下一个`then()`方法。这意味着如果你在`then()`中传入非函数参数,它将被视为一个空操作,而不会对Promise链产生任何影响。 “值穿透”的意思是,传入的非函数值会被忽略。 代码举例: ```js const myPromise = new Promise((resolve, reject) => { resolve('Hello'); }); myPromise .then('Invalid Argument') .then(res1 => { console.log('res1:', res1); return 'World'; }) .then(res2 => { console.log('res2:', res2); }); ``` 打印结果: ``` res1: Hello res2: World ``` ## Promise 实例的 catch() 方法 catch()方法是 Promise实例上的一个方法。它其实是放在Promise的原型上的 `Promise.prototype.catch`。 ### catch() 方法的参数 catch()方法可以接收一个参数。这个参数是一直处于**监听状态**的回调函数。当 Promise 的状态为 rejected(任务执行失败)时会立即执行这个回调函数。 代码举例: ```js const promise = new Promise((resolve, reject) => { reject('qianguyihao reject'); }); promise .then(res => { console.log('res:', res); }) .catch(err => { console.log('err:', err); }); ``` 打印结果: ``` err: qianguyihao reject ``` ### catch() 方法可以被多次调用 一个 Promise 的 catch() 方法可以被多次调用。每次调用时我们都可以传入对应 rejected 状态的回调函数。当 Promise 的状态变为 rejected 时,这些回调函数都会被执行。 catch() 被调用多次的伪代码: ```js const myPromise = new Promise(); myPromise.catch(); myPromise.catch(); myPromise.catch(); ``` 代码举例: ```js const myPromise = new Promise((resolve, reject) => { reject('qianguyihao rejected'); }); myPromise.catch(err => { console.log('失败回调1'); console.log('err1:', err); }); myPromise.catch(err => { console.log('失败回调2'); console.log('err2:', err); }); myPromise.catch(err => { console.log('失败回调3'); console.log('err3:', err); }); ``` 打印结果: ``` 失败回调1 err1: qianguyihao rejected 失败回调2 err2: qianguyihao rejected 失败回调3 err3: qianguyihao rejected ``` 代码解释: 当 myPromise 状态为 rejected 时,下面的四个 catch() 方法**都在监听**,所以这四个 catch() 方法都会收到状态确定的通知,进而都会执行。 ## catch() 方法的返回值(重要) 与 then() 方法类似,catch()方法默认也是有返回值的,它会返回一个**新的Promise对象**。因为 catch()方法的返回值永远是一个 Promise 对象,所以我们才可以对它进行**链式调用**。 Promise 链式调用的伪代码: ```js // 伪代码 myPromise.then().then().catch().then() ``` 上方代码中,因为 myPromise.catch() 的返回值本身就是一个 Promise,所以才可以继续调用 then()、继续调用 catch()。 与 then() 方法类似,**catch()方法返回的 Promise 对象处于什么状态呢**?catch()方法的参数里,是一个回调函数。这取决于回调函数的返回值是什么。情况如下: 1、当catch()方法中的回调函数在执行时,那么Promise 处于 pending 状态。 2、当 catch方法中的回调函数中,手动 return 一个返回值时,那么 Promise 的状态取决于返回值的类型。当返回值这行代码执行完毕后, Promise 会立即决议,进入确定状态(成功 or 失败),进而触发下一个then/catch 函数的执行。同时可以给下一个 then/catch 传递参数。具体情况如下: - 情况1:如果没有返回值(相当于 return undefined),或者返回值是**普通值/普通对象**,那么 Promise 的状态为fulfilled。这个值会作为then()回调的参数。 - 情况2:如果返回值是**另外一个新的 Promise**,那么原 Promise 的状态将**交给新的 Promise 决定**。这两个Promise 的状态一致。 - 情况3:如果返回值是一个对象,并且这个对象里有实现then()方法(这种对象称为 **thenable** 对象),那就会执行该then()方法,并且根据**then()方法的结果来决定Promise的状态**。 还有一种特殊情况: - 情况4:当catch()方法传入的回调函数遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态。 **小结**:catch()方法里,我们可以通过 return **传递结果**给下一个新的Promise。 ### 默认返回值 如果catch()方法的回调函数里没写返回值(相当于 return undefined),那么catch()方法的返回值是一个新的Promise。新 Promise 的状态为fulfilled,其then()方法里,res的值为 undefined。 代码举例: ```js const myPromise = new Promise((resolve, reject) => { reject('qianguyihao rejected'); }); myPromise .catch(err => { console.log('err:', err); /* 这里虽然什么都没写,底层默认写了如下代码: return new Promise((resolve, reject) => { resolve(undefined); // resolve() 的参数是空 }) */ }) .then(res => { console.log('res:', res); }); ``` 打印结果: ``` err: qianguyihao rejected res: undefined ``` ### 返回普通值 我们也可以在 catch()方法的回调函数里,手动 return 自己想要的数据,比如一个普通值 value1。这个普通值就可以传递给下一个新的Promise。新 Promise 的状态为fulfilled,其then()方法里,res的值为 value1。 代码举例: ```js const myPromise = new Promise((resolve, reject) => { reject('1号'); }); myPromise .catch(err => { console.log('err1:', err); return '2号'; /* 上面这行 return,相当于: return new Promise((resolve, reject)=> { resolve('2号'); }) */ }) .then(res => { console.log('res2:', res); }) .then(res => { console.log('res3:', res); }); ``` 返回结果: ``` err1: 1号 res2: 2号 res3: undefined ``` ## catch() 方法的执行时机 ### Promise 抛出 rejected 异常时,一定要捕获并处理 当 Promise 状态为 rejected 时,表示抛出异常,如果不处理失败的回调,行不行呢?不行,会报错。代码举例: ```js const promise = new Promise((resolve, reject) => { // 在这里抛出异常 reject('qianguyihao reject'); }); promise.then(res => { console.log('res:', res); }); ``` ![image-20230521135912267](https://img.smyhvae.com/image-20230521135912267.png) 这个报错的意思是:未捕获 rejected 失败状态的 Promise 异常。必须要加一个 catch() 进行捕获。 书写 Promise 时,比较好的习惯是,无论如何都要在末尾写一个 catch() 方法。 ### 可在 then() 中通过 throw 抛出异常 先来看一段代码: ```js const myPromise = new Promise((resolve, reject) => { resolve('aaa'); }); myPromise .then(res => { console.log('res1:', res); // 如果我想在这里 return 一个失败状态的promise,该怎么做? }) .then(res => { console.log('res2:', res); }) .catch(err => { console.log('err:', err); }); ``` 注意看注释,如果在那个位置return 一个失败状态的Promise,该怎么做? 做法1: ```js return new Promise((resolve, reject)=> { reject('第二个 promise 执行失败'); }) ``` 做法2: ```js throw new Error('第二个 Promise 执行失败'); ``` 做法2比做法1更为常用,完整代码如下: ```js const myPromise = new Promise((resolve, reject) => { resolve('aaa'); }); myPromise .then(res => { console.log('res1:', res); // 抛出异常:相当于 return 一个失败状态的 Promise throw new Error('第二个 Promise 执行失败'); }) .then(res => { console.log('res2:', res); }) .catch(err => { console.log('err:', err); }); ``` 打印结果: ``` res1: aaa err: Error: 第二个 Promise 执行失败 ``` 当通过 throw 抛出异常后,当前 then() 里的后续代码会暂停执行,后续的 then() 也会暂停执行,直接往后走到最近的 catch()。 throw 这种写法在实战开发中很常用,需要理解并记住。 ### 找到最近的 catch() 去执行 我们先来看一段代码: ```js const myPromise = new Promise((resolve, reject) => { reject('qianguyihao rejected'); }); myPromise .then(res => { console.log('res1:', res); }) .then(res => { console.log('res2:', res); }) .catch(err => { console.log('err:', err); }); ``` 打印结果: ``` err: qianguyihao rejected ``` 上方代码中的 catch() 是属于哪个 Promise 实例的方法呢?其实没有严格的界限。它既可以捕获 myPromise的异常,也可以捕获那两个 then()的异常,就是这么灵活。 再来看一段代码: ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled'); }); myPromise .then(res => { console.log('res1:', res); // 遇到异常(或者任务失败)后,会找到最近的 catch() 去执行 throw new Error('not login') }) .then(res => { console.log('res2:', res); }, err => { console.log('err2:', err); }) .catch(err => { console.log('err3:', err); }); ``` 打印结果: ``` res1: qianguyihao fulfilled err2: Error: not login ``` 请记住,myPromise 的状态变为失败时,它会找到**最近的**那个**失败回调函数**并执行。这是 Promise的内部机制。 ## 处理失败状态的两种写法 我们有两种写法可以捕获 Promise的失败/异常状态: - 写法 1:单独写 catch() 方法作为失败的回调函数。 - 写法 2:then()方法里可以传两个参数,第⼀个参数是成功时的回调函数,第⼆个参数是失败时的回调函数。 ### 代码格式 这两种写法的**代码格式**如下: ```js // 第一步:model层的接口封装 const myPromise = new Promise((resolve, reject) => { // 这里做异步任务(比如 ajax 请求接口,或者定时器),然后执行 resolve 或者 reject。 ... ... }); const onFulfilled = (res) => { console.log(res); }; const onRejected = function (err) { console.log(err); }; // 写法1:通过 catch 方法捕获失败状态的Promise myPromise.then(onFulfilled).catch(onRejected); // 写法2:then()方法里可以传两个参数,第⼀个参数是成功时的回调函数,第⼆个参数是失败时的回调函数。 myPromise.then(onFulfilled, onRejected); ``` 注意事项: 1、上面这两种写法是等价的,选其中一种写法即可。这两种写法几乎没有区别。 2、有一点点区别: - `myPromise.then(onFulfilled).catch(onRejected)`:既可以捕获到 myPromise 的异常,**也可以捕获到 then() 里面的异常**(划重点)。 - `myPromise.then(onFulfilled, onRejected)`:只能捕获到 promise的异常,无法捕获then()里面的异常。 知识拓展:`myPromise.catch().then()`这种写法,只能捕获到 myPromise 里面的异常。 ### 代码举例 这两种写法在实战开发中的**代码举例**如下: ```js function myPromise() { return new Promise((resolve, reject) => { // 这里做异步任务(比如 ajax 请求接口,或者定时器) ... ... }); } // 写法1 myPromise() .then((res) => { // 从 resolve 获取正常结果 console.log('接口请求成功时,走这里'); console.log(res); }) .catch((err) => { // 从 reject 获取异常结果 console.log('接口请求失败时,走这里'); console.log(err); }) .finally(() => { console.log('无论接口请求成功与否,都会走这里'); }); // 写法 2:(和写法 1 等价) myPromise() .then( (res) => { // 从 resolve 获取正常结果 console.log('接口请求成功时,走这里'); console.log(res); }, (err) => { // 从 reject 获取异常结果 console.log('接口请求失败时,走这里'); console.log(err); } ) .finally(() => { console.log('无论接口请求成功与否,都会走这里'); }); ``` **代码解释**:写法 1 和写法 2 的作用是等价的。只不过,写法 2 是把 catch 里面的代码作为 then 里面的第二个参数而已。 ## Promise 实例的 finally() 方法 finally() 方法是在ES9(ES 2018)中新增的一个特性,表示 Promise 对象无论变成 fulfilled 状态 还是 rejected 状态,finally() 里传入的回调函数都会被执行。 finally() 里可传入一个参数,这个参数是一个回调函数。回调函数不传参数,因为前面无论是 fulfilled 状态,还是 rejected状态,这个回调函数都会执行。 finally() 方法很实用,可以避免我们写很多重复代码,它的执行时机也有很重要的应用场景。 代码举例: ```js const promise1 = new Promise((resolve, reject) => { resolve('promise1 fulfilled'); }); const promise2 = new Promise((resolve, reject) => { reject('promise2 rejected'); }); promise1 .then(res => { console.log('res1:', res); }) .catch(err => { console.log('err1:', err); }) .finally(() => { console.log('promise1 决议后都会执行的代码'); }); promise2 .then(res => { console.log('res2:', res); }) .catch(err => { console.log('err2:', err); }) .finally(() => { console.log('promise2 决议后都会执行的代码'); }); ``` 打印结果: ``` res1: promise1 fulfilled err2: promise2 rejected promise1 决议后都会执行的代码 promise2 决议后都会执行的代码 ``` ## Promise的其他写法 ### 写法1 ## Promise 规范 Promise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规范的对象或函数都可以成为 Promise。 关于 promise 规范的详细解读,可以看下面这个链接: - Promises/A+ 规范: - 【翻译】Promises/A+规范:https://www.ituring.com.cn/article/66566 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/08-Promise的链式调用.md ================================================ --- title: 08-Promise的链式调用 --- ## 前言 实际开发中,我们经常需要先后请求多个接口:发送第一次网络请求后,等待请求结果;有结果后,然后发送第二次网络请求,等待请求结果;有结果后,然后发送第三次网络请求。以此类推。 比如说:在请求完接口 1 的数据`data1`之后,需要根据`data1`的数据,继续请求接口 2,获取`data2`;然后根据`data2`的数据,继续请求接口 3。换而言之,现在有三个网络请求,请求 2 必须依赖请求 1 的结果,请求 3 必须依赖请求 2 的结果。 如果按照往常的写法,会有三层回调,陷入“回调地狱”的麻烦。 这种场景其实就是接口的多层嵌套调用,在前端的异步编程开发中,经常遇到。有了 Promise 以及更高级的写法之后,我们可以把多层嵌套调用按照**线性**的方式进行书写,非常优雅。也就是说:Promise 等ES6的写法可以把原本的**多层嵌套写法**改进为**链式写法**。 我们来对比一下嵌套写法和链式调用的写法,你会发现后者的非常优雅。 ## Promise 链式调用:封装多次网络请求 ### ES5 中的传统嵌套写法 伪代码举例: ```js // 封装 ajax 请求:传入请求地址、请求参数,以及回调函数 success 和 fail。 function requestAjax(url, params, success, fail) { var xhr = new xhrRequest(); // 设置请求方法、请求地址。请求地址的格式一般是:'https://api.example.com/data?' + 'key1=value1&key2=value2' xhr.open('GET', url); // 设置请求头(如果需要) xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { success && success(xhr.responseText); } else { fail && fail(new Error('接口请求失败')); } }; } // ES5的传统写法,执行 ajax 请求,层层嵌套 requestAjax( 'https://api.qianguyihao.com/url_1', params_1, res1 => { console.log('第一个接口请求成功:' + JSON.stringify(res1)); // ajax嵌套调用 requestAjax('https://api.qianguyihao.com/url_2', params_2, res2 => { console.log('第二个接口请求成功:' + JSON.stringify(res2)); // ajax嵌套调用 requestAjax('https://api.qianguyihao.com/url_3', params_3, res3 => { console.log('第三个接口请求成功:' + JSON.stringify(res3)); }); }); }, (err1) => { console.log('qianguyihao 请求失败:' + JSON.stringify(err1)); } ); ``` 上面的代码层层嵌套,可读性很差,而且出现了我们常说的回调地狱问题。 ### Promise 的嵌套写法 改用 ES6 的 Promise 之后,写法上会稍微改进一些。代码举例如下: ```js // 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数,以及回调函数 success 和 fail。 function requestAjax(url, params, success, fail) { var xhr = new xhrRequest(); // 设置请求方法、请求地址。请求地址的格式一般是:'https://api.example.com/data?' + 'key1=value1&key2=value2' xhr.open('GET', url); // 设置请求头(如果需要) xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { success && success(xhr.responseText); } else { fail && fail(new Error('接口请求失败')); } }; } // 【model层】将接口请求封装为 Promise function requestData1(params_1) { return new Promise((resolve, reject) => { requestAjax('https://api.qianguyihao.com/url_1', params_1, res => { // 这里的 res 是接口返回的数据。返回码 retCode 为 0 代表接口请求成功。 if (res.retCode == 0) { // 接口请求成功时调用 resolve('request success' + res); } else { // 接口请求异常时调用 reject({ retCode: -1, msg: 'network error' }); } }); }); } // requestData2、requestData3的写法与 requestData1类似。他们的请求地址、请求参数、接口返回结果不同,所以需要挨个单独封装 Promise。 function requestData2(params_2) { return new Promise((resolve, reject) => { requestAjax('https://api.qianguyihao.com/url_2', params_2, res => { if (res.retCode == 0) { resolve('request success' + res); } else { reject({ retCode: -1, msg: 'network error' }); } }); }); } function requestData3(params_3) { return new Promise((resolve, reject) => { requestAjax('https://api.qianguyihao.com/url_3', params_3, res => { if (res.retCode == 0) { resolve('request success' + res); } else { reject({ retCode: -1, msg: 'network error' }); } }); }); } // 【业务层】Promise 调接口的嵌套写法。温馨提示:这段代码在接下来的学习中,会被改进无数次。 // 发送第一次网络请求 requestData1(params_1).then(res1 => { console.log('第一个接口请求成功:' + JSON.stringify(res1)); // 发送第二次网络请求 requestData1(params_2).then(res2 => { console.log('第二个接口请求成功:' + JSON.stringify(res2)); // 发送第三次网络请求 requestData1(params_3).then(res3 => { console.log('第三个接口请求成功:' + JSON.stringify(res3)); }) }) }) ``` 上方代码非常经典。在真正的实战中,我们往往需要嵌套请求**多个不同的接口**,它们的接口请求地址、要处理的 resolve 和 reject 的时机、业务逻辑往往是不同的,所以需要分开封装不同的 Promise 实例。也就是说,如果要调三个不同的接口,建议单独封装三个不同的 Promise 实例:requestData1、requestData2、requestData3。 这三个 Promise 实例,最终都需要调用底层的公共方法 requestAjax()。每个公司都有这样的底层方法,里面的代码会做一些公共逻辑,比如:封装原生的 ajax请求,用户登录态的校验等等;如果没有这种公共方法,你就自己写一个,为组织做点贡献。 但是,细心的你可能会发现:上面的最后10行代码仍然不够优雅,因为 Promise 在调接口时出现了嵌套的情况,实际开发中如果真这么写的话,是比较挫的,阅读性非常差,我不建议这么写。要怎么改进呢?这就需要用到 Promise 的**链式调用**。 ### Promise 的链式调用写法(重要) 针对多个不同接口的嵌套调用,采用 Promise 的**链式调用**写法如下:(将上方代码的最后10行,改进如下) ```js requestData1(params_1).then(res1 => { console.log('第一个接口请求成功:' + JSON.stringify(res1)); // 【关键代码】继续请求第二个接口。如果有需要,也可以把 res1 的数据传给 requestData2()的参数 return requestData2(res1); }).then(res2 => { console.log('第二个接口请求成功:' + JSON.stringify(res2)); // 【关键代码】继续请求第三个接口。如果有需要,也可以把 res2 的数据传给 requestData3()的参数 return requestData3(res2); }).then(res3 => { console.log('第三个接口请求成功:' + JSON.stringify(res3)); }).catch(err => { console.log(err); }) ``` 上面代码中,then 是可以链式调用的,一旦 return 一个新的 Promise 实例之后,后面的 then() 就可以作为这个新 Promise 在成功后的回调函数。这种**扁平化**的写法,更方便维护,可读性更好;并且可以更好的**管理**请求成功和失败的状态。 这段代码很经典,你一定要多看几遍,多默写几遍,倒背如流也不过分。如果你平时的异步编程代码能写到这个水平,说明你对 Promise 已经入门了,因为绝大多数人都是用的这个写法。 其实还有更高级、更有水平的写法,那就是用生成器、用 async ... await 来写Promise的链式调用,也就是改进上面的十几行代码。你把它掌握了,编程水平才能更上一层楼。我们稍后会讲。 ### Promise 链式调用举例:封装 Node.js 的回调方法 代码结构与上面的类似,这里仅做代码举例,不再赘述。 传统写法: ```js fs.readFile(A, 'utf-8', function (err, data) { fs.readFile(B, 'utf-8', function (err, data) { fs.readFile(C, 'utf-8', function (err, data) { console.log('qianguyihao:' + data); }); }); }); ``` 上方代码多层嵌套,存在回调地狱的问题。 Promise 写法: ```js function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, 'utf8', (err, data) => { if (err) reject(err); resolve(data); }); }); } read(A) .then((data) => { return read(B); }) .then((data) => { return read(C); }) .then((data) => { console.log('qianguyihao:' + data); }) .catch((err) => { console.log(err); }); ``` ## 用 async ... await 封装链式调用 前面讲的 Promise 链式调用是用 `then().then().then()` 这种写法。其实我们还可以用更高级的写法,也就是用生成器、用 async ... await 改写那段代码。改进之后,代码写起来非常简洁。 在学习这段内容之前,你需要先去《JavaScript进阶/迭代器和生成器》那篇文章里去学习迭代器、生成器相关的知识。生成器是一种特殊的迭代器,async ... await 是生成器的语法糖。 ### 用生成器封装链式调用 代码举例: ```js // 封装 Promise 链式请求 function* getData(params_1) { // 【关键代码】 const res1 = yield requestData1(params_1); const res2 = yield requestData2(res1); const res3 = yield requestData3(res2); } // 调用 Promise 链式请求 const generator = getData(params_1); generator.next().value.then(res1 => { generator.next(res1).value.then(res2 => { generator.next(res2).value.then(res3 => { generator.next(res3); }) }) }) ``` 生成器在执行时,是分阶段执行的,每次遇到 next()方法后就会执行一个阶段,遇到 yield 就会结束当前阶段的执行并暂停。 上方代码中,yield 后面的内容是当前阶段产生的 Promise 对象;yield 前面的内容是要传递给下一个阶段的参数。 ### 用 async ... await 封装链式调用(重要) 上面的生成器代码有些晦涩难懂,实际开发中,通常不会这么写。我们更喜欢用 async ... await 语法封装 Promise 的链式调用。async ... await 是属于生成器的语法糖,写起来更简洁直观、更容易理解。 代码举例: ```js // 封装:用 async ... await 调用 Promise 链式请求 async function getData() { const res1 = await requestData1(params_1); const res2 = await requestData2(res1); const res3 = await requestData3(res2); } getData(); ``` 代码解释:requestData1()、requestData2()、requestData3() 这三个函数都是一个Promise对象,其内部封装的代码写法已经在前面「Promise 的嵌套写法」这一小段中讲过了。 上面的代码非常简洁。实际开发中也经常用到,非常实用。暂时我们先记住用法,下一章我们会学习 async ... await 的详细知识。 ## 链式调用,如何处理任务失败的情况 在链式调用多个异步任务的Promise时,如果中间有一个任务失败或者异常,要怎么处理呢?是继续往下执行?还是停止执行,直接抛出异常?这取决于你的业务逻辑是怎样的。 常见的处理方案有以下几种,你可以根据具体情况**按需**选择。 ### 统一处理失败的情况,不继续往下走 针对 a、b、c 这三个请求,不管哪个请求失败,我都希望做统一处理。这种代码要怎么写呢?我们可以在最后面写一个 catch。 由于是统一处理多个请求的异常,所以**只要有一个请求失败了,就会马上走到 catch**,剩下的请求就不会继续执行。比如说: - a 请求失败:然后会走到 catch,不执行 b 和 c - a 请求成功,b 请求失败:然后会走到 catch,不执行 c。 代码举例如下: ```js getPromise('a.json') .then((res) => { console.log(res); return getPromise('b.json'); // 继续请求 b }) .then((res) => { // b 请求成功 console.log(res); return getPromise('c.json'); // 继续请求 c }) .then((res) => { // c 请求成功 console.log('c:success'); }) .catch((err) => { // 统一处理请求失败 console.log(err); }); ``` ### 中间的任务失败后,如何继续往下走? 在多个Promise的链式调用中,**如果中间的某个Promise 执行失败,还想让剩下的其他 Promise 顺利执行**的话,那就请在中间**那个失败的Promise里加一个失败的回调函数**(可以写到then函数的第二个参数里,也可以写到catch函数里)。捕获异常后,便可继续往下执行其他的Promise。 代码举例: ```js const promise1 = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 1'); }); const promise2 = new Promise((resolve, reject) => { reject('qianguyihao rejected 2'); }); const promise3 = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 3'); }); promise1 .then(res => { console.log('res1:', res); // return 一个 失败的 Promise return promise2; }) .then(res => { console.log('res2:', res); return promise3; }, err => { // 如果 promise2 为失败状态,可以通过 then() 的第二个参数(即失败的回调函数)捕获异常,然后就可以继续往下执行其他 Promise console.log('err2:', err); // 关键代码:即便 promise2 失败了,也要继续执行 Promise3 return promise3; }) .then(res => { console.log('res3', res); }, err => { console.log('err3:', err); }); ``` 打印结果: ``` res1: qianguyihao fulfilled 1 err2: qianguyihao rejected 2 res3 qianguyihao fulfilled 3 ``` 上方代码中,我们单独处理了 promise2 失败的情况。不管promise2 成功还是失败,我们都想让后续的 promise3 正常执行。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/09-Promise类的方法.md ================================================ --- title: 09-Promise类的方法 --- ## Promise 类的方法简介 Promise 的 API 分为两种: - Promise 实例的方法(也称为:Promis的实例方法) - Promise 类的方法(也称为:Promise的静态方法) 前面几篇文章,讲的都是 Promise **实例**的方法(需要先将Promise实例化),它们都是存放在Promise的prototype上的。今天这篇文章,我们来讲一下 Promise **类**的方法。 Promise **类**的方法:可以直接通过大写的`Promise.xxx`调用的方法。这里的`xxx`就称之为静态方法。 Promise 的自带 API 提供了如下静态方法: | Promise 的静态方法 | 含义 | 版本 | | -------------------- | ------------------------------------------------------------ | ------- | | Promise.resolve() | 返回一个成功状态的 Promise 对象 | ES 2015 | | Promise.reject() | 返回一个失败状态的 Promise 对象 | ES 2015 | | Promsie.all() | 所有 Promise 都执行成功才算成功;或者任意一个 Promise 执行失败,就算失败 | ES 2015 | | Proimse.allSettled() | 不论成功与失败,把所有Promise的执行结果全部返回 | ES 2020 | | Promise.race() | Promise集合中,返回第一个执行完成(无论成功与失败)的 Promise | ES 2015 | | Promise.any() | Promise集合中,返回第一个执行成功的Promise | ES 2021 | ## Promise.resolve() 和 Promise.reject() ### 使用场景 当我们在定义一个 Promise 的过程中,如果涉及到异步操作,那就需要通过`new Promise`的方式创建一个 Promise 实例。 但有些场景下,我们已经有一个**现成的内容**了,希望**将其转成 Promise 来使用**。此时,我们可以用 `Promise.resolve()` 将其封装为成功的状态。同理,用`Promise.reject()`可以封装为失败的状态。 比如说,有时候,promise 里面并没有异步操作,我只是**单纯地想通过 promise 的方式返回一个字符串**(有的业务就是有这样的需求),那就可以通过 `Promise.reslove('字符串')`、 `Promise.reject('字符串')` 这种**简写**的方式返回。 代码举例: ```js const promise = Promise.resolve('qianguyihao') promise.then(res => { console.log('res:', res); }); // 上方代码如果是连续书写的话,也可以简写成: Promise.resolve('qianguyihao').then(res => console.log('res:', res)); ``` `Promise.resolve('qianguyihao')` 这种写法似乎过于啰嗦,直接 `return 'qianguyihao'`不行吗?that depands。举个例子,我们在调用别人的方法时,对方如果要求返回值必须是 Promise对象,那么,Promise.resolve() 就能派上用场了。 `Promise.resolve()`和`Promise.reject()`的返回值就是一个 Promise。 ### 用法拆解 `Promise.resolve()`的用法相当于new Promise(),并执行resolve()操作。下面这两种写法是等价的: ```js // 写法1:Promise 类的 resolve() 方法 const promise = Promise.resolve(params); // 写法2:Promise 实例的 resolve() 方法 const promise = new Promise((resolve, reject)=> resolve(params)); ``` Promise.reject()的用法同理。下面这两种写法是等价的: ```js // 写法1:Promise 类的 reject() 方法 const promise = Promise.reject(params); // 写法2:Promise 实例的 reject() 方法 // 第一个形参用不到,我们通常用 下划线 表示。这是一种约定俗成的规范写法。 const promise = new Promise((_, reject)=> reject(params)); ``` 写法2显然过于啰嗦,写法1用得更多。 写法2中,我们可以学到一个写代码的小技巧:如果某个形参我们用不到,但又必须写出来的话,我们通常用**下划线**表示。这是一种约定俗成的规范写法,比较简洁。 ### resolve()和reject()的参数 resolve()参数中传入的值,可以有很多种类型,进而决定 Promise 的状态: - 情况1:如果resolve()中传入**普通的值或者普通对象**,那么这个值会作为then()回调的参数。Promise 的状态为fulfilled。 - 情况2:如果resolve()中传入的是**另外一个新的 Promise**,那么原 Promise 的状态将**交给新的 Promise 决定**。 - 情况3:如果resolve()中传入的是**thenable** 对象,那就**会执行该then()方法**,并且根据**then()方法的结果来决定Promise的状态**。 reject()的参数中,无论传入什么值,Promise都会直接进入 rejected 状态,并触发 catch() 方法的执行。 我们在前面的文章《Promise入门详解》中针对这些情况做了详细介绍,在此不再赘述。 ### 代码详解 resolve()、reject()既可以作为 Promise 实例的方法,也可以作为 Promise 类的方法。这两种情况,我们来对比看看。 例 1: ```js function foo(flag) { if (flag) { return new Promise((resolve) => { // 这里可以做异步操作 resolve('success'); }); // return Promise.resolve('success2'); } else { return new Promise((reslove, reject) => { // 这里可以做异步操作 reject('fail'); }); } } // 执行 reslove 的逻辑 foo(true).then((res) => { console.log(res); }); // 执行 reject 的逻辑 foo(false).catch((err) => { console.log(err); }); ``` 例 2:(见证奇迹的时刻) ```js function foo(flag) { if (flag) { // Promise的静态方法:直接返回字符串 return Promise.resolve('success'); } else { // Promise的静态方法:直接返回字符串 return Promise.reject('fail'); } } // 执行 reslove 的逻辑 foo(true).then((res) => { console.log(res); }); // 执行 reject 的逻辑 foo(false).catch((err) => { console.log(err); }); ``` 例 1 和例 2 的打印结果是一样的。这两段代码的区别在于:例 1 里面可以封装异步任务;例 2 只能单纯的返回一个字符串等变量,不能封装异步任务。 ## Promise.all() Promise.all()的参数是一个数组,数组里可以填写多个 Promise;Promise.all()的返回值是一个新的 Promise。这里我们以三个 Promise 为例,比如 `Promsie.all([p1, p2, p3])`。它的作用是将p1、p2、p3 这三个 Promise 包裹在一起,**组成一个新的 Promise**。 **新 Promise 的状态**由 p1、p2、p3 这三个 Promse **共同决定**: - 当 p1、p2、p3等所有的 Promise 状态都变为 fulfilled 时,新的 Promise 将变为 fulfilled 状态,并会将 p1、p2、p3 等所有 Promise 的返回值**组成一个数组**,作为 then() 的参数。 - 当p1、p2、p3 等 Promise中有一个 Promise 状态为 rejected 时,新的 Promise 将立马变为 rejected 状态,并会将第一个 reject() 的返回值作为 catch() 的参数。 `Promsie.all([p, p2, p3])` 的**使用场景**:并发处理多个异步任务,所有任务都执行成功,才算成功(才会走到 then);只要有一个任务失败,就会马上走到 catch,整体都算失败。参数里传的是多个 Promise 实例组成的数组。 Promsie.all() 在实际开发中使用得非常频繁,真的很好用。我们在开发一个前端页面时,经常需要同时调用多个接口,等待这些接口的数据都准备好之后,前端再来做接下来的事。如果你也遇到这样的需求,那么 Promsie.all() 适合你。 ### 语法举例 **1、异步任务都执行成功时**: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); resolve('promise 1 成功'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); resolve('promise 2 成功'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.all([promise1, promise2, promise3]) .then((res) => { // 三个异步任务都执行成功,才会走到这里 // 这里拿到的 res,是三个成功的返回结果组成的数组 console.log('all promise res:' + JSON.stringify(res)); }) .catch((err) => { // 只要有一个异步任务执行失败,就会马上走到这里 console.log(err); }); ``` 打印结果: ```js // 1秒后 执行 promise1 // 2秒后 执行 promise2 // 3秒后 执行 promise3 all promise res:["promise 1 成功","promise 2 成功","promise 3 成功"] ``` **2、异步任务有至少一个执行失败时**: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); resolve('promise 1 成功'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); // 这里通过 reject() 的方式,表示任务执行失败 reject('promise 2 失败'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.all([promise1, promise2, promise3]) .then((res) => { // 三个异步任务都执行成功,才会走到这里 console.log('走到 then:' + JSON.stringify(res)); }) .catch((err) => { // 只要有一个异步任务执行失败,就会马上走到这里 console.log('走到 catch:' + err); }); ``` 打印结果: ```js // 1秒后 执行 promise1 // 2秒后 执行 promise2 走到 catch:promise 2 失败 // 3秒后 执行 promise3 ``` 可以看到,当 promise2 执行失败之后,马上就走到了 catch,获取到了 promise2 失败的结果。 要注意的是,promise1、promise3并不会执行 resolve(),它俩状态是 pending,且无法获取它俩的结果。我们只知道整体的任务是失败的,获取了整体的失败结果。 ### Promise.all()案例:多张图片上传 案例:现在有一个**图片上传**的接口,每次请求接口时只能上传一张图片。需求是:当用户连续上传完九张图片(正好凑齐九宫格)之后,给用户一个“上传成功”的提示。这个时候,我们就可以使用`Promsie.all()`。 这个例子,在实际的项目开发中,经常遇到,属于高频需求,需要记住并理解。 1、代码举例如下: ```js const imgArr = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg', '6.jpg', '7.jpg', '8.jpg', '9.jpg']; const promiseArr = []; imgArr.forEach((item) => { const p = new Promise((resolve, reject) => { // 在这里做图片上传的异步任务。图片上传成功后,接口会返回图片的 url 地址 // upload img ==> return imgUrl if (imgUrl) { // 单张图片上传完成 resolve(imgUrl); } else { reject('单张图片上传失败'); } }); promiseArr.push(p); }); Promise.all(promiseArr) .then((res) => { console.log('图片全部上传完成'); console.log('九张图片的url地址,组成的数组:' + res); }) .catch((res) => { console.log('部分图片上传失败'); }); ``` 2、上方代码解释: (1)只有九张图片都上传成功,才会走到 then。 第一张图会成功调 upload 接口,并返回 imgUrl,但不会走到 resolve,因为要等其他八张图的执行结果,再决定是一起走 resolove 还是一起走 reject。 (2)按时间顺序来看,假设第一张图片上传成功,第二张图片上传失败,那么,最终的表现是: - 对于前端来说,九张图都会走到 reject;整体会走到 catch,不会走到 then。 - 对于后端来说,第一张图片会上传成功(因为写入 DB 是不可逆的),第二张图上传失败,剩下的七张图,会正常请求 upload img 接口。 **其实九张图的 upload img 请求都已经发出去了**。对于后端来说,是没有区别的(而且读写 DB 的操作不可逆),只是在前端的交互表现不同、走到 resolve / reject / then / catch 的时机不同而已。 3、**思维拓展**: - 拓展 1:如果你希望九张图同时上传,并且想知道哪些图上传成功、哪些图上传失败,则可以这样做:**无论 upload img 接口请求成功与否,全都执行 resolve**。这样的话,最终一定会走到 then,然后再根据接口返回的结果判断九张图片的上传成功与否。 - 拓展 2:实战开发中,在做多张图片上传时,可能是一张一张地单独上传,各自的上传操作相互独立。此时 `Promise.all`便不再适用,这就得具体需求具体分析了。 ### 注意:某个任务失败之后,其他任务会继续执行 一定要注意,当执行 Promise.all() / Promise.race() / Promise.any() 等方法时,如果其中一个任务失败了,**其他任务并没有停止,会继续执行**。只是前端拿不到其他任务的执行状态而已。 其他任务是否需要做一些特殊梳理,就要结合你自己的业务逻辑来考虑。 ## Promse.allSettled() Promise.all()方法组成的多个Promise中,有个明显的特点是:只要有一个 Promise 元素进入 rejected 状态,则整体的 Promise 会立即进入 rejected 状态。其他 Promise 元素会处于 pending 状态,任务本身是否执行成功,我们在前端代码里无从知晓,因为无法拿到处理结果。我们只知道整体的 Promise 是 fulfilled或者 rejected ,获取整体的成功/失败结果。 如果你认为 Promise.all() 的这一点无法满足你的需求,那么, Promise.allSettled() 可以提供一种新思路。 Promise.allSettled() 是ES11(ES 2020)中提供的新API。它会等待所有的 Promise 元素都有结果(无论是 fulfilled,还是rejected)后,才会有最终的结果(settled),而且状态一定是 fulfilled。 Promise.allSettled() 的状态为 fulfilled,不代表 里面的 Promise 元素都是 fulfilled,这只是在表明,里面的 Promise 元素都已经有了就结果(可能成功、可能失败)。 ### 语法举例 ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); resolve('promise 1 成功'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); reject('promise 2 失败'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.allSettled([promise1, promise2, promise3]).then(res => { // 注意看 res 的返回结果 console.log('allSettled:', res); }); ``` 打印结果: ``` 执行 promise1 执行 promise2 执行 promise3 allSettled: [ { "status": "fulfilled", "value": "promise 1 成功" }, { "status": "rejected", "reason": "promise 2 失败" }, { "status": "fulfilled", "value": "promise 3 成功" } ] ``` 打印结果截图: image-20230523193237044 从上面的打印结果可以看出,Promise.allSettled() 的状态为 fulfilled后,then()的回调函数里,res 是一个数组,数组里存放了每个 Promise 元素的执行结果(包括状态和返回值)。 在实际开发中,Promise.all() 比 Promise.allSettled() 用得更多一些。 ## Promise.race() `Promise.race([p1, p2, p3])`:参数里传的是多个 Promise 元素组成的数组。可以并发处理多个Promise,整体的执行状态取**第一个执行完成的 Promise**的状态,且状态和第一个完成的任务状态保持一致。 上面这句话,第一次读时,可能很绕口。我以异步任务为例,说的再通俗一点:在多个同时执行的异步任务中,等待哪个任务 **最先执行完成**(无论是走到 resolve,还是走到 reject,都算执行完成),整体的状态就立即跟这个任务保持一致。如果这个任务执行成功,那整体就算成功(走到 then);如果这个任务执行失败,那整体就算失败(走到 catch)。 `race`的中文翻译,可以理解为“竞赛”、“竞争”。意思是,谁先抢到名额,就认定谁了。**谁前有结果,就用谁的结果**。无论这个人最终的结局是成功或者失败,整体的结局,都以这个人的结局为准。 我刚开始学 Promise.race()的时候,误以为它的含义是“只要有一个异步**执行成功**,整体就算成功(走到 then);所有任务都执行失败,整体才算失败(走到 catch)”。现在想来,真是大错特错,过于懵懂。 现在我顿悟了,准确来说,Promise.race()强调的是:只要有一个异步任务**执行完成**,整体就是**完成**的。 Promise.race()的**应用场景**:在众多 Promise 实例中,最终结果只取一个 Promise 的状态,**谁返回得最快就用谁的 Promise **状态。 我们来看看各种场景的打印结果,继续前行。 ### 语法举例 **场景 1、所有任务都执行成功时**: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); resolve('promise 1 成功'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); resolve('promise 2 成功'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.race([promise1, promise2, promise3]) .then((res) => { // 第一个完成的任务,如果执行成功,就会走到这里 // 这里拿到的 res,是第一个成功的 promise 返回的结果,不是数组 console.log(JSON.stringify(res)); console.log('走到then:' + res); }) .catch((err) => { // 第一个完成的任务,如果执行失败,就会走到这里 console.log(err); }); ``` 打印结果: ```js // 1秒后 执行 promise1 走到then:promise 1 成功 // 2秒后 执行 promise2 // 3秒后 执行 promise3 ``` **场景 2、第一个任务成功、第二个任务失败时**: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); resolve('promise 1 成功'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); // 第二个任务执行失败时 reject('promise 2 失败'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.race([promise1, promise2, promise3]) .then((res) => { // 第一个完成的任务,如果执行成功,就会走到这里 console.log('走到then:' + res); }) .catch((err) => { // 第一个完成的任务,如果执行失败,就会走到这里 console.log('走到catch:' + err); }); ``` 打印结果: ```js // 1秒后 执行 promise1 走到then:promise 1 成功 // 2秒后 执行 promise2 // 3秒后 执行 promise3 ``` 可以看出,场景 2 的打印结果和场景 1 的打印结果,是一样的。因为第一个执行完成的任务是成功的,所以整体就算成功,马上走到 then()。 **场景 3、第一个任务失败、第二个任务成功时**: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); // 第一个任务执行失败时 reject('promise 1 失败'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); resolve('promise 2 成功'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.race([promise1, promise2, promise3]) .then((res) => { // 第一个完成的任务,如果执行成功,就会走到这里 console.log('走到then:' + res); }) .catch((err) => { // 第一个完成的任务,如果执行失败,就会走到这里 console.log('走到catch:' + err); }); ``` 打印结果: ```js // 1秒后 执行 promise1 走到catch:promise 1 失败 // 2秒后 执行 promise2 // 3秒后 执行 promise3 ``` 看清楚了没?场景 3 的最终打印结果,是走到了 catch;任务 2 和任务 3 里的 resolve,并没有执行。 场景 3 的代码,一定要好好理解。 ### Promise.race()举例:图片加载超时 现在有个需求是这样的:前端需要加载并显示一张图片。如果图片在三秒内加载成功,那就显示图片;如果三秒内没有加载成功,那就按异常处理,前端提示“加载超时”或者“请求超时”。 代码实现: ```js // 图片请求的Promise function getImg() { return new Promise((resolve, reject) => { let img = new Image(); img.onload = function () { // 图片的加载,是异步任务 resolve(img); }; img.src = 'https://img.smyhvae.com/20200102.png'; }); } // 加载超时的 Promise function timeout() { return new Promise((resolve, reject) => { // 采用 Promise.race()之后,如果 timeout() 的 promise 比 getImg() 的 promise先执行,说明定时器时间到了,那就算超时。整体的最终结果按失败处理。 setTimeout(() => { reject('图片加载超时'); }, 3000); }); } Promise.race([getImg(), timeout()]) .then((res) => { // 图片加载成功 console.log(res); }) .catch((err) => { // 图片加载超时 console.log(err); }); ``` 如代码注释所述:采用 Promise.race() 之后,如果 timeout() 的 Promise 比 getImg() 的 Promise 先执行,说明定时器时间到了,那就算超时。整体的最终结果按失败处理。 这个思路很巧妙。用同样的思路,我们还可以处理网络请求超时的问题。如果接口请求时长超过 3 秒,就按超时处理,也就是下面我们要举的例子。 ### Promise.race()举例:网络请求超时 现在有这种需求:如果接口请求时长超过 3 秒,就按超时处理。 基于这种需求,我们可以用 Promise.race() 来实现:一个 Promise 用于请求接口,另一个 Promise 用于 setTimeout() 定时器。把这两个 Promise 用 Promise.race() 组装在一起,谁先执行,那么最终的结果就以谁的为准。 代码举例: ```js function query(url, delay = 4000) { let promiseArr = [ myAajax(url), new Promise((resolve, reject) => { setTimeout(() => { reject('网络请求超时'); }, delay); }), ]; return Promise.race(promiseArr); } query('http://localhost:8899/xxx_url', 3000) .then((res) => { console.log(res); }) .catch((error) => { console.log(error); }); ``` ## Promise.any() Promise.any() 是 ES12(ES 2021)中推出的新API。它类似于 Promise.race(),但有一个关键的区别:Promise.any() 会等待参数中第一个状态为 fulfilled 的Promise元素,然后立即进入 fulfilled状态。 如果参数中所有的 Promise 元素都进入了 rejected,那么也会等到所有的Promise都变成rejected 状态,最终报错 AggregateError。 ### 语法举例 **场景1**、第一个任务失败,第二个任务成功: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); reject('promise 1 失败'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); resolve('promise 2 成功'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); resolve('promise 3 成功'); }, 3000); }); Promise.any([promise1, promise2, promise3]).then(res => { console.log('走到then:', res); }); ``` 打印结果: ``` // 1秒后 执行 promise1 // 2秒后 执行 promise2 走到then(): promise 2 成功 // 3秒后 执行 promise3 ``` **场景2**、三个任务都失败: ```js const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise1'); reject('promise 1 失败'); }, 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise2'); reject('promise 3 失败'); }, 2000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行 promise3'); reject('promise 3 失败'); }, 3000); }); Promise.any([promise1, promise2, promise3]) .then(res => { console.log('走到then:', res); }) .catch(err => { console.log('走到catch:', err); }); ``` 打印日志: ``` // 1秒后 执行 promise1 // 2秒后 执行 promise2 // 3秒后 执行 promise3 走到catch: AggregateError: All promises were rejected ``` 注意看打印结果中的报错信息。`执行 promise3`这行日志出来之后,报错的那行马上就出来了。 ### 兼容性问题 `Promise.any()` 方法依然是实验性的,尚未被所有的浏览器完全支持。它当前处于 [TC39 第四阶段草案(Stage 4)](https://github.com/tc39/proposal-promise-any)。 ## 总结 Promise 的静态方法简化处理了多个并发操作的代码,使其更加方便、直观地调用。 Promise 不仅能解决嵌套异步任务的**回调地域**问题,也可管理多个异步任务的**并发请求**。 Promise 本身不是异步的,但是它可以封装异步任务,并对异步操作进行良好的、舒适简洁的状态管理,这便是 Promise 的魅力所在。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/10-async异步函数.md ================================================ --- title: 10-async异步函数 --- ## 异步函数(用 async 声明的函数) ### 异步函数的定义 使用`async`关键字声明的函数,称之为异步函数。在普通函数前面加上 async 关键字,就成了异步函数。语法举例: ```js // 写法1:函数声明的写法 async function foo1() { } // 写法2:表达式写法(ES5语法) const foo2 = async function () { } // 写法3:表达式写法(ES6箭头函数语法) const foo3 = async () => { } // 写法4:定义一个类,在类中添加一个异步函数 class Person { async foo4() { } } ``` JS中的“异步函数”是一个专有名词,特指用`async`关键字声明的函数,其他函数则称之为普通函数。如果你在一个普通函数中定义了一个异步任务,那并不叫异步函数,而是叫包含异步任务的普通函数。 async (异步的)这个单词是 asynchronous 的缩写;相反,sync(同步的)这单词是 synchronous 的缩写。 上面的异步函数代码,执行顺序与普通函数相同,默认情况下会同步执行。如果想要发挥异步执行的作用,则需要配合 await 关键字使用。稍后我们再讲 async/await的语法。 ## 异步函数的返回值 > 异步函数的返回值和普通函数差别比较大,需要特别关注。 普通函数的返回值,默认是 undefined;也可以手动 return 一个返回值,那就以手动 return的值为准。 **异步函数的返回值永远是 Promise 对象**。至于这个 Promise 后续会进入什么状态,那就要看情况了。主要有以下几种情况: - 情况1:如果异步函数内部返回的是普通值(包括 return undefined时)或者普通对象,那么Promise 的状态为fulfilled。这个值会作为then()回调的参数。 - 情况2:如果异步函数内部返回的是**另外一个新的 Promise**,那么 Promise 的状态将**交给新的 Promise 决定**。 - 情况3:如果异步函数内部返回的是一个对象,并且这个对象里有实现then()方法(这种对象称为 **thenable** 对象),那就会执行该then()方法,并且根据**then()方法的结果来决定Promise的状态**。 另外还有一种特殊情况: - 情况4:如果异步函数内部在执行时遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态。 上面这四种情况似曾相识,我们在前面学习“resolve() 传入的参数”、“then()方法的返回值”知识点时,都有类似的情况,知识都是相通的。 ### 默认返回值 代码举例: ```js async function foo2() { // 相当于 return undefined,也相当于 return Promise.resolve(undefined) }; async function foo3() { Promise.resolve('qianguyihao'); // 相当于 return undefined,也相当于 return Promise.resolve(undefined) }; // foo2()、foo3()都是一个Promise对象 foo2().then(res => { console.log(res); // 打印结果:undefined }) foo3().then(res => { console.log(res); // 打印结果:undefined }) ``` 代码解释:异步函数即便没有手动写返回值,也相当于 `return Promise.resolve(undefined)`。 ### 返回普通值 比如下面这段代码: ```js async function foo() { return 'qianguyihao' }; ``` ![image-20230608114346235](https://img.smyhvae.com/image-20230608114346235.png) 可以看到,foo() 的返回值是Promise对象,不是字符串。上面的代码等价于下面这段代码: ```js async function foo() { return Promise.resolve('qianguyihao'); }; ``` 进而,我们可以通过 Promise 对象的then()方法。代码举例如下。 举例1:(异步函数中手动 return 一个值) ```js async function foo() { return 'qianguyihao'; // 上面这行代码相当于:return Promise.resolve('qianguyihao'); }; // foo() 是一个Promise对象 foo().then(res => { console.log(res); // 打印结果:qianguyihao }) ``` ## async/await 的使用 ### 异步函数配合 await 关键字使用 我们可以在`async`声明的异步函数中,使用 `await`关键字来暂停函数的执行,等待一个异步操作完成。温馨提示:await 关键字不能在普通函数中使用,只能在异步函数中使用。 在等待异步操作期间,异步函数会暂停执行,并让出线程,使其他代码可以继续执行。一旦异步操作完成,该异步函数会恢复执行,并返回一个 Promise 对象。具体解释如下: (1)await的后面是一个表达式,这个表达式要求是一个 Promise 对象(通常是一个封装了异步任务的Promise对象)。await执行完成后可以得到异步结果。 (2)await 会等到当前 Promise 的状态变为 fulfilled之后,才继续往下执行异步函数。 - async 的返回值是 Promise 对象。 ### 本质是语法糖 async/await 是在 ES8(即ES 2017)中引入的新语法,是另外一种异步编程解决方案。 async/await 本质是 生成器 Generator 的语法糖,是对Generator的封装。什么是语法糖呢?语法糖就是让语法变得更加简洁、更加舒服,有一种甜甜的感觉。 async/await 的写法使得编写异步代码更加直观和易于管理,避免了使用回调函数或Promise链的复杂性。认识到这一点,非常重要。 ### Promise、Generator、async/await的对比 我们在使用 Promise、async/await、Generator 的时候,返回的都是 Promise 的实例。 如果直接使用 Promise,则需要通过 then 来进行链式调用;如果使用 async/await、Generator,写起来更像同步的代码。 接下来,我们看看 async/await 的代码是怎么写的。 ### async/await 的基本用法 async 后面可以跟一个 Promise 实例对象。代码举例如下: ```js const request1 = function () { const promise = new Promise((resolve, reject) => { requestAjax('https://www.baidu.com/xxx_url', (res) => { if (res.retCode == 200) { // 这里的 res 是接口1的返回结果 resolve('request1 success' + res); } else { reject('接口请求失败'); } }); }); return promise; }; async function requestData() { // 关键代码 const res = await request1(); return res; } requestData().then(res => { console.log(res); }); ``` ### 用 async/await 封装Promise链式调用【重要】 假设现在有三个网络请求,请求2必须依赖请求1的结果,请求3必须依赖请求2的结果,如果按照ES5的写法,会有三层回调,会陷入“回调地狱”。 这种场景其实就是接口的多层嵌套调用。之前学过 Promise,它可以把原本的**多层嵌套调用**改进为**链式调用**。 而本文要学习的 async/await ,可以把原本的“多层嵌套调用”改成类似于同步的写法,非常优雅。 代码举例: ```js // 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数,以及回调函数 success 和 fail。 function requestAjax(url, params, success, fail) { var xhr = new xhrRequest(); // 设置请求方法、请求地址。请求地址的格式一般是:'https://api.example.com/data?' + 'key1=value1&key2=value2' xhr.open('GET', url); // 设置请求头(如果需要) xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { success && success(xhr.responseText); } else { fail && fail(new Error('接口请求失败')); } }; } // 【model层】将接口请求封装为 Promise function requestData1(params_1) { return new Promise((resolve, reject) => { requestAjax('https://api.qianguyihao.com/url_1', params_1, res => { // 这里的 res 是接口返回的数据。返回码 retCode 为 0 代表接口请求成功。 if (res.retCode == 0) { // 接口请求成功时调用 resolve('request success' + res); } else { // 接口请求异常时调用 reject({ retCode: -1, msg: 'network error' }); } }); }); } // requestData2、requestData3的写法与 requestData1类似。他们的请求地址、请求参数、接口返回结果不同,所以需要挨个单独封装 Promise。 function requestData2(params_2) { return new Promise((resolve, reject) => { requestAjax('https://api.qianguyihao.com/url_2', params_2, res => { if (res.retCode == 0) { resolve('request success' + res); } else { reject({ retCode: -1, msg: 'network error' }); } }); }); } function requestData3(params_3) { return new Promise((resolve, reject) => { requestAjax('https://api.qianguyihao.com/url_3', params_3, res => { if (res.retCode == 0) { resolve('request success' + res); } else { reject({ retCode: -1, msg: 'network error' }); } }); }); } // 封装:用 async ... await 调用 Promise 链式请求 async function getData() { // 【关键代码】 const res1 = await requestData1(params_1); const res2 = await requestData2(res1); const res3 = await requestData3(res2); } getData(); ``` 上面这段代码比较长,我们在上一章学习《Promise的链式调用》时,已经详细讲过了。 ### await 后面也可以跟一个异步函数 前面讲到,await后面通常是一个执行异步任务的Promise对象。由于异步函数的返回值本身就是一个Promise,所以,我们也可以在await 后面也可以跟一个异步函数。 代码举例: ```js const request1 = function () { return new Promise((resolve, reject) => { resolve('request1 请求成功'); }); }; async function request2() { const res = await request1(); return res; } async function request3() { // 【关键代码】request2() 既是一个异步函数,同样也是一个 Promise,所以可以跟在 await 的后面 const res = await request2(); console.log('res:', res); } request3(); ``` ## 异步函数的异常处理 前面讲过,如果异步函数内部在执行时遇到异常或者手动抛出异常时,那么, 这个异步函数返回的Promise 处于rejected 状态。 捕获并处理异步函数的异常时,有两种方式: - 方式1:通过 Promise的catch()方法捕获异常。 - 方式2:通过 try catch捕获异常。 在处理异步函数的异常情况时,方式2更为常见。 如果我们不捕获异常,则会往上层层传递,最终传递给浏览器,浏览器会在控制台报错。 ### 方式1:过 Promise的catch()方法捕获异常 ```js function requestData1() { return new Promise((resolve, reject) => { reject('任务1失败'); }) } function requestData2() { return new Promise((resolve, reject) => { resolve('任务2成功'); }) } async function getData() { // requestData1 在执行时,遇到异常 await requestData1(); /* 由于上面的代码在执行是遇到异常,所以,这里虽然什么都没写,底层默认写了如下代码: return Promise.reject('任务1失败'); */ // 下面这行代码不会再走了 await requestData2(); } // getData() 这个异步函数的返回值是一个 Promise,状态为 rejected,所以会走到 catch() getData().then(res => { console.log(res); }).catch(err => { console.log('err:', err); }); ``` 打印结果: ``` err: 任务1失败 ``` ### 方式2:通过 try catch 捕获异常 如果你觉得上面的写法比较麻烦,还可以通过 try catch 捕获异常。 代码举例: ```js function requestData1() { return new Promise((resolve, reject) => { reject('任务1失败'); }) } function requestData2() { return new Promise((resolve, reject) => { resolve('任务2成功'); }) } async function getData() { try { // requestData1 在执行时,遇到异常 await requestData1(); /* 由于上面的代码在执行是遇到异常,当前任务立即终止,所以,这里虽然什么都没写,底层默认写了如下代码: return Promise.reject('任务1失败'); */ // 下面这两代码不会再走了 console.log('qianguyihao1'); await requestData2(); } catch (err) { // 捕获异常代码 console.log('err:', err); } } getData(); console.log('qianguyihao2'); ``` 打印结果: ``` qianguyihao2 err1: 任务1失败 ``` ## 总结 在 async 函数中,不是所有的 异步任务都需要 await。如果两个任务在业务上没有**依赖关系**,则不需要 await;也就是说,可以并发执行,不需要线性执行,避免无用的等待。 ## 参考链接 - [js async await 终极异步解决方案](https://www.cnblogs.com/CandyManPing/p/9384104.html) - [理解 JavaScript 的 async/await](https://segmentfault.com/a/1190000007535316) ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/11-异常处理方案.md ================================================ --- title: 11-异常处理方案 --- ## 异常处理方案 在JS开发中,**处理**异常包括两步:先**抛出**异常,然后**捕获**异常。 ### 为什么要做异常处理 异常处理非常重要,至少有以下几个原因: 1. 防止程序报错甚至停止运行:当代码执行过程中发生错误或异常时,如果没有适当的异常处理机制,程序可能会报错、停止运行,甚至崩溃。通过处理异常,我们可以捕获错误并采取适当的措施避免系统报错。 2. 错误排查和调试:异常处理有助于定位和排查错误。可以通过捕获异常并输出相关信息,比如打印日志、错误上报、跟踪堆栈等等,以便快速定位问题所在,并进行调试和修复。 3. 提高代码健壮性和可靠性:可以采取适当的措施处理潜在的异常情况,从而减少程序出错的可能性。 4. 提升用户体验:通过兜底、容错、容灾等异常处理方案,可以向用户提供有效的错误信息提示,而不是让用户界面无响应甚至白屏。 ### 抛出异常 抛出异常的使用场景举例: 我们经常会封装一些工具函数,这些函数可能给自己用,也可能给外部团队用。 在函数内部,如果不符合预期的业务逻辑,或者遇到异常情况时,很多人的写法是直接 return,不往下执行了。但是 return 的写法存在一个很大的弊端:**调用者不知道是因为函数内部没有正常执行,还是执行的返回结果就是一个undefined**。return 的写法只是规避了问题,没有解决问题。建议的做法是:我们**需要手动抛出异常**。 ### 捕获异常 如果只是抛出异常,而不捕获异常的话,是比较危险的。这意味着当前任务立即终止,不再执行(当然,后续的其他任务会正常执行)。此外,这个异常信息会层层往上,抛给上层的**调用者**。如果一直未被捕获,则最终会抛给**浏览器**,浏览器控制台就会报错。 接下来,我们看一下不同代码场景下的异常处理方案。 ### 上报异常 如果有必要的话,你可以把异常信息和日志,上报给监控服务器,然后集中分析。我每天上班第一件事,就是打开监控系统,看错误日志,然后对症下药解决问题。 ## 同步代码的异常处理 ### 通过 throw 抛出异常 我们可以通过 `throw`关键字,抛出一个**用户自定义**的异常。当代码执行时遇到 throw 语句时,当前函数会停止停止,即:**当前函数** throw 后面的代码不会再执行。 throw 意思是,告诉调用者,当前被调用的函数报错了,调用者接下来需要捕获异常或者修改代码逻辑。 可以在 throw 的后面添加表达式或者数据类型,将添加的内容抛出去。数据类型可以是:number、string、boolean、对象等。 代码举例: ```js function sum(num1, num2) { if (typeof num1 !== "number") { throw "type error: num1传入的类型有问题, 必须是number类型" } if (typeof num2 !== "number") { throw "type error: num2传入的类型有问题, 必须是number类型" } return num1 + num2 } sum('a', 'b'); ``` 打印结果: ![image-20230608180755608](https://img.smyhvae.com/image-20230608180755608.png) 当然,我们还可以 throw一个封装好的对象。比如: ```js class myError { constructor(errCode, errMsg) { this.errCode = errMsg; this.errMsg = errMsg; } } function foo() { throw new myError(-1, 'not login'); } foo(); ``` 上面这种写法比较麻烦,一般不这么写。其实,JS中已经内置了 Error 类,专门用于生成错误信息。 ### Error 类 JS内置的 Error 类非常好用。 代码举例: ```js function foo() { throw new Error('not login'); } foo(); ``` 打印结果: ![image-20230608180103263](https://img.smyhvae.com/image-20230608180103263.png) 上面的打印结果可以看到,通过 Error 抛出来的错误,不仅可以看到报错信息,还可以看到调用栈,便于快速定位问题所在。非常方便。 ### 通过 try catch 捕获异常 同步代码,只抛出异常,不捕获异常的代码举例: ```js function foo() { throw new Error('not login'); } foo(); // 当前任务立即终止,不再执行;下面这行代码和 foo() 都在同一个 同步任务 中 console.log('qianguyihao'); ``` 打印结果: ![image-20230608182003407](https://img.smyhvae.com/image-20230608182003407.png) 可以看到,最后一行的 log 并没有执行。 我们可以使用 try catch 抛出异常, 对上述代码进行改进。代码举例: ```js function foo() { throw new Error('not login'); } // 通过 try catch 手动捕获异常 try { foo(); } catch (err) { console.log(err); } // 当前任务的后续代码会继续执行 console.log('qianguyihao'); ``` 打印结果: ![image-20230608182140002](https://img.smyhvae.com/image-20230608182140002.png) ### 通过 try catch finally 捕获异常 如果有些代码必须要执行,我们可以放到 finally 里。 - 不管是否遇到异常,finally的代码一定会执行。 - 如果 try 和 finally 中都有返回值,那么会使用finally中的返回值。 代码举例: ```js function foo() { throw new Error('not login'); } // 通过 try catch 捕获异常 try { foo(); } catch (err) { console.log(err); } finally { console.log("finally") } // 后续代码会继续执行 console.log('qianguyihao'); ``` ### try catch 只能捕获同步代码的异常 try catch只能捕获同步代码里的异常,而 Promise.reject() 是异步代码。 原因是:当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误。对于微任务而言(比如 promise)promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。 参考链接: - [try-catch 能抛出 promise 的异常吗](https://blog.csdn.net/xiaoluodecai/article/details/107297404) ### 使用 window.onerror 监听未被捕获的代码异常 如果JS代码抛出了异常但没有进行捕获,我们可以使用 JS 自带的 `window.onerror` 事件监听到这些错误。 代码举例: ```js // 监听同步代码的异常 window.onerror = (event) => { console.error('onerror 监听到未被捕获的异常:', event) }; function foo1() { throw new Error('not login'); } function foo2() { throw new Error('network error'); } foo1(); foo2(); ``` 打印结果: ![image-20230624162123559](https://img.smyhvae.com/image-20230624162123559.png) ## Promise 的异常处理 ### reject() 会自动抛出异常 在使用 Promise 时,当我们调用了 reject() 之后,系统会**自动抛出异常**,不需要我们手动抛出异常。这是 Promise的内部机制。但是我们需要手动捕获异常。 当 Promise 进入 rejected状态之后,会触发 catch()方法的执行,捕获异常。此时,成功完成了Promise异常处理的闭环。 ### 在then() 中抛出异常(重要) 当then()方法传入的回调函数中,如果遇到异常或者手动抛出异常,那么,then()所返回的**新的 Promise 会进入rejected 状态**,进而触发新Promise 的 catch() 方法的执行,做异常捕获。 **场景1**:在then()方法传入的回调函数中,如果代码**在执行时遇到异常**,系统会**自动抛出异常**。此时我们需要在 catch() 里**手动捕获异常**,否则会报错。 自动抛出异常的代码举例:(由于没有捕获异常,所以会报错) ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao1 fulfilled'); }); myPromise.then(res => { console.log('res1:', res); // 显然,person 并没有 forEach()方法。所以,代码在执行时,会遇到异常。 const person = { name: 'vae' }; person.forEach(item => { console.log('item:', item); }) // 这行代码不会执行,因为上面的代码报错了 console.log('qianguyihao2'); }).then(res => { console.log('res2:', res); }) // 定时器里的代码正常执行 setTimeout(() => { console.log('qianguyihao3'); }, 100) ``` 运行结果: ![image-20230615090007932](https://img.smyhvae.com/image-20230615090007932.png) 代码改进:(代码在执行时遇到异常,此时我们捕获异常,所以系统不会报错,这才是推荐的写法) ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao1 fulfilled'); }); myPromise.then(res => { console.log('res1:', res); // 显然,person 并没有 forEach()方法。所以,代码在执行时,会遇到异常。 const person = { name: 'vae' }; person.forEach(item => { console.log('item:', item); }) // 这行代码不会执行,因为上面的代码报错了 console.log('qianguyihao2'); }).then(res => { console.log('res2:', res); }).catch(err => { // 在 catch()方法传入的会调函数里,捕获异常 console.log('err2:', err); }) // 定时器里的代码正常执行 setTimeout(() => { console.log('qianguyihao3'); }, 100) ``` 打印结果: ![image-20230624072927944](https://img.smyhvae.com/image-20230624072927944.png) **场景2**:在then()方法传入的回调函数中,如果我们手动抛出异常,此时我们需要在 catch() 里**手动捕获异常**,否则会报错。 代码举例:(手动抛出异常,未捕获,所以会报错) ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 1'); }); myPromise.then(res => { console.log('res1:', res); // 手动抛出异常 throw new Error('qianguyihao rejected 2') }).then(res => { console.log('res2:', res); }) // 定时器里的代码正常执行 setTimeout(() => { console.log('qianguyihao3'); }, 100) ``` 打印结果: ![image-20230624073252797](https://img.smyhvae.com/image-20230624073252797.png) 代码改进:(代码在执行时遇到异常,此时我们捕获异常,所以系统不会报错,这才是推荐的写法) ```js const myPromise = new Promise((resolve, reject) => { resolve('qianguyihao fulfilled 1'); }); myPromise.then(res => { console.log('res1:', res); // 手动抛出异常 throw new Error('qianguyihao rejected 2') }).then(res => { console.log('res2:', res); }, (err) => { console.log('err2:', err); }) // 定时器里的代码正常执行 setTimeout(() => { console.log('qianguyihao3'); }, 100) ``` 打印结果: ![image-20230624073604599](https://img.smyhvae.com/image-20230624073604599.png) ### 使用 unhandledrejection 事件监听未被捕获的Promise异常 如果Promise抛出了异常但没有进行捕获,我们可以使用JS自带的 `unhandledrejection` 事件监听到这些错误。这个事件非常有用,尤其是当我们需要**集中做日志收集**时,屡试不爽。这个事件只能用于监听 Promise 中的异常,不能用于其他同步代码的异常。 先来看下面这行代码: ```js const myPromise = new Promise((resolve, reject) => { console.log('qianguyihao1'); reject('not login'); console.log('qianguyihao2'); }) ``` 打印结果: ![image-20230624154609747](https://img.smyhvae.com/image-20230624154609747.png) 上面的代码抛出了异常,但没有捕获异常,所以我们可以用 unhandledrejection 事件监听到。代码举例: ```js // 监听未被捕获的 Promise 异常 window.addEventListener('unhandledrejection', (event) => { console.error(`unhandledrejection 监听到异常,写法1: ${event.reason}`) }); window.onunhandledrejection = event => { console.error(`unhandledrejection 监听到异常,写法2: ${event.reason}`); }; window.onerror = (event) => { console.error('onerror 监听到异常:', event); }; const promise1 = new Promise((resolve, reject) => { reject('not login'); }) const promise2 = new Promise((resolve, reject) => { throw new Error('network error'); resolve(); }) ``` 打印结果: ![image-20230624172634569](https://img.smyhvae.com/image-20230624172634569.png) 可以看到,promise1 和 Promise2 的异常,都被 unhandledrejection 事件**收集**到了。 代码举例2: ```js window.addEventListener('unhandledrejection', (event) => { console.error(`unhandledrejection 监听到异常: ${event.reason}`) }); window.onerror = (event) => { console.error('onerror 监听到异常:', event); }; const myPromise = new Promise((resolve, reject) => { setTimeout(() => { throw new Error('not login'); resolve(); }, 100); }) ``` 打印结果: ![image-20230624172350994](https://img.smyhvae.com/image-20230624172350994.png) 上面的代码中,unhandledrejection 无法监听异常,因为定时器里的代码属于宏任务。 ### resolve()之后,再报错无效 代码举例: ```js const myPromise = new Promise((resolve, reject) => { resolve('fulfilled'); throw new Error("自定义错误"); }); myPromise.then(res => { console.log("res", res); return res + 1; }).catch(err => { console.log("err:", err); }); ``` 打印结果: ```js res fulfilled ``` 上方代码中,第3行的异常代码相当于没写。因为 resolve()之后,Promise的状态会立即进入 fulfilled,然后走到 then(),状态不可逆。 ## async await 的异常处理 ### 捕获异常 代码举例: ```js function requestData1() { return new Promise((resolve, reject) => { reject('任务1失败'); }) } function requestData2() { return new Promise((resolve, reject) => { resolve('任务2成功'); }) } async function getData() { // requestData1 在执行时,遇到异常 await requestData1(); /* 由于上面的代码在执行是遇到异常,所以,这里虽然什么都没写,底层默认写了如下代码: return Promise.reject('任务1失败'); */ // 下面这两行代码不会再执行了,因为上面的代码遇到了异常 console.log('qianguyihao1'); await requestData2(); } getData(); // 【注意】定时器里的代码会正常实行,因为它在另外一个宏任务里,不在上面的微任务里 setTimeout(() => { console.log('qianguyihao2'); }, 100) ``` 打印结果: ![image-20230615085743284](https://img.smyhvae.com/image-20230615085743284.png) 所以,为了避免上述问题,我们还需要手动捕获异常。我们捕获到异常之后,这个异常就不会继续网上抛了,更不会抛给浏览器。 ## 高级用法 ### ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/12-事件循环机制、宏任务和微任务.md ================================================ --- title: 12-事件循环机制、宏任务和微任务 --- ## 浏览器的事件循环机制(重要) ![image-20230608154453933](https://img.smyhvae.com/image-20230608154453933.png) 执行顺序如下: - 同步任务:进入主线程后,立即执行。 - 异步任务:会先进入 Event Table;等时间到了之后,再进入 任务队列 (Event Queue)排队(排队是因为同一时间,JS 只能执行一个任务),先进先出。比如说,`setTimeout(()=> {}, 1000)`这种定时器任务,需要等一秒之后再进入 Event Queue。 - 当主线程的任务执行完毕之后,此时主线程处于空闲状态,于是会去读取 Event Queue 中的任务队列,如果有任务,则进入到主线程去执行。 ## Node.js 事件循环机制 浏览器的 EventLoop 依据的是 HTML5 规范。而 Node.js 的 EventLoop 是由Node.js底层的 libuv 规定的。 libuv是一个专注于异步IO的跨平台库。 Node.js的事件循环中,有六个队列。其中,微任务有两个队列,宏任务有四个队列。 一、微任务队列: - 顺序1:next tick queue。比如:process.nextTick - 顺序2:other queue。比如:Promise的then回调、queueMicrotask 二、宏任务队列: - 顺序3:timer queue。比如:setTimeout、setInterval - 顺序4:poll queue。比如:IO事件 - 顺序5:check queue。比如:setImmediate - 顺序6:close queue。比如:close事件 参考链接: - 【荐】浏览器及nodeJS中的EventLoop事件循环机制:https://www.cnblogs.com/weiyongchao/p/13766429.html - 浏览器和Node 事件循环的区别:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/26 ## 宏任务和微任务 JS中的任务分为同步任务、异步任务。 JS中的异步任务分为宏任务(macrotask)、微任务(microtask)。在早期,异步任务中只有宏任务,没有微任务。后来的语言标准中,推出了“微任务”,因为**希望微任务能够尽早执行**。 ### 宏任务、微任务分类 事件循环的队列中,有两个队列。 1、**宏任务队列**,包含这些任务: - ajax 网络请求 - setTimeout、setInterval - DOM事件 - UI渲染 - I/O文件读写操作。 2、**微任务队列**,包含这些任务: - Promise的then回调 - Mutation Observer API:监听DOM节点变化。 - queueMicrotask():可直接将某个任务加入到微任务队列中。 在执行一个 Promise 对象时,当走完 resolve() 进入 fulfilled状态后,会立刻把 `.then()`里面的代码加入到**微任务队列**当中。 ### 任务的执行顺序 JS中的任务执行顺序:**同步任务 --> 微任务 --> 宏任务**。 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会**先查询微任务队列中是否还有任务需要执行**: - 当前宏任务执行之前,必须要保证微任务队列是空的。 - 如果微任务队列不为空,那就优先执行微任务队列中的任务。 ## 任务执行顺序的面试题 实际开发中,基本不会出现下面这些题目,因为很多时候我们无法精准控制异步任务的执行顺序。但是它们在面试中出现的频率特别高,因为熟悉这些思维训练,有利于考察我们对JS单线程、事件循环机制、宏任务和微任务等原理的掌握程度。 ### 题 1:宏任务和微任务的执行顺序 ```js setTimeout(() => { // 宏任务 console.log('setTimeout'); }, 0); new Promise((resolve, reject) => { resolve(); console.log('promise1'); // 同步任务 }).then((res) => { // 微任务 console.log('promise then'); }); console.log('同步任务'); // 同步任务 ``` 打印结果: ``` promise1 同步任务 promise then setTimeout ``` 上方代码执行的顺序依次是:**同步任务 --> 微任务 --> 宏任务**。 ### 题 2:在宏任务中嵌套了微任务 ```js new Promise((resolve, reject) => { setTimeout(() => { resolve(); console.log('setTimeout'); // 宏任务 }, 0); console.log('promise1'); }).then((res) => { // 微任务 console.log('promise then'); }); console.log('同步任务'); ``` 打印结果: ``` promise1 同步任务 setTimeout promise then ``` 上方代码解释:在执行宏任务的**过程中**,创建了一个微任务。但是需要**先把当前这个宏任务执行完**,再去**创建并执行**微任务。 ### 题3:综合题 ```js console.log("script start") setTimeout(() => { console.log("setTimeout1"); new Promise(resolve => { resolve(); }).then(() => { new Promise(resolve => { resolve(); }).then(() => { console.log("then1"); }); console.log("then2"); }); }); new Promise(resolve => { // 下面这两行代码,即便调换顺序,也不影响打印结果 console.log("promise1"); resolve(); }).then(() => { console.log("then3"); }); setTimeout(() => { console.log("setTimeout2"); }); console.log('同步代码'); queueMicrotask(() => { console.log("queueMicrotask") }); new Promise(resolve => { resolve(); }).then(() => { console.log("then4"); }); console.log("script end"); ``` 打印结果: ``` // 第一次循环 script start promise1 同步代码 script end // 第二次循环 then3 queueMicrotask then4 // 第三次循环 setTimeout1 then2 then1 // 第四次循环 setTimeout2 ``` ### 题4:async await 题目 代码举例: ```js console.log('script start') async function async2() { console.log('async2') } async function async1() { console.log('async1 start') await async2(); console.log('async1 end') } setTimeout(() => { console.log('setTimeout') }, 0) async1(); new Promise(resolve => { console.log('promise1') resolve(); }).then(function () { console.log('then1') }) console.log('script end'); ``` 打印结果: ``` script start async1 start async2 promise1 script end async1 end then1 setTimeout ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 06-JavaScript基础:异步编程/13-Promise的高级用法.md ================================================ ## Node.js 中的util.promisify()方法 Node.js 中有一个内置的方法 util.promisify(),它可以很方便地将 ES5回调函数写法的方法,转成Promise写法的方法。就不需要我们手动封装Promise了。 代码举例: ```js // 引入 util 模块 const util = require('util'); // 引入 fs 模块 const fs = require('fs'); // 返回一个新的函数,这个函数是一个 Promise 对象 const readFilePromise = util.promisify(fs.readFile); readFilePromise('readme.txt').then(res => { console.log('res:', res.toString()); }); ``` ## 使用 Promise 封装定时器,实现延迟函数 代码举例: ```js // 方法:XX秒后执行指定的代码。这个方法,就是在宏任务(定时器)的执行过程中,创建了一个微任务(resolve) function delaySeconds(delay = 1000) { return new Promise((resolve) => setTimeout(resolve, delay)); } delaySeconds(2000) .then(() => { console.log('qiangu'); return delaySeconds(3000); }) .then(() => { console.log('yihao'); }); ``` 打印结果: ```js // 2秒后打印: qiangu // 再等3秒后打印: yihao ``` ## 请求重试 参考链接: - 网络请求失败自动重试 js 重试机制:https://blog.csdn.net/Seasons_in_your_sun/article/details/126468481 ================================================ FILE: 06-JavaScript基础:异步编程/14-Promise常见面试题.md ================================================ ## 这些代码的打印结果是什么? 想要考察一个人对 Promise 的掌握程度,就让他看看这些代码的打印结果是什么。 **例1**:resolve()多次 ```js const promise = new Promise((resolve, reject) => { resolve(); resolve(); }); promise .then(res => { console.log('成功的回调'); }) .catch(err => { console.log('失败的回调'); }); ``` 打印结果: ``` 成功的回调 ``` ================================================ FILE: 07-JavaScript进阶/01-var、let、const的区别.md ================================================ --- title: 01-var、let、const的区别 --- ## var、let、const 的区别 ### 1、var 声明的变量会挂载在 window 对象上,而 let 和 const 声明的变量不会 举例: ```js var a = '我是a'; console.log(a); // 打印结果:我是a console.log(window.a); // 打印结果:我是a ``` ```js let b = '我是b'; console.log(b); // 打印结果:我是b console.log(window.b); // 打印结果:undefined ``` ```js let c = '我是c'; console.log(c); // 打印结果:我是c console.log(window.b); // 打印结果:undefined ``` var 的这一特性,会造成 window 全局变量的污染。举例如下: ```js var innerHeight = 100; console.log(window.innerHeight); // 打印结果:永远都是100 ==> 会覆盖 window 自带的 innerHeight 属性 ``` ### 2、var 声明的变量存在变量提升,let 和 const 声明的变量不存在变量提升 举例:(先使用,再声明) ```js console.log(a); // 打印结果:undefined ==> a已经声明但没有赋值 var a = '我是a'; ``` ```js console.log(b); // 报错:Uncaught ReferenceError: Cannot access 'b' before initialization ==> 找不到b这个变量 let b = '我是b'; ``` ```js console.log(c); // 报错:Uncaught ReferenceError: Cannot access 'c' before initialization ==> 找不到c这个变量 const c = '我是c'; ``` ### 3、var 声明不存在块级作用域,let 和 const 声明存在块级作用域 举例: ```js { var a = '我是a'; let b = '我是b'; const c = '我是c'; } console.log(a); // 我是a console.log(b); // 报错:Uncaught ReferenceError: b is not defined ==> 找不到b这个变量 console.log(c); // 报错:Uncaught ReferenceError: c is not defined ==> 找不到c这个变量 ``` 报错是因为找不到 b 和 c 这两个变量。 ### 4、同一作用域下,var 可以重复声明变量,let 和 const 不能重复声明变量 ```js var a = '我是a'; var a = 'qianguyihao'; console.log(a); // 打印结果:qianguyihao ``` ```js let b = '我是b'; let b = 'qianguyihao'; console.log(b); //报错:Uncaught SyntaxError: Identifier 'b' has already been declared ==> 变量 b 已经被声明了 ``` ```js const c = '我是c'; const c = 'qianguyihao'; console.log(c); //报错:Uncaught SyntaxError: Identifier 'c' has already been declared ==> 变量 c 已经被声明了 ``` 备注:通过第3、第4点可以看出:使用 let/const 声明的变量,不会造成全局污染。 ### 5、let 和 const 的暂时性死区(DTC) **举例 1**:(表现正常) ```js const name = 'qianguyihao'; function foo() { console.log(name); } foo(); // 执行函数后,打印结果:smyhvae ``` 上方例子中, 变量 name 被声明在函数外部,此时函数内部可以直接使用。 **举例 2**:(报错) ```js const name = 'qianguyihao'; function foo() { console.log(name); const name = 'hello'; } foo(); // 执行函数后,控制台报错:Uncaught ReferenceError: Cannot access 'name' before initialization ``` 代码解释:如果在当前块级作用域中使用了变量 name,并且当前块级作用域中通过 let/const 声明了这个变量,那么,**声明语句必须放在使用之前,也就是所谓的 DTC(暂时性死区)**。DTC 其实是一种保护机制,可以让我们养成良好的编程习惯。 关于”暂时性死区“的更多介绍,详本项目的另一篇文章《JavaScript之ES6语法:变量let、const和块级作用域.md》。 ### 6、const:一旦声明必须赋值;声明后不能再修改 一旦声明必须赋值: ```js const a; console.log(a); // 报错:Uncaught SyntaxError: Missing initializer in const declaration ``` ### 总结 基于上面的种种区别,我们可以知道:var 声明的变量,很容易造成全局污染。以后我们尽量使用 let 和 const 声明变量吧。 ## const 常量到底能不能被修改 我们知道:用 const 声明的变量无法被修改。但还有一点,我们一定要记住: - 如果用 const 声明基本数据类型,则无法被修改; - 如果用 const 声明引用数据类型(即“对象”),这里的“无法被修改”指的是**不能改变内存地址的引用**;但对象里的内容是可以被修改的。 举例 1:(不能修改) ```js const name = 'qianguyihao'; name = 'vae'; // 因为无法被修改,所以报错:Uncaught TypeError: Assignment to constant variable ``` 举例 2:(不能修改) ```js const obj = { name: 'qianguyihao', age: 28, }; obj = { name: 'vae' }; // 因为无法被修改,所以报错:Uncaught TypeError: Assignment to constant variable ``` 举例 3:(可以修改) ```js const obj = { name: 'qianguyihao', age: 28, }; obj.name = 'vae'; // 对象里的 name 属性可以被修改 ``` 因为 变量名 obj 是保存在**栈内存**中的,它代表的是对象的引用地址,它是基本数据类型,无法被修改。但是 obj 里面的内容是保存在**堆内存**中的,它是引用数据类型,可以被修改。 ## 传值和传址的区别 详见《JavaScript基础/对象简介.md》。 ## for 循环的经典案例 详见《JavaScript之ES6语法:变量let、const和块级作用域.md》。 ## 参考链接 - [JS 中 var、let、const 区别](https://juejin.im/post/5e49249be51d4526e651b654) ================================================ FILE: 07-JavaScript进阶/02-浅拷贝和深拷贝.md ================================================ ## 前言 在 JavaScript 的编程中经常需要对数据进行复制,这就涉及到浅拷贝和深拷贝,是非常重要的概念。 ## 浅拷贝 ### 概念 创建一个新的对象B,来接收你要重新复制的对象A的值: - 如果对象A里面的属性是基本类型,拷贝的是基本类型的值; - 但如果对象A里面的属性是引用类型,拷贝的是内存中的**地址**(不是拷贝**值**)。也就是说,拷贝后的内容和原始内容,指向的是同一个地址。如果一个对象的属性值发生了变化,另一个对象的属性值也会发生变化。 浅拷贝在拷贝引用类型的数据时,只拷贝**第一层**的属性,再深层的属性无法进行拷贝。用一个成语形容叫“藕断丝连”。 ## 深拷贝 ### 概念 创建一个新的对象B,来接收你要重新复制的对象A的值: - 在堆内存中开辟了一块全新的内存地址,将对象A的属性完全复制过来。 - 这两个对象相互独立、互不影响,彻底实现了内存上的分离。 下面讲一下实现深拷贝的几种方式。 ### 方式1:JSON.stringify() 和 JSON.parse() 这是最简单的深拷贝方法,先把对象序列化成 json 字符串,然后将JSON 字符串生成一个新的对象。 代码实现: ```js let obj1 = { a:1, b:[1,2,3] } let str = JSON.stringify(obj1); let obj2 = JSON.parse(str); console.log(obj2); //{a:1,b:[1,2,3]} obj1.a = 2; obj1.b.push(4); console.log(obj1); //{a:2,b:[1,2,3,4]} console.log(obj2); //{a:1,b:[1,2,3]} ``` 方式1属于乞丐版。缺点是: (1)主要缺点: - 无法拷贝函数、undefined、symbol。经过 JSON.stringify 序列化之后的字符串中这个键值对会消失。 - 无法拷贝 Map、Set; - 无法拷贝对象的循环引用,即 obj[key] = obj。 (2)其他缺点: - 拷贝 Date 引用类型会变成字符串; - 拷贝 RegExp 引用类型会变成空对象; - 无法拷贝不可枚举的属性; - 无法拷贝对象的原型链; - 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null; 无法拷贝函数的代码举例: ```js const obj = { fn: () => {}, name: 'qianguyihao' }; console.log(JSON.stringify(obj)); // {"name":"qianguyihao"} ``` 无法拷贝循环引用的代码举例: ```js const obj = { fn: () => {}, name: 'qianguyihao' }; obj.self = obj; /* 控制台报错: Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' --- property 'self' closes the circle at JSON.stringify () */ console.log(JSON.stringify(obj)); ``` 小结:如果你的数据结构是简单的数据类型,使用方式1是最简单和快捷的选择;但如果数据类型稍微复杂一点,方式1 就不行了。 ### 方式2:手写递归 如果只考虑简单的数组、对象,方式2是满足要求的。 ```js const obj1 = { name: 'qianguyihao', age: 30, address: { city: 'shenzhen' } } const obj2 = deepClone(obj1) obj2.address.city = 'beijing' console.log(obj1.address.city) /** * 深拷贝 * @param {Object} obj 要拷贝的对象 */ function deepClone(obj = {}) { // 1、判断是值类型还是引用类型 if (typeof obj !== 'object' || obj == null) { // obj 如果不是对象和数组,或者是 null,就直接return return obj } // 2、判断是数组还是对象 // 初始化返回结果:数组或者对象 let result if (obj instanceof Array) { result = [] } else { result = {} } for (let key in obj) { // 保证 key 不是原型的属性 if (obj.hasOwnProperty(key)) { // 3、递归【关键代码】 result[key] = deepClone(obj[key]) } } return result } let obj1 = { a:{ b:1 } } let obj2 = deepClone(obj1); obj1.a.b = 2; console.log(obj2); // {a:{b:1}} ``` 上面的代码,还有一种写法,更容易理解: ```js function deepClone(obj) { let cloneObj = {} for(let key in obj) { // 遍历 if(typeof obj[key] ==='object') { cloneObj[key] = deepClone(obj[key]) // 是对象就再次调用该函数递归 } else { cloneObj[key] = obj[key] // 如果是基本类型,直接复制值 } } return cloneObj } let obj1 = { a:{ b:1 } } let obj2 = deepClone(obj1); obj1.a.b = 2; console.log(obj2); // {a:{b:1}} ``` 方式2只考虑了 object 和 Array这种 对普通的引用类型的值,是属于比较基础的深拷贝。缺点是: (1)主要缺点: - 无法拷贝函数 Function。 - 无法拷贝 Map、Set。 - 无法拷贝对象的循环引用,即 obj[key] = obj。 (2)其他缺点: - 无法拷贝不可枚举的属性以及 Symbol 类型。 - 无法拷贝 Date、RegExp、Error 这样的引用类型。 ### 方式3:改进版 针对上面几个问题,可以用如下几点改进: (1)针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法; (2)当参数为 Date、RegExp 类型,则直接生成一个新的实例返回; (3)利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链; (4)利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map 和 weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。 ```js /** * 深拷贝 * @param obj obj * @param map weakmap 为了避免循环引用 */ function cloneDeep(obj, map = new WeakMap()) { if (typeof obj !== 'object' || obj == null ) return obj // 避免循环引用 const objFromMap = map.get(obj) if (objFromMap) return objFromMap let target = {} map.set(obj, target) // Map if (obj instanceof Map) { target = new Map() obj.forEach((v, k) => { const v1 = cloneDeep(v, map) const k1 = cloneDeep(k, map) target.set(k1, v1) }) } // Set if (obj instanceof Set) { target = new Set() obj.forEach(v => { const v1 = cloneDeep(v, map) target.add(v1) }) } // Array if (obj instanceof Array) { target = obj.map(item => cloneDeep(item, map)) } // Object for (const key in obj) { const val = obj[key] const val1 = cloneDeep(val, map) target[key] = val1 } return target } ``` ================================================ FILE: 07-JavaScript进阶/03-迭代器和生成器.md ================================================ ## 迭代器 ### 概念 **迭代器**(Iterator)是 JavaScript 中一种特殊的对象,它提供了一种**统一的、通用的**方式**遍历**个各种不同类型的数据结构。可以遍历的数据结构包括:数组、字符串、Set、Map 等**可迭代对象**。我们也可以自定义实现迭代器,以支持遍历自定义的数据结构。 通过迭代器,我们可以按顺序逐个获取数据中的元素,不需要手动跟踪索引(索引也可称之为指针、游标)。迭代器的行为很像数据库中的游标(cursor)。 我们也不需要关心可迭代对象内部的实现细节,即不需要关心目标对象是数组还是字符串,还是其他的数据结构。对于迭代器来说,这些数据结构都是一样的处理方式。 迭代器是一种常见的编程模式,最早出现在1974年设计的CLU编程语言中。不仅仅在JS中,其他许多编程语言(比如 Java、Python 等)都提供了迭代器的概念和实现。技术实现各有不同,但目的都是帮助我们用通用的方式遍历对象的数据结构,提高代码的简洁性、可读性和维护性。 ### 迭代协议 迭代协议并不是编程语言的内置实现或语法,而是协议。迭代协议具体分为两个协议:可迭代协议、迭代器协议。 **迭代器协议**规定了产生一系列值(无论是有限个还是无限个)的标准方式。 迭代器是一个具体的对象,这个对象要符合迭代器协议。**在JS中,某个对象只有实现了符合特定要求的 next() 方法,这个对象才能成为迭代器**。 ### 实现原理:next() 方法 在JS中,迭代器的实现原理是通过定义一个特定的`next()` 方法,该方法在每次迭代中返回一个包含两个属性的对象:done 和 value。 具体来说,next() 方法有如下要求: (1)参数:无参数或者有一个参数。 (2)返回值:返回一个应当有以下两个属性的对象。属性值如下: - done 属性(Boolean 类型):表示迭代是否已经完成。当迭代器遍历完所有元素时,done 为 true,否则为 false。具体解释如下: - 如果迭代器可以产生序列中的下一个值,则为 false,这等价于没有指定 done 属性。 - 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 可以省略,如果 value 依然存在,即为迭代结束之后的默认返回值。 - value 属性:包含当前迭代步骤的值,可能是具体的值,也可能是 undefined。每次调用 next() 方法时,迭代器返回下一个值。done 为true时,可以省略。 ### 举例:为数组创建迭代器 按照上面讲的迭代器协议,我们可以给一个数组手动创建一个用于遍历的迭代器。代码举例如下: ```js const strArr = ['qian', 'gu', 'yi', 'hao']; // 为数组封装迭代器 function createArrayIterator(arr) { let index = 0; return { next: () => { if (index < arr.length) { return { done: false, value: arr[index++] }; } else { return { done: true }; } }, }; } const strArrIterator = createArrayIterator(strArr); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); console.log(JSON.stringify(strArrIterator.next())); ``` 打印结果: ``` {"done":false,"value":"qian"} {"done":false,"value":"gu"} {"done":false,"value":"yi"} {"done":false,"value":"hao"} {"done":true} ``` 你可能会有疑问:实际开发中,我们真的需要大费周章地为一个简单的数组写一个迭代器函数吗?数组直接拿来遍历不就完事了吗? 是的,这大可不必。初衷是为了了解迭代器的原理。 ## 可迭代对象 我们要注意区分一些概念:迭代器、可迭代对象、容器。迭代器是提供迭代功能的对象。可迭代对象是被迭代的目标对象,也称之为容器。 ### 概念 当一个对象实现了 **iterable protocol 协议**时,它就是一个可迭代对象。这个对象要求必须实现了 `@@iterator` 方法,在内部封装了迭代器。我们可以通过 `Symbol.iterator` 函数调用该迭代器。 当我们使用迭代器的方式去遍历数组、字符串、Set、Map 等数据结构时,这些数据对象就属于可迭代对象。这些数据对象本身,内部就自带了迭代器。 可是,有些数据对象,并不具备可迭代的能力,那要怎么封装成可迭代对象呢?以及,可迭代对象需要具备什么特征?可迭代对象有什么用处?这就是本段要讲的内容。 ### 可迭代对象的特征 凡是可迭代对象,都具备如下特征: 1、可迭代对象都有一个 [Symbol.iterator] 函数。通过这个函数,我们可以进行数据遍历操作。以一个简单的数组进行举例: ```js const myArr = ['qian', 'gu', 'yi', 'hao']; console.log(typeof myArr[Symbol.iterator]); console.log(myArr[Symbol.iterator]); console.log(typeof myArr[Symbol.iterator]()); console.log(myArr[Symbol.iterator]()); // 获取数组自带的迭代器对象 const myIterator = myArr[Symbol.iterator](); // 通过迭代器的 next() 方法遍历数组 console.log(myIterator.next()); console.log(myIterator.next()); console.log(myIterator.next()); console.log(myIterator.next()); console.log(myIterator.next()); ``` 打印结果: image-20230525211636012 2、可迭对象可以进行 for ... of 操作。其实 for ... of 底层就是调用了 `@@iterator` 方法。代码举例: ```js const myArr = ['qian', 'gu', 'yi', 'hao']; // 可迭代对象可以进行 for ... of 操作。for ... of 也是一种遍历操作。 for (const item of myArr) { // 这里的 item,其实就是迭代器里的 value 属性的值。 console.log(item); } ``` 打印结果: ``` qian gu yi hao ``` ### 原生可迭代对象 以下这些对象,都是原生可迭代对象,请务必记住: - String 字符串 - Array 数组 - Map - Set - arguments 对象 - NodeList 对象(DOM节点的集合) 原生可迭代对象的内部已经实现了可迭代协议,它们都符合可迭代对象的特征。比如,它们内部都有一个迭代器;他们可以用 for ... of 进行遍历。 为何要记住上面这些可迭代对象,因为可迭代对象的应用场景非常多,且非常好用。我们继续往下学习。 ### 可迭代对象的应用场景(重要) 可迭代对象有许多应用场景,包括但不仅限于: 1、JavaScript的语法: - for ... of - 展开语法 ... - yield* - 解构赋值 2、创建一些对象: - new Map([Iterable]):参数是可选的,可不传参数,也可以传一个可迭代对象作为参数 - new WeakMap([iterable]) - new Set([iterable]) - new WeakSet([iterable]) 3、方法调用 - Array.from(iterable):将一个可迭代对象转为数组 - Promise.all(iterable) - Promise.race(iterable) 今后在遇到这些应用场景时,这些原生可迭代对象可以直接拿来用。 比如说,通过阅读官方文档后我们得知,`new Set()`的写法中,括号里的参数可以不写,也可以传入一个可迭代对象 `iterable`。那么,字符串、数组、Set、Map等可迭代对象,在你需要的时候都可以传进去使用。而且,`const a = new Set()`写法中,变量 a 也是一个可迭代对象。 `Promise.all(iterable)` 只能传数组吗?非也。准确来说,Promise.all()的参数中,传入的不是数组,而是一个可迭代对象。代码举例: ```js const promise1 = Promise.resolve('promise1 resolve'); const promise2 = Promise.resolve('promise2 resolve'); const promise3 = Promise.resolve('promise3 resolve'); const promiseSet = new Set(); promiseSet.add(promise1); promiseSet.add(promise2); promiseSet.add(promise3); // 准确来说,Promise.all()的参数中,传入的不是数组,而是一个可迭代对象 Promise.all(promiseSet).then(res => { console.log('res:', res); }); ``` 代码举例: ``` res: ['promise1 resolve', 'promise2 resolve', 'promise3 resolve'] ``` arguments 同样是一个可迭代对象,但不是数组。我们可以通过`Array.from(iterable)`方法将 arguments 转为数组,进而让其享受数组的待遇,调用数组的各种方法。代码举例: ```js foo('a', 'b', 'c'); // 定义函数 function foo() { // Array.from() 中的参数可以传入可迭代对象,将参数转为数组。arguments 是 foo() 函数的参数 const arr = Array.from(arguments); console.log(arr); } ``` 打印结果: ``` ['a', 'b', 'c'] ``` 学完了迭代器、可迭代对象的知识之后,很多关于函数传参、数据遍历、数据结构等方面的JS知识,就能融会贯通了。 ## 手写迭代器 很多数据对象由于不是可迭代对象,我们可以为其手动创建一个迭代器,这个数据对象就成了可迭代对象。 ### 为普通对象创建外部迭代器 代码举例: ```js const myObj1 = { strArr: ['qian', 'gu', 'yi', 'hao'], }; // 为 myObj.strArr 封装迭代器 let index = 0; const strArrIterator = { next: () => { if (index < myObj1.strArr.length) { return { done: false, value: myObj1.strArr[index++] }; } else { return { done: true }; } }, }; console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); ``` 打印结果: ``` {done: false, value: 'qian'} {done: false, value: 'gu'} {done: false, value: 'yi'} {done: false, value: 'hao'} {done: true} ``` ### 将普通对象封装为可迭代对象 上面的数据 myObj1,不属于可迭代对象,因此我们单独写了一个迭代器对象 strArrIterator。但是这两个对象是分开的。 还有一种更高级的做法是,把迭代器封装到数据对象的内部。完事之后,这个数据对象就是妥妥的可迭代对象。 将普通的数据对象封装为可迭代对象时,**具体做法**是:在数据对象内部,创建一个名为`[Symbol.iterator]`的迭代器函数,这个函数名是固定的(这种写法属于计算属性名);然后这个函数内需要返回一个迭代器,用于迭代当前的数据对象。 我们以下面这两个对象为例: ```js const myObj1 = { strArr: ['qian', 'gu', 'yi', 'hao'], }; const myObj2 = { name: 'qianguyihao', skill: 'web', }; ``` 如果尝试用 for of 去遍历它们,会报错: ```js const myObj2 = { name: 'qianguyihao', skill: 'web', }; for (const item of myObj2) { // 打印报错:Uncaught TypeError: myObj2 is not iterable。意思是:myObj2 不是可迭代对象 console.log(item); } ``` 所以,我们可以将这两个普通对象封装为可迭代对象。 1、将 myObj1 封装为可迭代对象,遍历 myObj1.strArr。代码举例如下: ```js const myObj1 = { strArr: ['qian', 'gu', 'yi', 'hao'], // 在 myObj1 的内部创建一个迭代器 [Symbol.iterator]: function () { let index = 0; const strArrIterator = { next: function () { if (index < myObj1.strArr.length) { return { done: false, value: myObj1.strArr[index++] }; } else { return { done: true }; } }, }; return strArrIterator; }, }; // 获取 myObj1 的迭代器对象 const strArrIterator = myObj2[Symbol.iterator](); // 通过迭代器遍历 myObj1.strArr 的数据 console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); ``` 打印结果: ``` {done: false, value: 'qian'} {done: false, value: 'gu'} {done: false, value: 'yi'} {done: false, value: 'hao'} {done: true} ``` 上方代码有一个改进之处,如果把迭代器函数改成箭头函数,就可以通过 `this.strArr` 代表 `myObj2.strArr` 了,写法更简洁。代码改进如下: ```js const myObj1 = { strArr: ['qian', 'gu', 'yi', 'hao'], // 在 myObj1 的内部创建一个迭代器 [Symbol.iterator]: function () { let index = 0; const strArrIterator = { next: () => { if (index < this.strArr.length) { return { done: false, value: this.strArr[index++] }; } else { return { done: true }; } }, }; return strArrIterator; }, }; // 获取 myObj1 的迭代器对象 const strArrIterator = myObj2[Symbol.iterator](); // 通过迭代器遍历 myObj1.strArr 的数据 console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); console.log(strArrIterator.next()); ``` 打印结果不变。 2、将 myObj2 封装为可迭代对象,遍历里面的键值对。代码举例如下: ```js const myObj2 = { name: 'qianguyihao', skill: 'web', // 将普通对象 myObj2 封装为可迭代对象,目的是遍历 myObj2 的键值对 [Symbol.iterator]: function () { // const keys = Object.keys(this); // 获取对象的 key // const values = Object.values(this); // 获取对象的 value const entries = Object.entries(this); // 获取对象的键值对 let index = 0; const iterator = { next: () => { if (index < entries.length) { return { done: false, value: entries[index++] }; } else { return { done: true }; } }, }; return iterator; }, }; // 可迭对象可以进行for of操作,遍历对象的键值对 for (const item of myObj2) { const [key, value] = item; console.log(key, value); } ``` 打印结果: ``` name qianguyihao skill web ``` ### 将自定义类封装为可迭代对象 在面向对象开发时,如果你希望自己创建的类也具备可迭代的能力,那么,你可以在定义类的时候手动添加一个 `@@iterator`方法,让其成为可迭代对象。 代码举例: ```json // 定义类 class Person { constructor(name, arr) { this.name = name; this.arr = arr; } // 定义一个名为 [Symbol.iterator] 的实例方法,封装迭代器 [Symbol.iterator]() { let index = 0; const iterator = { next: () => { if (index < this.arr.length) { return { done: false, value: this.arr[index++] }; } else { return { done: true }; } }, }; return iterator; } } const person1 = new Person('千古壹号', ['前端', '工程师']); const person2 = new Person('许嵩', ['想象之中', '有何不可']); // Person的实例已经封装为可迭代对象了,可以通过 for ... of 进行遍历 for (const item of person2) { console.log(item); } ``` 打印结果: ``` 想象之中 有何不可 ``` ### 如何中断迭代器,停止继续遍历 迭代器在遍历数据对象的过程中,如果我们希望在符合指定条件下停止继续遍历,那么,我们可以使用 break、return、throw 等关键字中断迭代器。其中, break 关键字用得最多。 此外,我们还可在迭代器函数中添加一个名为`return()`的方法,这个方法的作用是监听迭代器的中断,书写代码的位置与 `next()`方法并列。 代码举例如下: ```js const myObj2 = { name: 'qianguyihao', skill: 'web', // 将普通对象 myObj2 封装为可迭代对象,目的是遍历 myObj2 的键值对 [Symbol.iterator]: function () { const entries = Object.entries(this); // 获取对象的键值对 let index = 0; const iterator = { next: () => { if (index < entries.length) { return { done: false, value: entries[index++] }; } else { return { done: true }; } }, // 【关键代码】监听迭代器的中断 return: () => { console.log('迭代器被中断了'); return { done: true }; }, }; return iterator; }, }; // 可迭对象可以进行 for of 操作,遍历对象的键值对 for (const item of myObj2) { const [key, value] = item; console.log(key, value); if (value == 'qianguyihao') { // 【关键代码】如果发现 value 为 qianguyihao,则中断迭代器,停止继续遍历 break; } } ``` 打印结果: ``` name qianguyihao 迭代器被中断了 ``` 根据打印结果可以看出,迭代器只遍历了 myObj2 对象的第一个元素,符合指定条件后,通过 break 语句中断了迭代器,停止了继续遍历;与此同时,迭代器中的 return() 函数监听到了迭代器的中断。对了,return() 函数中,还需要写 `return { done: true }`表示迭代器的使命已结束;如果不写这行则会报错:`Uncaught TypeError: Iterator result undefined is not an object`。 ## 生成器 ### 概念 我们平时写的函数,基本是通过 return 返回值,或者发生异常,函数才会终止执行。这还不够灵活。 **生成器**是 ES6 中新增的一种特殊的函数,所以也称为“生成器函数”。它可以更灵活地控制函数什么时候执行, 什么时候暂停等等,**控制精度很高**。 生成器函数使用 `function*` 语法编写。最初调用时,生成器函数不执行任何代码,而是返回一个称为 Generator 的迭代器。通过调用生成器的 next() 方法时,Generator 函数将执行,直到遇到 yield 关键字时暂停执行。 可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次。 ### 生成器函数和普通函数的区别 - 生成器函数需要在 `function` 关键字后面加一个符号 `*`。 - 生成器函数可以通过 `yield` 关键字控制函数的执行流程。 - 生成器函数的返回值是一个生成器(Generator)。生成器是一种特殊的迭代器。 ## 生成器函数拆解 ### 定义一个生成器函数 如果要定义一个生成器函数,我们需要在`function`单词和函数名之间加一个`*`符号。 `*`符号有下面四种写法,最推荐的是第一种写法: ```js function* generator1() { /*code*/ } // 推荐写法 function *generator2() { /*code*/ } function * generator3() { /*code*/ } function*generator4() { /*code*/ } ``` 截图如下: ![image-20230624184902630](https://img.smyhvae.com/image-20230624184902630.png) 代码举例: ```js function* foo() { console.log('1'); console.log('2'); console.log('3'); } foo(); ``` ### yield 表达式 但是上面的代码写完后,并不会有打印结果,因为**我们还需要调用生成器的 next()方法,生成器函数才会执行,直到遇到 yield 关键字后暂停执行**;反反复复,**直到遇到 return关键字,或者遇到函数末尾时,结束执行**。代码举例: ```js // 通过 * 符号,定义一个生成器函数 function* foo() { console.log('1'); yield; console.log('2'); // 下面这行 console.log('a') 会跟 yield 一起执行 yield console.log('a'); console.log('3'); } const generator = foo(); // 返回一个生成器对象 // 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行 generator.next(); // 这行代码执行后,打印结果是:1 generator.next(); // 这行代码执行后,打印结果是:1 2 a generator.next(); // 这行代码执行后,打印结果是:1 2 a 3 ``` 仔细看注释,生成器 generator 每调用一次 next() ,foo()函数里的代码就往下执行一次,直到遇到 yield 后暂停。 ### next() 方法的返回值 生成器既然是一种特殊的迭代器,那么它也有 next()方法,而且 next()方法里同样有 done 和 value 这两个属性。我们来看看这两个属性的**默认属性值**是什么: ```js // 通过 * 符号,定义一个生成器函数 function* foo() { console.log('阶段1'); yield; console.log('阶段2'); yield; console.log('阶段3'); return; // 执行 return 之后,函数不再继续往下走了,生成器的 next()方法的 done 属性值为 true。 console.log('阶段4'); } // 执行生成器函数,返回一个生成器对象 const generator = foo(); // 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。 console.log(generator.next()); console.log(generator.next()); console.log(generator.next()); ``` 打印结果: ``` 阶段1 {value: undefined, done: false} 阶段2 {value: undefined, done: false} 阶段3 {value: undefined, done: true} ``` 上方代码的打印结果可以看出,生成器函数在遇到函数的末尾,或者遇到 return 之后,函数就不再继续往下走了,next()方法的 done 属性值为 true。 还可以看到,next()方法的 value 属性值默认为 undefined,**如果某些情况下我们希望 value属性有值**的话,可以通过 yield 关键字进行传递。代码举例: ```js // 通过 * 符号,定义一个生成器函数 function* foo() { console.log('阶段1'); yield 'a'; // 【关键代码】yield 后面写的内容,就是传递给 next() 方法的 value 属性值 console.log('阶段2'); yield 'b'; console.log('阶段3'); return; // 这里的 return,相当于 return undefined console.log('阶段4'); } // 执行生成器函数,返回一个生成器对象 const generator = foo(); // 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。 console.log(generator.next()); // 打印生成器对象的 next()方法 console.log(generator.next()); console.log(generator.next()); ``` 打印结果: ``` 阶段1 {value: 'a', done: false} 阶段2 {value: 'b', done: false} 阶段3 {value: undefined, done: true} ``` ### next() 方法的参数 根据前面的代码实例得知,生成器函数是分多个阶段执行的。此时有一个诉求:如何给当前阶段传递参数呢? 答案是:可以通过当前阶段 next() 方法的参数,给当前阶段传值。这个参数值会成为**上一个阶段** yield 语句的返回值。 代码举例: ```js // 通过 * 符号,定义一个生成器函数 function* foo() { console.log('阶段1'); // 【关键代码】第二次调用 next()方法时,通过 res2 接收 next()方法的参数值 const res2 = yield 'a'; console.log('阶段2:', res2); // 第三次调用 next()方法时,通过 res3 接收 next()方法的参数值 const res3 = yield 'b'; console.log('阶段3:', res3); return; } // 执行生成器函数,返回一个生成器对象 const generator = foo(); // 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。 console.log(generator.next()); // 执行第一阶段 console.log(generator.next('next2')); // 执行第二阶段,并传参 console.log(generator.next('next3')); // 指定第三阶段 ``` 打印结果: ``` 阶段1 {value: 'a', done: false} 阶段2: next2 {value: 'b', done: false} 阶段3: ntext3 {value: undefined, done: true} ``` 第一次学习时,这段代码可能比较难理解。在理解时需要注意的是,将 next2 这个属性值赋值给 res2,这个操作的执行时机是在**第二阶段的最开始**做的,不是在第一阶段的末尾做的。并且,这个属性值是通过第一阶段的 yield 返回值接收的。 ### 如何中途结束生成器的执行 如果想在中途结束生成器的执行,有三种方式: - 方式1:return 语句。这个在前面已经讲过。 - 方式2:通过生成器的 return() 函数。 - 方式3:通过生成器的 throw() 函数抛出异常。 方式2的代码举例: ```js // 通过 * 符号,定义一个生成器函数 function* foo() { console.log('阶段1'); const res2 = yield 'a'; console.log('阶段2:', res2); const res3 = yield 'b'; console.log('阶段3:', res3); return; } // 执行生成器函数,返回一个生成器对象 const generator = foo(); console.log(generator.next()); // 【关键代码】通过生成器的 return()方法, 立即结束 foo 函数的执行 console.log(generator.return('next2')); // 这行写了也没用,阶段2、阶段3都不会执行的 console.log(generator.next('next3')); ``` 打印结果: ``` 阶段1 {value: 'a', done: false} {value: 'next2', done: true} {value: undefined, done: true} ``` 上方代码可以看出,阶段2、阶段3都不会执行;return()方法里的参数传给了 value 属性。 方式3的代码举例: ```js // 通过 * 符号,定义一个生成器函数 function* foo() { console.log('阶段1'); const res2 = yield 'a'; console.log('阶段2:', res2); const res3 = yield 'b'; console.log('阶段3:', res3); return; } // 执行生成器函数,返回一个生成器对象 const generator = foo(); console.log(generator.next()); // 【关键代码】通过生成器的 throw()方法抛出异常, 立即结束 foo 函数的执行 console.log(generator.throw(new Error('next2 error'))); // 这行写了也没用,阶段2、阶段3都不会执行的 console.log(generator.next('next3')); ``` 打印结果: ``` 阶段1 {value: 'a', done: false} Uncaught Error: next2 error ``` ## 生成器的应用 ### 用生成器代替迭代器 在前面的迭代器内容中,我们学习过“将普通对象封装为可迭代对象”。那段代码改用生成器的写法也可以实现。代码举例: ```js const myObj2 = { name: 'qianguyihao', skill: 'web', // 将普通对象 myObj2 封装为可迭代对象,目的是遍历 myObj2 的键值对。通过生成器 function* 的的方式实现 [Symbol.iterator]: function* () { const entries = Object.entries(this); // 获取对象的键值对 for (let index = 0; index < entries.length; index++) { // 【关键代码】通过 yield 控制迭代器分阶段执行;并将每个阶段的值存放到迭代器的 next() 方法的 value 属性中 yield entries[index]; } }, }; // 写法1:通过 for ... of 遍历可迭代对象 for (const item of myObj2) { const [key, value] = item; console.log(key, value); } console.log('---'); // 写法2:通过 next() 方法遍历可迭代对象。与写法1等价。 const iterator = myObj2[Symbol.iterator](); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); ``` 打印结果: ``` name qianguyihao demo.html:30 skill web --- done: false, value:['name', 'qianguyihao'] done: false, value:['skill', 'web'] done: true, value:undefined ``` ### 在指定数字范围内生成一个值 代码举例: ```js function* createValueGenerator(start, end) { // 前闭后开 for (let i = start; i < end; i++) { yield i; } } const valueGenerator = createValueGenerator(1, 3); console.log(valueGenerator.next()); console.log(valueGenerator.next()); console.log(valueGenerator.next()); ``` 打印结果: ``` {value: 1, done: false} {value: 2, done: false} {value: undefined, done: true} ``` ## yield* 的使用 ### 使用 yield* 迭代可迭代对象 语法格式: ```js yield* 某个可迭代对象 ``` `yield*` 是 yield 的一种语法糖,也就是一种简写形式。它会依次迭代一个**可迭对对象**,每次迭代一个值,并且会产生一个**新的可迭代对象**。 我们在前面讲过可迭代对象的应用场景,简单提到过 `yield*`,接下来讲讲它具体如何使用的。 先来看下面这段代码,用生成器的方式迭代数组: ```js function* createArrayGenerator(arr) { for (const item of arr) { yield item; } } const myArr = ['a', 'b', 'c']; const arrGenerator = createArrayGenerator(myArr); console.log(arrGenerator.next()); console.log(arrGenerator.next()); console.log(arrGenerator.next()); console.log(arrGenerator.next()); ``` 打印结果: ``` {value: 'a', done: false} {value: 'b', done: false} {value: 'c', done: false} {value: undefined, done: true} ``` 上面这段代码,换成 `yield*` 的写法会非常简洁,如下: ```js function* createArrayGenerator(arr) { // 【关键代码】yield* 的后面必须是一个可迭代对象 yield* arr; } const myArr = ['a', 'b', 'c']; const arrGenerator = createArrayGenerator(myArr); console.log(arrGenerator.next()); console.log(arrGenerator.next()); console.log(arrGenerator.next()); console.log(arrGenerator.next()); ``` 打印结果不变。代码解释:`yield*` 的后面必须是一个可迭代对象,而且 createArrayGenerator()函数会返回一个可迭代对象 arrGenerator。 ### 将自定义类封装为可迭代对象 我们在前面学习迭代器时,曾通过迭代器“将自定义类封装为可迭代对象”。那段代码,改用生成器 `yield*` 的方式也可以实现。代码举例: ```js // 定义类 class Person { constructor(name, arr) { this.name = name; this.arr = arr; } // 【关键代码】在定义生成器函数时,如果没有 function 这个单词的话,也可以直接在函数名的前面添加 * 符号。 *[Symbol.iterator]() { // 【关键代码】一行代码,直接遍历 this.arr 这个可迭代对象,同时返回一个新的可迭代对象 yield* this.arr; } } const person1 = new Person('千古壹号', ['前端', '工程师']); const person2 = new Person('许嵩', ['想象之中', '有何不可']); // Person的实例已经封装为可迭代对象了,可以通过 for ... of 进行遍历 for (const item of person1) { console.log(item); } console.log('---'); // 也可以通过 Symbol.iterator() 方法进行遍历 const personIterator = person2[Symbol.iterator](); console.log(personIterator.next()); console.log(personIterator.next()); console.log(personIterator.next()); ``` 打印结果: ``` 前端 工程师 --- {value: '想象之中', done: false} {value: '有何不可', done: false} {value: undefined, done: true} ``` ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 07-JavaScript进阶/JavaScript开发积累.md ================================================ --- title: 09-JavaScript开发积累 --- ### 方法的注释 方法写完之后(注意,一定要先写完整),我们在方法的前面输入`/**`,然后回车,会发现,注释的格式会自动补齐。 比如: ```javascript /** * 功能:给定元素查找他的第一个元素子节点,并返回 * @param ele * @returns {Element|*|Node} */ function getFirstNode(ele){ var node = ele.firstElementChild || ele.firstChild; return node; } ``` ### 断点调试 (1)先让程序运行一遍。 (2)f12,弹出代码调试工具 (3)打断点: ![](http://img.smyhvae.com/20180124_2035.png) 然后刷新页面。 (4)一步步调试,每点击一次,执行一步: ![](http://img.smyhvae.com/20180124_2036.png) (5)监视变量: 当然,也可以添加变量或者表达式到监视窗口。操作如下: ![](http://img.smyhvae.com/20180124_2037.png) 上图中,选择变量或表达式,然后右键add to watch. 然后监视窗口: ![](http://img.smyhvae.com/20180124_2038.png) ### 2019-05-20-给数组、对象赋值 **数组赋值的正确写法**: ```javascript this.todayList.splice(0, 0, ...dataList); ``` **对象赋值的正确写法**: ```javascript Object.assign(obj2, obj1); ``` 上方代码中,是将`obj1` 的值追加到`obj2`中。如果对象里的属性名相同,会被覆盖。 ### 2019-11-25-在新的窗口中打开url 在原来的窗体中跳转到新页面: ```javascript window.location.href="要跳转的新页面"; ``` 在新窗体中打开新页面: ```javascript window.open('你所要跳转的新页面'); ``` ### 2019-12-10-JavaScript 新特性:Optional Chaining(可选链式调用)语法 以往写代码,我们一般都这么写: ```javascript if (result && result.user && result.user.name && result.user.name.length) { console.log('qianguyihao'); } ``` 有了 Optinal Chain 语法之后,就简洁很多了,可以这么写: ```javascript if (result?.user?.name?.length) { console.log('qianguyihao'); } ``` 参考链接: - 了解 JavaScript 新特性:Optional Chaining: - 原文链接: https://v8.dev/features/optional-chaining ### 2020-04-28-判断字符串的包含关系 ```js var str = 'qiangu2'; if (str == ('qiangu1' || 'qiangu2')) { console.log('qianguyihao'); } ``` 注意,上面的代码,根本就不会走 console.log 语句,因为if里面的内容是false。 如果我们要判断变量 `str` 是否在 `qiangu1、qiangu2`的合集里,我们应该这样写: ```js var str = 'qiangu2'; if (str == 'qiangu1' || str == 'qiangu2') { console.log('qianguyihao'); } ``` ================================================ FILE: 07-JavaScript进阶/Promise的一些题目.md ================================================ --- title: 07-Promise的一些题目 publish: true --- ## Promise 的执行顺序 ### 题目 1 代码举例: ```js const p = new Promise((resolve, reject) => { console.log(1); }); console.log(2); ``` 打印结果: ``` 1 2 ``` 我们需要注意的是:Promise 里的代码整体,其实是同步任务,会立即执行。 补充:上面的代码中,如果继续写`p.then()`,那么 `then()`里面是不会执行的。因为在定义 promise 的时候需要写 resolve,调用 promise 的时候才会执行 `then()`。 基于此,我们再来看下面这段代码: ```js const p = new Promise((resolve, reject) => { console.log(1); resolve(); }); console.log(2); p.then((res) => { console.log(3); }); ``` 打印结果: ``` 1 2 3 ``` ### 题目 2 代码举例: ```js // 封装 ajax 请求:传入回调函数 success 和 fail function ajax(url, success, fail) { var xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', url); xmlhttp.send(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { success(xmlhttp.responseText); } else { fail(new Error('接口请求失败')); } }; } new Promise((resolve, reject) => { ajax('a.json', (res) => { console.log('a接口返回的内容:' + res); resolve(); }); }) .then((res) => { console.log('a成功'); new Promise((resolve, reject) => { ajax('b.json', (res) => { console.log('b接口返回的内容:' + res); resolve(); }); }); }) .then((res) => { // 因为上面在b接口的时候,并没有 return,也就是没有返回值。那么,这里的链式操作then,其实是针对一个空的 promise 对象进行then操作 console.log('b成功'); }); ``` 打印结果: ``` a接口返回的内容 a成功 b成功 b接口返回的内容 ``` ### 题目 3 举例1: ```js new Promise((resolve, reject) => { resolove(); console.log('promise1'); // 代码1 }).then(res => { console.log('promise then)'; // 代码2:微任务 }) console.log('千古壹号'); // 代码3 ``` 打印结果: ``` promise1 千古壹号 promise then ``` 代码解释:代码1是同步代码,所以最先执行。代码2是**微任务**里面的代码,所以要先等同步任务(代码3)先执行完。 当写完`resolove();`之后,就会立刻把 `.then()`里面的代码加入到微任务队列当中。 ================================================ FILE: 07-JavaScript进阶/call、apply、bind的区别.md ================================================ --- title: 06-call、apply、bind的区别 publish: true --- ## call()和apply() ### 介绍 这两个方法都是函数对象的方法,需要通过函数对象来调用。 当函数调用call()和apply()时,函数都会立即**执行**。 - 都可以用来改变函数的this对象的指向。 - 第一个参数都是this要指向的对象(函数执行时,this将指向这个对象),后续参数用来传实参。 ### 显式绑定this JS提供的绝大多数函数以及我们自己创建的所有函数,都可以使用call 和apply方法。 它们的第一个参数是一个对象。因为你可以直接指定 this 绑定的对象,因此我们称之为显式绑定。 例1: ```javascript function foo() { console.log(this.a); } var obj = { a: 2 }; // 将 this 指向 obj foo.apply(obj); //打印结果:2 ``` ### 第一个参数的传递 1、thisObj不传或者为null、undefined时,函数中的this会指向window对象(非严格模式)。 2、传递一个别的函数名时,函数中的this将指向这个**函数的引用**。 3、传递的值为数字、布尔值、字符串时,this会指向这些基本类型的包装对象Number、Boolean、String。 4、传递一个对象时,函数中的this则指向传递的这个对象。 ### call()和apply()的区别 call()和apply()方法都可以将实参在对象之后依次传递,但是apply()方法需要将实参封装到一个**数组**中统一传递(即使只有实参只有一个,也要放到数组中)。 比如针对下面这样的代码: ```javascript var persion1 = { name: "小王", gender: "男", age: 24, say: function (school, grade) { alert(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade); } } var person2 = { name: "小红", gender: "女", age: 18 } ``` 如果是通过call的参数进行传参,是这样的: ```javascript persion1.say.call(persion2, "实验小学", "六年级"); ``` 如果是通过apply的参数进行传参,是这样的: ```javascript persion1.say.apply(persion2, ["实验小学", "六年级"]); ``` 看到区别了吗,call后面的实参与say方法中是一一对应的,而apply传实参时,要封装成一个数组,数组中的元素是和say方法中一一对应的,这就是两者最大的区别。 ### call()和apply()的作用 - 改变this的指向 - 实现继承。Father.call(this) ## bind() - 都能改变this的指向 - call()/apply()是**立即调用函数** - bind()是将函数返回,因此后面还需要加`()`才能调用。 bind()传参的方式与call()相同。 参考链接: - - - - [JS中改变this指向的方法](http://www.xiaoxiaohan.com/js/38.html) ================================================ FILE: 07-JavaScript进阶/this.md ================================================ --- title: 08-this --- ## this ### this的作用 - this可以帮我们简化很多代码。比如`xiaoming.name`、`xiaoming.age`可以直接写成`this.name`、`this.age`。 - 特别是当我们不知道一个对象是什么,或者这个对象没有名字但又很想调用它的时候,就会使用到this对象。 **举例:** - 遍历DOM对象,绑定click事件,调用当前点击的对象的id,而不是所有对象的id。 代码: ```html Document
    div0
    div1
    div2
    div3
    div4
    ``` 点击其中的任何一个元素后,`i`的打印结果是5。你可能会觉得很惊讶。我们来解释一下: 当代码执行完毕后,i已经等于5了。因为一旦运行程序,for循环已经执行完了,此时i等于5。 现在,我们尝试在 myDiv[i].onclick事件中写代码,如果打印: ``` console.log(i); //打印结果为5 ``` 如果打印: ``` console.log(myDiv[i].id); ``` 上方这行代码,打印会报错,因为i=5;如果想打印每个div的id,应该这样写: ``` console.log(this.id); ``` 你看,this的作用,就体现出来了。 PS:顺便提醒一下,上面的代码中,如果把`var i`改成`let i`,效果又完全不同了。参考链接:[let和var在for循环中的表现](http://blog.csdn.net/stopllL/article/details/64130664) ### 全局作用域中的this 当一段代码在浏览器中执行时,所有的全局变量和对象都是在window对象上定义的。换而言之,所有的全局变量和对象都属于window对象。 ## this的定律 this关键字永远指向函数(方法)运行时的**所有者**。 ### 函数赋值给变量时,this指向window 比如: ``` var foo1 = args.getInfo; foo1(); var foo2 = function(){}; foo2(); ``` this都是指向window。 ### 以函数形式调用时,this永远都是window ### 以方法的形式调用时,this是调用方法的对象 ## 解决闭包中的this指向问题 内部函数是可以访问到外部函数的变量的。 方式一:直接通过父函数的名字访问 方式二:如果不知道父函数的名字,在父函数里加一句`_this = this`,此时`_this`相当于父函数的名字。 ### 当this遇到一些特殊的函数时 ================================================ FILE: 07-JavaScript进阶/作用域.md ================================================ --- title: 05-作用域和闭包 publish: true --- ## 前言 面试问题: - 说一下对变量提升的理解 - 说明this的几种不同的使用场景 - 创建10个``标签,点击的时候弹出来对应的序号 - 如何理解作用域 - 实际开发中闭包的应用 涉及到的知识点: - 执行上下文 - this - 作用域 - 作用域链 - 闭包 ## 执行上下文 执行上下文主要有两种情况: - 全局代码: 一段` ``` 理解: - 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外) - 查找变量时就是沿着作用域链来查找的 查找一个变量的查找规则: ```javascript var a = 1 function fn1() { var b = 2 function fn2() { var c = 3 console.log(c) console.log(b) console.log(a) console.log(d) } fn2() } fn1() ``` - 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2 - 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3 - 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常 ================================================ FILE: 07-JavaScript进阶/创建对象和继承.md ================================================ --- title: 04-创建对象和继承 publish: true --- ## 创建对象的几种方式 ### 通过Object ```html 01_Object构造函数模式 ``` ### 方式二:对象字面量 ```html 02_对象字面量 ``` ### 方式三:工厂模式 - 方式:通过工厂函数动态创建对象并返回。 返回一个对象的函数,就是**工厂函数**。 - 适用场景: 需要创建多个对象。 - 问题: 对象没有一个具体的类型,都是Object类型。 由于这个问题的存在,工厂模式用得不多。 ```html 03_工厂模式 ``` ### 方式四:自定义构造函数 ```html 04_自定义构造函数模式 ``` 方式四引入了继承。 ## 继承的几种方式 ### 通过构造函数继承 在子类型构造函数中通用call()调用父类型构造函数 ### 原型链继承 子类型的原型为父类型的一个实例对象 ### 组合继承 ================================================ FILE: 07-JavaScript进阶/数据的赋值.md ================================================ --- title: 02-数据的赋值 --- ## 对象赋值 ### 用 Object.assgin() 实现浅拷贝 代码举例: ```js const obj1 = { name: 'qianguyihao', age: 28, desc: 'hello world', }; const obj2 = { name: '许嵩', sex: '男', }; // 【关键代码】浅拷贝:把 obj1 赋值给 obj2。这行代码的返回值也是 obj2 Object.assign(obj2, obj1); console.log(JSON.stringify(obj2)); ``` 打印结果: ``` { "name":"qianguyihao", "sex":"男", "age":28, "desc":"hello world" } ``` 注意,**上面这行代码在实际开发中,会经常遇到,一定要掌握**。它的作用是:将 obj1 的值追加到 obj2 中。如果两个对象里的属性名相同,则 obj2 中的值会被 obj1 中的值覆盖。 ## 数组赋值 ### 扩展运算符 ```js arr2 = arr1; ``` 上方代码中,其实是让 arr2 指向 arr1 的地址。也就是说,二者指向的是同一个内存地址。 如果不想让 arr1 和 arr2 指向同一个内存地址,我们可以借助扩展运算符来做: ```javascript let arr2 = [...arr1]; //arr2 会开辟新的内存地址 ``` 参考链接: - [javaScript中浅拷贝和深拷贝的实现](https://github.com/wengjq/Blog/issues/3) ================================================ FILE: 07-JavaScript进阶/数组的进阶操作.md ================================================ --- title: 数组的进阶操作 --- ## 删除数组的元素 现在有这样一个需求:遍历数组的同时,删除数组中的所有元素。 思路:我们可以想到的办法是使用数组的 splice() 方法,此外还有 JS 的 delete 关键字。 需要注意的是:使用数组的 splice() 方法删除数组元素之后,数组的长度会发生变化;使用 delete 删除数组中的元素之后,**数组的长度不会发生变化**。 下面来看看具体写法。 ### 使用数组的 splice() 方法 写法1:(错误) ```js let arr = [1, 2, 3, 4, 5]; for (let i = 0; i < arr.length; i++) { console.log(arr.length); arr.splice(i, 1); } console.log(arr); ``` 打印结果及解释: ```bash 数组长度的打印结果:5、4、3 arr最终的值:[2, 4] ``` 写法1的错误在于:没有意识到,splice方法会使 arr 的长度不断变化。 写法2:(错误) ```js let arr = [1, 2, 3, 4, 5]; const len = arr.length; for (let i = 0; i < len; i++) { console.log(arr.length); arr.splice(i, 1); } console.log(arr); ``` 打印结果: ```bash 数组长度的打印结果:5、4、3、2、2 arr最终的值:[2, 4] ``` 上方代码,依然没有达到预期的结果。 写法3:(正确写法,从末尾开始删除元素) ```js let arr = [1, 2, 3, 4, 5]; for (let i = arr.length - 1; i >= 0; i--) { console.log(arr.length); arr.splice(i, 1); } console.log(arr); console.log(arr.length); ``` 打印结果及解释: ```bash 数组长度的打印结果:// 5、4、3、2、1 arr最终的值:[] arr最终的长度:0 ``` ### 使用 delete 关键字 ```js let arr = [1, 2, 3, 4, 5]; for (let i = 0; i < arr.length; i++) { console.log(arr.length); // 注意点: 通过 delete 删除数组中的元素, 数组的长度不会发生变化 delete arr[i]; } console.log(JSON.stringify(arr)); ``` 打印结果及解释: ```bash 数组长度的打印结果:5、5、5、5、5 最终的arr,是空数组,长度为5:[null, null, null, null ,null] ``` ================================================ FILE: 07-JavaScript进阶/高阶函数.md ================================================ --- title: 25-高阶函数 publish: true --- ## 高阶函数 ### 高阶函数的概念 当 函数 A 接收函数 B 作为**参数**,或者把函数 C 作为**返回值**输出时,我们称 函数 A 为高阶函数。 通俗来说,高阶函数是 对其他函数进行操作 的函数。 ### 高阶函数举例1:把其他函数作为参数 ```js function fn1(a, b, callback) { console.log(a + b); // 执行完上面的 console.log() 语句之后,再执行下面这个 callback 函数。也就是说,这个 callback 函数是最后执行的。 callback && callback(); } fn1(10, 20, function () { console.log('我是最后执行的函数'); }); ``` 打印结果: ``` 30 我是最后执行的函数 ``` ### 高阶函数举例2:把其他区函数作为返回值 ```js function fn1() { let a = 20; return function () { console.log(a); }; } const foo = fn1(); // 执行 fn1() 之后,会得到一个返回值。这个返回值是函数 foo(); ``` 上面的代码,产生了闭包现象。关于闭包,详见下一篇文章《JavaScript基础/闭包.md》。 ================================================ FILE: 08-前端基本功:CSS和DOM练习/01-CSS基础练习:JD首页的制作(顶部和底部).md ================================================ --- title: 01-CSS基础练习:JD首页的制作(顶部和底部) publish: true --- ## 前言 京东是典型的电商类网站,学习这个网站的制作比较有价值。我们准备用WebStorm进行开发。 京东首页的截图为: ### 页面规划:新建一个空的工程 我们首先新建一个空的工程: ![](http://img.smyhvae.com/20180118_1733.png) ### CSS初始化(基本样式) 京东网站有一些基本样式,在各个页面中都要用到:(将这些基本样式copy到css.base里面去) base.css中的公共的部分: ```css @charset "UTF-8"; /*css 初始化 */ html, body, ul, li, ol, dl, dd, dt, p, h1, h2, h3, h4, h5, h6, form, fieldset, legend, img { margin:0; padding:0; } fieldset, img,input,button { border:none; padding:0;margin:0;outline-style:none; } /*去掉边框、去掉轮廓(比如输入框外面的蓝边框)*/ /*去掉列表前面的圆点*/ ul, ol { list-style: none; } input { padding-top:0; padding-bottom:0; font-family: "SimSun","宋体";} /*字体一般是指定这两个*/ select, input { vertical-align:middle;} select, input, textarea { font-size:12px; margin:0; } textarea { resize:none; } /*禁止文本输入框在右下角拖拽(因为拖动后会调整输入框大小)*/ img {border:0; vertical-align:middle; } /* 去掉图片底侧默认的3像素空白缝隙*/ table { border-collapse:collapse; } body { font:12px/150% Arial,Verdana,"\5b8b\4f53"; /*\5b8b\4f53指的是宋体*/ color:#666; background:#fff } /*start:清除浮动【推荐此方式进行清除浮动】。左浮动和右浮动都清除了,盒子刚好达到闭合的状态*/ .clearfix:before, .clearfix:after { content: ""; display: table; } .clearfix:after { clear: both; } .clearfix { *zoom: 1; /*IE/7/6*/ } /*end:清除浮动*/ a {color:#666; text-decoration:none; } /*去掉超链接的下划线*/ a:hover{color:#C81623;} /*鼠标悬停时的颜色*/ h1,h2,h3,h4,h5,h6 {text-decoration:none;font-weight:normal;font-size:100%;} /*font-size:100% 的意思是:让它们和父亲一样大,避免在不同的浏览器中显示大小不一致*/ s,i,em{font-style:normal;text-decoration:none;} /*去掉i标签和em的斜体,取消s标签的删除线*/ .col-red{color: #C81623!important;} /*公共类*/ .w { /*版心(可视区)。需要专门提取出来 */ width: 1210px; margin: 0 auto; } .fl { float: left } .fr { float: right } .al { text-align: left } .ac { text-align: center } .ar { text-align: right } .hide { display: none } ``` 上方代码解释: (1)**清除浮动**的方式: ```css .clearfix:before, .clearfix:after { content: ""; display: table; } .clearfix:after { clear: both; } .clearfix { *zoom: 1; /*IE/7/6*/ } ``` 这是如今比较流行的清除浮动的方式。比如小米官网就是采用的这种。 (2)其他属性: 我们给`fieldset, img,input,button`等标签设置了`outline-style:none`,意思去掉轮廓(比如去掉输入框外面的蓝边框,去掉之后,蓝色没有了,但是黑色依然存在)。去掉的原因是:首先,轮廓不好看;其次,在google浏览器和在火狐浏览器上,渲染的效果不同。 img标签中,我们通过`vertical-align:middle`属性**去掉图片底侧默认的3像素空白缝隙**,还有一种方法可以达到效果,那就是`display: block`。 给`h1,h2,h3,h4,h5,h6`设置**`font-size:100%`**是因为:h标签在每个浏览器中显示的大小不一致,设置此属性则表示,**让它们都和父亲一样大**。 (3)一些小标记 `s`是删除线,`i`和`em`是斜体。我们经常用它们做一些小装饰、小图标。 ### 引入css文件 base.css初始化之后,我们需要在html文件中引入它。引入外部样式表的方式如下:(`stylesheet`指样式表) ```html ``` 效果如下: ![](http://img.smyhvae.com/20180118_2002.png) 注意,**base.css和index.css的书写顺序不能颠倒**,因为是按照书写顺序,从上往下进行加载的。 ### Favicon 小图标 Favicon 图标指的是箭头处这个小图标: ![](http://img.smyhvae.com/20180118_2013.png) 官网链接可以下载这个小图标。 我们把`favicon.ico`图片放到工程文件的根目录,通过下面这种方式进行加载: ```html ``` 注意,`shortcut icon`是Favicon的专有名词,不能改成别的单词。 代码位置: ![](http://img.smyhvae.com/20180118_2020.png) ## 顶部导航的制作 我们先制作下面这个部分,它位于网站的最顶部: ![](http://img.smyhvae.com/20180118_2040.png) 顶部导航栏的html结构如下:(直接放在body标签下) ```html
    送至:北京
    • 你好,请登录    免费注册
    • 我的订单
    • 我的京东
    • 京东会员
    • 企业采购
    • 手机京东
    • 关注京东
    • 客户服务
    • 网站导航
    ``` 顶部导航栏需要加入的css样式如下:(放到base.css中) ```css /*顶部导航start*/ .shortcut { height: 30px; line-height: 30px; background-color: #f1f1f1; } .dt, .shortcut .fore { padding: 0 20px 0 10px; position: relative; } .dt i, .fore i { font: 400 15px/15px "宋体"; position: absolute; top: 13px; right: 3px; height: 7px; overflow: hidden; width: 15px; } .dt s, .fore s { position: absolute; top: -8px; left: 0; } .fr li { float: left; padding: 0 10px; } .fr .line { width: 1px; height: 12px; background-color: #ddd; margin-top: 9px; padding: 0; } .shortcut .tel-jd { padding: 0 20px 0 25px; } .tel { position: absolute; width: 15px; height: 20px; background: url(../images/sprite.png) no-repeat; left: 5px; top: 5px; } /*顶部导航end*/ ``` css代码解释: (1)整个的顶部导航栏是一个shortcut: ```css .shortcut { height: 30px; line-height: 30px; background-color: #f1f1f1; } ``` 然后将左侧的文字设置为左浮动,右侧的文字设置为右浮动。 (2)完成左侧部分的文字。 (3)右侧部分文字的结构:ul中放了九个li,用来存放文字。代码快捷键是`ul>li*9`(符号`>`是包含的关系)。 需要注意的是,“登录”和“注册”是同一个
  • 里面的两个``。它们是一个整体,所以要放到同一个li里。 (4)文字中间的间隔线: ![](http://img.smyhvae.com/20180119_1503.png) 上图所示,我们发现,每个li之间都有`1像素宽、12像素高的间隔线`,这个也是用li做的。 (5)增加文字右侧的小三角。 (6)在`手机京东`这个li中增加手机小图标,这里用到了css精灵图。 京东顶部导航条的工程文件:[2018-01-19-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-19-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar) ## 顶部banner图 接下来我们只做顶部的banner图,效果如下: ![](http://img.smyhvae.com/20180122_1020.png) 也就是上图中“1元抢宝”的那个位置。 涉及到的html代码如下: ```html ``` 在base.css中涉及到的css代码如下: ```css /*topbanner start*/ .topbanner { background-color: #8A25C6; } .close-banner { position: absolute; right:0; top:5px; width: 19px; height: 19px; background: url(../images/close.png) no-repeat; } .close-banner:hover { background-position:bottom; } .tp{ position: relative; } /*topbanner end*/ ``` 代码解释: 重点是`close-banner`这个class,也就是右上角的那个`X`。这里用到了子绝父相,注意,设置相对定位的父亲是`tb`这个class,因为要考虑到网页缩放的情况。 `.close-banner:hover`这个属性里,我们设置的背景图的定位是bottom,意思是,保证精灵图和父亲的底边贴齐,就不用使用像素的方式对精灵兔图进行定位了。 ## 搜索框 搜索框的UI如下: ![](http://img.smyhvae.com/20180122_1301.png) 上图中,包含了四个部分: - 左侧的logo - 中间的搜索框 - 右侧的购物车 - 热搜文字(中间搜索框的下方) 我们在WebStorm中输入`.search-logo+.search-input+.search-car+.search-moreA`,然后按tab键,就可以补齐代码: ```html
    ``` 相关的html代码如下: ```html ``` 相关的css代码如下: ```css /*search部分start*/ .search-logo { float: left; width: 362px; height: 60px; padding: 20px 0; } .search-logo a { width: 270px; height: 60px; display: block; text-indent: -9999px; background: url(../images/logo.png) no-repeat; } .search-input { float: left; height: 36px; padding-top: 25px; } .search-input input { float: left; width: 450px; height: 32px; padding-left: 4px; font: 400 14px/32px "microsoft yahei"; color: rgb(153, 153, 153); border: 2px solid #B61D1D; border-right: 0; } .search-input button { width: 82px; height: 36px; color: #fff; float: left; font: 400 16px/36px "微软雅黑"; background-color: #B61D1D; cursor: pointer; /*cursor: pointer; 变成小手*/ /*cursor: text; 变成光标*/ /*cursor: move; 变成四角箭头*/ /*cursor: default; 变成小白*/ } .search-car { float: right; width: 96px; height: 34px; line-height: 34px; padding-left: 43px; position: relative; margin: 25px 65px 0 0; border: 1px solid #DFDFDF; background-color: #F9F9F9; } .icon1 { position: absolute; top: 9px; left: 18px; width: 18px; height: 16px; background: url(../images/tel.png) no-repeat 0 -58px; } .icon2 { position: absolute; right: 10px; color: #999; /*font-family: "SimSun";*/ font: 13px/34px "SimSun"; } .icon3 { position: absolute; top: -5px; /*left: 0;*/ width: 16px; height: 14px; background-color: #C81623; line-height: 14px; text-align: center; color: #fff; border-radius: 7px 7px 7px 0; /*画圆角矩形*/ } .search-moreAlink { float: left; width: 530px; height: 28px; line-height: 28px; } .search-moreAlink a { margin-right: 8px; } /*search部分end*/ ``` 对于这四个部分,我们依次来讲解。 ### 1、左侧的logo 为了便于SEO,需要给图片这个超链接加上文字,然后设置文字的缩进为`text-indent: -9999px;`。 ### 2、搜索栏 “搜索”按钮:当我们把鼠标放在“搜索”上的时候, 发现鼠标变成了小手,这里是用到了`cursor`属性。 `cursor`有如下属性值: ```css cursor: pointer; /*变成小手*/ cursor: text; /*变成光标*/ cursor: move; /*变成四角箭头*/ cursor: default; /*变成默认的箭头*/ ``` ### 3、购物车 购物车里包含了四个元素:一个文字,三个图标。 为了让文字“我的购物车”这个``上下方向居中,我们给``标签的行高line-height为父亲的高度。 另外,“我的购物车”这四个字并不是水平居中的,于是,我们可以给它一个左侧的padding,而不用给右侧padding。 另外三个小图标可以用绝对定位来做。 右上角的小图标(圆角矩形):它的红色背景不是图片,而是用`border-radius`属性画的**圆角矩形**。 圆角矩形`border-radius`有下面几种画法: ``` border-radius: 宽/高一半; border-radius: 50%; border-radius: 0.3em; border-radius: 左上角 右上角 右下角 左下角; ``` ### 搜索框下方的热搜文字 热搜文字的功能性并不强,仅仅使用几个超链接``标签即可(每个 a 之间用margin隔开)。不需要像别的导航栏那样,在ul里放li,在li里放a。 注意,每个 a 之间是用margin隔开,不是用padding隔开;否则的话,鼠标点击中间的空白处也会出现跳转。 顶部导航条+顶部banner+搜索框的工程文件:[2018-01-21-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-21-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar) ## slogen:口号 要求实现的效果如下: ![](http://img.smyhvae.com/20180122_1630.gif) 上图可以看到,这里要实现的效果是:无论浏览器如何移动,要保证第二个slogen的左侧位于浏览器的正中间。这是可以用到绝对定位的知识。 html的代码如下: ```html
    ``` `class=slogen`指的是整个slogen区域。item表示四个口号中相同的部分。 css的代码如下; ```css /*底部的口号 start*/ .slogen { height: 54px; padding: 20px 0; background-color: #f5f5f5; position: relative; margin-bottom: 15px; } .item { width: 302px; position: absolute; top: 20px; left: 50%; } .slogen1 { margin-left: -608px; } .slogen2 { margin-left: -304px; } .slogen3 { margin-left: 2px; } .slogen4 { margin-left: 304px; } /*底部的口号 end*/ ``` 我们给item设置`left: 50%;`,确保每个item移到了父亲的正中间。然后每个item各自移动相应的距离即可实现。 ## 最下方的购物指南&区域覆盖 需要实现的效果如下: ![](http://img.smyhvae.com/20180122_1726.png) 上图中,需要实现的内容包括两个部分:左侧的购物指南和右侧的区域覆盖(我把这两个部分用红线隔开了)。 ### 购物指南 需要使用的布局如下: ![](http://img.smyhvae.com/20170704_1727.png) 这里的重点是要量出dt和dd的行高。 html代码如下: ```html
    ``` 因为这片区域是浮动的,我们要通过`clearfix`这个class清除浮动,防止其被覆盖。 css代码如下: ```css /*购物指南等 start*/ .footer-shopping { margin-top: 16px; /*和上方保持距离*/ } .footer-shopping dl{ float: left; width: 200px; } dl.last-dl { width: 100px; } .footer-shopping dt{ height: 34px; font: 400 16px/34px "microsoft yahei"; } .footer-shopping dd{ line-height: 20px; } /*购物指南等 end*/ ``` ### 区域覆盖 html代码如下: ```html

    京东自营覆盖区县

    京东已向全国2654个区县提供自营配送服务,支持货到付款、POS机刷卡和售后上门服务。

    查看详情 >
    ``` css代码如下: ```css /*覆盖区域 start*/ .coverage { float: left; width: 186px; height: 169px; margin-right: 60px; padding-left: 17px; background: url(../images/china.png) no-repeat left bottom; } .coverage h3 { height: 34px; font: 400 16px/34px "microsoft yahei"; } .coverage p { padding-top: 8px; } .coverage a { float: right; } /*覆盖区域 end*/ ``` 注意这里将精灵图设置为背景时,用到的定位属性是`left bottom`,意思是保证精灵图的左侧跟父亲左侧贴齐,下方和父亲下方贴齐。这样做的话,就不用通过像素来进行定位了。 ## 最底部 最底部的效果如下: ![](http://img.smyhvae.com/20180122_1909.png) 如上图所示,它包含了三个部分。 涉及到的html代码如下: ```html ``` 涉及到的css代码如下: ```css /*最底部start*/ .footer-bottom { margin-top: 20px; text-align: center; /*让文字在容器中水平方向居中*/ padding: 20px 0 30px; border-top: 1px solid #E5E5E5; } .footer-bottom .footer-about a{ margin: 0 10px; } .footer-copyright { padding: 10px 0; } .footer-bottom-img a { margin: 0 5px; } /*最底部end*/ ``` 你去京东官网看看,发现最最底部的文字竟然是图片: ![](http://img.smyhvae.com/20180122_1912.png) ## 总结 以上全部内容,最终实现的效果如下: ![](http://img.smyhvae.com/20180122_1920.png) 对应的工程文件:[2018-01-22-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-22-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar) ================================================ FILE: 08-前端基本功:CSS和DOM练习/02-CSS基础练习:JD首页的制作(快捷导航部分).md ================================================ --- title: 02-CSS基础练习:JD首页的制作(快捷导航部分) publish: true --- 我们在上一篇文章中制作的网页最顶部的导航,是属于网页导航。 本文中,Banner图上方的导航,叫做**快捷导航**(shortcut)。 ##快捷导航的骨架 我们先制作快捷导航的骨架。如下图所示: ![](http://img.smyhvae.com/20180123_1057.png) 上图中,`shortcut-nav-menu-all`和`shortcut-nav-menu-one`都是属于`shortcut-nav-menu`部分,只不过,后者是将父亲撑破了。 为了实现上图,对应的html代码如下: ```html ``` css代码如下: ```css /*shortcut-nav部分start*/ .shortcut-nav { height: 44px; border-bottom: 2px solid #B1191A; } .shortcut-nav-menu { /*撑开和撑破是两回事:撑开说明盒子变成那么大,撑破盒子还是那么大,子盒子很大。子盒子shortcut-nav-menu-one把父亲撑破了*/ width: 210px; height: 44px; /*浮动的盒子相互影响,不过是否在同一个盒子中*/ float: left; position: relative; z-index: 1; /*通过z-index属性将层级放到最高*/ } .shortcut-nav-menu-all a { display: block; width: 190px; height: 44px; color: white; padding: 0 10px; background-color: #B1191A; font: 400 15px/44px "microsoft yahei"; } .shortcut-nav-menu-one { height: 465px; margin-top: 2px; background-color: #C81623; border-left: 1px solid #B1191A; border-bottom: 1px solid #B1191A; /*border-left: 1px solid #000;*/ /*border-bottom: 1px solid #000;*/ } .shortcut-nav-items { width: 730px; height: 44px; float: left; } .shortcut-nav-items li { float: left; } .shortcut-nav-items a { display: inline-block; height: 44px; padding: 0 20px; color: #333; font: 400 16px/44px "microsoft yahei"; } .shortcut-nav-items a:hover { color: #C81623; } .shortcut-nav-img { width: 200px; height: 44px; float: right; margin-top: -10px; margin-right: 50px; /*position: relative;*/ /*left: -50px;*/ /*top: -10px;*/ } /*shortcut-nav部分end*/ ``` ## 具体的商品分类 商品的具体分类即`shortcut-nav-menu-one`部分,我们来实现这部分的代码。要求实现的效果如下: ![](http://img.smyhvae.com/20180123_1510.gif) 我们在上面的代码中已经给`shortcut-nav-menu-one`设置了一些属性(例如给左边和下边增加一个像素的红色边框),在此基础之上,需要新增的代码如下: html代码: ```html ``` css部分如下: ```css /*具体的商品分类start*/ .shortcut-nav-menu-one ul { font: 400 14px/31px "microsoft yahei"; /*字体属性具有继承性,让儿子 a 具有此继承性*/ } .shortcut-nav-menu-one li { padding-left: 10px; position: relative; } .shortcut-nav-menu-one a{ color: #fff; } .shortcut-nav-menu-one i { right: 10px; position: absolute; } .shortcut-nav-menu-one li:hover { background-color: #fff; } .shortcut-nav-menu-one li:hover a,span,i{ color: #C81623; } /*具体的商品分类end*/ ``` ## 轮播图slider + 京东快报 接下来,我们要实现下面这个部分: ![](http://img.smyhvae.com/20180123_1527.png) 组成部分包括:左侧的轮播图、右侧的京东快报 & 充话费 & 右下角的小海报。 结构如下: ```html
    ``` 我们依次来讲解。 ### 1、轮播图:main-slider 首页的banner图是首页独有的,所以这部分的css代码要写在index.css里,不要写在base.css里。 html代码如下: ```html
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    < >
    ``` 注意:超链接 a 标签中,`href="javascript:;`表示点击超链接时,什么都不做。 CSS代码如下: ```css .main-slider { float: left; margin: 12px 0 0 220px; width: 730px; height: 454px; position: relative; } .main-slider ul { position: absolute; bottom: 10px; left: 50%; margin-left: -66px; } .main-slider ul li { float: left; width: 18px; height: 18px; color: #fff; background-color: #3E3E3E; border-radius: 50%; /*圆角矩形*/ line-height: 18px; text-align: center; /*让 li 里面的文本水平方向居中*/ margin: 0 2px; cursor: pointer; /*鼠标悬停时变成小手*/ } .main-slider .arrow-left { /* 轮播图左侧的箭头*/ position: absolute; top: 50%; margin-top: -31px; left: 0px; width: 28px; height: 62px; background-color: rgba(0,0,0,0.3); color: #fff; font: 400 22px/62px "sumsun"; text-align: center; border-radius: 10px 0 0 10px; } .main-slider .arrow-left:hover { background-color: rgba(0,0,0,0.7); } .main-slider .arrow-right { /*轮播图右侧的箭头*/ position: absolute; top: 50%; margin-top: -31px; right: 0px; width: 28px; height: 62px; background-color: pink; background-color: rgba(0,0,0,0.3); color: #fff; font: 400 22px/62px "sumsun"; text-align: center; border-radius: 10px 0 0 10px; } .main-slider .arrow-right:hover { background-color: rgba(0,0,0,0.7); } ``` 代码解释如下; (1)轮播图,我们采取的方式是:在超链接 a 里面放一个img标签。 (2)指示点:在一个ul中放多个li。然后通过绝对定位的方式,让ul放在轮播图的正中间(水平方向)。最后详细设置每个指示点li的属性(比如,`text-align: center`属性可以让li里面的文字水平居中)。 (3)左右两边的箭头:鼠标悬停时,颜色不同。我们通过`background-color: rgba(0,0,0,0.3)`设置背景的透明度。 最终实现的效果如下: ![](http://img.smyhvae.com/20180123_1951.png) ### 京东快报 html代码如下: ```html

    京东快报

    更多 >
    • [特惠]新闻1
    • [公告]新闻2
    • [特惠]新闻3
    • [公告]新闻4
    • [特惠]新闻5
    ``` css代码如下; ```css .main-news-top-faster { height: 163px; border-bottom: 1px dashed #E4E4E4; /*虚线*/ } .main-news-top-faster-title { height: 32px; line-height: 32px; border-bottom: 1px dotted #E8E8E8; /*点线*/ padding: 0 15px; } .main-news-top-faster-title h2{ float: left; font: 400 16px/32px "microsoft yahei"; } .main-news-top-faster-title a { float: right; } .main-news-top-faster-content { padding: 5px 0 0 15px; } .main-news-top-faster-content li { line-height: 24px; } .main-news-top-faster-content span { font-weight: 700; margin-right: 5px; color: #666; } .main-news-top-money ul { width: 250px; } ``` ### 3、充话费部分:12个单元格(重要) **(1)步骤一:**画表格 充话费这部分,我们不用table标签来做,一般table标签一般是用来放文字的。这里因为有图片,所以我们用ul标签来做,在ul里放12个浮动的li。 如果我们直接这样进行设置: ```css .main-news-top-money ul { width: 250px; } .main-news-top-money li { width: 62px; height: 70px; border: 1px solid #E8E8E8; float: left; } ``` 会发现,效果不尽人意: ![](http://img.smyhvae.com/20180123_2202.png) 上图所示,我们发现,红框部分的12个li,并没有按照我们预期的那样进行排列。因为每个li有border。真实的li当中,它们的border是有重叠的。 解决办法: > 父亲宽度不够时,为了让盒子浮动不掉下去,可以给子盒子之上父盒子之下再给一个盒子,让它的宽度略大于父亲的宽度即可。 比如这里,**本身这个区域整体的宽度是250,我们就设置ul的宽度是260px即可(**满足的条件是:li的宽度*4 < **ul的宽度** < li的宽度*5)。 ul的宽度设置为260px之后发现,最右边和最下面的部分会多出来: ![](http://img.smyhvae.com/20180123_2207.png) 我们可以给`main-news-top-money`设置`overflow: hidden`,将多余的部分切掉(这是没有办法的事情)。 于是乎,充话费这部分的代码如下: html部分: ```html
    ``` css部分: ```css .main-news-top-money ul { width: 260px; /*让宽度略大于整体的宽度250px*/ } .main-news-top-money li { width: 62px; height: 70px; border: 1px solid #E8E8E8; float: left; border-top: 0; /* 将每个单元格的上边框去掉,因为跟单元格的下边框重合了。*/ margin-top: -1px; /* 整体向上移动一个单位,因为边框重合了*/ margin-left: -1px ;/* 整体向左移动一个单位,因为边框重合了*/ } ``` 这样的话,表格就画好了: ![](http://img.smyhvae.com/20180123_2240.png) **(2)步骤二:**往表格里填充内容 接下来,我们往表格里填充内容。最终,充话费部分的代码如下: html部分: ```html ``` index.css部分: ```css /*充话费部分start*/ .main-news-top-money { overflow: hidden; /*将多余的部分切掉*/ } .main-news-top-money ul { width: 260px; /*让宽度略大于整体的宽度250px*/ } .main-news-top-money li { width: 62px; height: 70px; border: 1px solid #E8E8E8; float: left; border-top: 0; /* 将每个单元格的上边框去掉,因为跟单元格的下边框重合了。*/ margin-top: -1px; /* 整体向上移动一个单位,因为边框重合了*/ margin-left: -1px ;/* 整体向左移动一个单位,因为边框重合了*/ } .main-news-top-money li a { display: block; width: 62px; height: 30px; padding-top: 40px; text-align: center; line-height: 30px; position: relative; } .main-news-top-money li a i { width: 25px; height: 25px; position: absolute; top: 13px; left: 18px; background: url("../images/fly.png") right top; } .main-news-top-money .main-news-top-money-icon2 { background: url("../images/fly.png") right -25px; } /*充话费部分end*/ ``` 代码解释: - 单元格里的文字:我们可以给单元格里的文字设置padding-top,保证文字位于单元格的底部。 - 单元格里的图片(精灵图)的位置:通过子绝父相的方式(子是图片``本身,相是每个单元格里的超链接文字``)。通过子绝父相的方式定位之后,发现精灵图都是一样的图标,目前的处理办法是:手动添加不同的class进行修改精灵图,以后等我们学习js了,就不用这么麻烦了。 画出的表格如下: ![](http://img.smyhvae.com/20180124_1121.png) ## 今日推荐 接下来,我们开始做下面这部分: ![](http://img.smyhvae.com/20180124_1434.png) 上图中的“今日推荐”,标签可以这样布局:ul > li > a > img 为了防止这部分的内容跑到上面去,我们可以给上面的`class-main`部分清除浮动。 “今日推荐”这部分的代码如下。 html代码如下: ```html ``` index.css中的代码如下; ```css /*今日推荐start*/ .today { padding: 10px 0 20px; } .today-left a{ display: block; width: 210px; height: 150px; background: url("../images/today.jpg"); } .today-right { float: right; width: 1000px; overflow: hidden; /*隐藏掉右侧超出的几个像素*/ position: relative; } .today-right ul { width: 410%; /*这一点很有技巧*/ } .today-right li{ float: left; margin-right: 1px; } /*今日推荐end*/ ``` ## banner两侧的广告 要实现的内容是下图中的箭头处: ![](http://img.smyhvae.com/20180124_1615.png) 注意这部分的div的位置,是放在`class="shortcut-nav"`和`class="main"`之间的。 两侧的广告其实是一个放在 a 标签里的超大背景图,而且这个大图的宽度超过了版心。所以,超链接的宽度给`width: 100%`更合适。a 的高度设置为图片的高度即可。 代码实现如下: html: ```html ``` index.css: ```css /*banner两侧的广告start*/ .banner-ad { position: relative; } .banner-ad a { width: 100%; height: 644px; background: url("../images/ad.png") no-repeat center top; position: absolute; } /*banner两侧的广告end*/ ``` 上方代码中,我们不用给图片的父亲`banner-ad`设置高度。 超链接a :我们不知道超链接的宽度是多少,所以直接设置为`width: 100%`。注意它的背景图的摆放位置,`center`确保了背景图位于水平方向的正中间,`top`确保了背景图和父亲定边对齐。 注意,上图中,两侧的广告实现之后发现,蓝框部分的两个位置(`main-news-top-faster`和`today-left`)点击时,发现跳转的是两侧广告的链接,因为它们的层级不够高。解决办法:给蓝框这两个部分加一个`position: relative`属性即可提高层级。 ## 总结 上一篇文章和这一篇文章,加起来,最终实现的效果如下: ![](http://img.smyhvae.com/20180124_1607.png) 工程文件:[2018-01-23-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-23-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar) ================================================ FILE: 08-前端基本功:CSS和DOM练习/03-DOM操作练习:基础练习.md ================================================ --- title: 03-DOM操作练习:基础练习 publish: true --- ## DOM操作练习 ### 举例1:点击按钮时,显示和隐藏盒子。 代码实现: ```html
    千古壹号
    ``` 代码解释: 当盒子是显示状态时,就设置为隐藏;当盒子是隐藏状态时,就设置为显示。注意这里的逻辑判断。 另外,这里用到了`innerHTHL`属性,它可以修改按钮上显示的文字。 代码最终显示的效果如下: 20180127_1518.gif ### 举例2:美女相册 这里推荐一个网站: - 占位图片生成的在线网站: 好处是:素材做出来之前,先留出空位,方便以后换图。比如这个链接可以生成400*300的占位图片。 需求: - (1)点击小图片,改变下面的大图片的src属性值,让其赋值为a链接中的href属性值。 - (2)让p标签的innnerHTML属性值,变成a标签的title属性值。 为了实现美女相册,代码如下: ```html

    美女画廊

    注册

    选择一个图片

    ``` 代码解释: (1)获取事件源:我们通过`ul.getElementsByTagName("a")`来获取ul里面的a元素。 (2)绑定事件:因为要绑定一个数组,所以这里用for循环来绑定 (3)【重要】书写事件驱动程序:这里是用`img.src = this.href`,而不是用`img.src = aArr[i].href`。因为this指的是函数的调用者。如果写成后者,等i变成了4,就会一直是4。显然不能达到效果。 (4)代码的最后一行:`return false`表示:阻止继续执行下面的代码。 实现的效果如下: 20180127_1630.gif 工程文件:[2018-01-27-美女相册demo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-27-%E7%BE%8E%E5%A5%B3%E7%9B%B8%E5%86%8Cdemo.rar) ### 举例3:鼠标悬停时,显示二维码大图 ```html
    ``` 实现效果: 20180127_1800.gif ## 表单元素的属性 表单元素的属性包括:type、value、checked、selected、disabled等。 ### 举例1:禁用文本框/解禁文本框 ```html 账号:

    密码: ``` 当文本框被禁用之后,文本框只读,不能编辑,光标点不进去。 上方代码可以看到,**禁用文本框**的代码是: ```javascript inp.disabled = "no"; //让disabled属性出现,即可禁用 ``` 我们的目的时让`disabled`这个属性出现,即可禁用。所以,属性值里可以写数字,可以写任意一个字符串,但不能写0,不能写false,不能为空。一般我们写no。 **解禁文本框**的代码是: ```javascript inp.disabled = false; // 方法1:让disabled属性消失,即可解禁。 inp.removeAttribute("disabled"); //方法2:推荐 ``` 我们的目的是删除`disabled`属性,即可解禁。属性值可以是false,可以是0。但我们一般采用方式2进行解禁。 实现效果: ### 举例2:文本框获取焦点/失去焦点 细心的读者会发现,京东和淘宝的搜索框,获取焦点时,提示文字的体验是不同的。 京东: 20180127_2000.gif 淘宝: 20180127_2005.gif 其实,**淘宝的提示文字,是用一个绝对定位的单独的标签来做的**。 京东是判断输入框是否获取焦点;淘宝是判断输入框内是否有用户输入的文字。 我们现在来实现一下。代码如下: ```html 京东:

    淘宝:

    placeholder: ``` 实现效果如下: 20180127_2010.gif 如上方所示,我们还可以用placeholder来做,但是IE678并不支持,所以不建议使用。 ### 举例3:用户注册信息错误时,输入框失去焦点后,高亮显示。 代码实现: ```html 账号:

    密码: ``` 代码解释:这次我们是在标签内调用function的,此时是先通过window调用的function。所以行内调用的时候要带this。 实现效果: 20180127_2035.gif ### 举例4:全选和反选 对应的代码如下: ```html
    菜名 饭店
    菜品1 木屋烧烤
    菜品2 蒸菜馆
    菜品3 海底捞火锅
    菜品4 面点王
    ``` 注意代码中的注释,需求2是比较难的地方,这里用到了两次for循环。第一次for循环是因为,要给每个input都要进行绑定事件。 实现的效果如下: 20180127_2320.gif ```javascript ``` ================================================ FILE: 08-前端基本功:CSS和DOM练习/04-DOM操作练习:Tab栏切换(通过className设置样式).md ================================================ --- title: 04-DOM操作练习:Tab栏切换(通过className设置样式) publish: true --- 京东网页上,可以看到下面这种tab栏的切换: ![](http://img.smyhvae.com/20180128_1750.gif) 我们把模型抽象出来,实现一下。 ## 举例引入:鼠标悬停时,current元素的背景变色 > 本段我们先举一个例子,因为这里用到了**排他思想**(先干掉 all,然后保留我一个)。对于理解tab切换,很有帮助。 完整的代码实现: ```html ``` 代码解释: 鼠标悬停时,current栏变色,这里用到了排他思想:先把所有按钮的className设置为空,然后把我(this)这个按钮的className设置为current,就可以达到变色的效果。核心代码是: ```javascript //排他思想:先把所有按钮的className设置为空,然后把我(this)这个按钮的className设置为current //排他思想和for循环连用 for(var j=0;j
    • 鞋子
    • 袜子
    • 帽子
    • 裤子
    • 裙子
    鞋子 袜子 帽子 裤子 裙子
    ``` 实现效果如下: ![](http://img.smyhvae.com/20180128_1610.gif) 上方代码的核心部分是: ```javascript for(var i=0;i
    • 鞋子
    • 袜子
    • 帽子
    • 裤子
    • 裙子
    鞋子 袜子 帽子 裤子 裙子
    • 鞋子
    • 袜子
    • 帽子
    • 裤子
    • 裙子
    鞋子 袜子 帽子 裤子 裙子
    • 鞋子
    • 袜子
    • 帽子
    • 裤子
    • 裙子
    鞋子 袜子 帽子 裤子 裙子
    ``` 注意,通过函数fn的封装之后,我们是通过参数element获取元素,而不再是document了。这样可以达到“抽象性”的作用,各个tab栏之间相互独立。 上方代码中,我们是通过给 liArr[i]一个index属性:` liArr[i].index = i`,然后让下方的span对应的index也随之对应显示:`spanArr[this.index].className = "show"`。 这样做比较难理解,根据上一段的规律,当然还有一种容易理解的方法是:**给liArr[i]增加index属性时,通过attribute的方式**,因为这种方式增加的属性是可以显示在标签上的。也就有了下面的方式二。 ### 方式二:通过attribute设置index的值 基于上面方式一中的代码,我们修改一下js部分的代码,其他部分的代码保持不变。js部分的代码如下: ```html ``` 不过,方式一的写法应该比方式二更常见。 **总结**:通过函数封装的形式,可以保证各个tab栏之间的切换互不打扰。最终实现效果如下: ![](http://img.smyhvae.com/20180128_1651.gif) ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 08-前端基本功:CSS和DOM练习/05-DOM操作练习:访问关系的封装.md ================================================ --- title: 05-DOM操作练习:访问关系的封装 publish: true --- ## 访问关系的函数封装 (1)函数封装 新建一个文件名叫`tools.js`,然后在里面封装访问关系。代码如下。 tools.js: ```javascript /** * Created by smyhvae on 2018/01/28. */ function getEle(id){ return document.getElementById(id); } /** * 功能:给定元素查找他的第一个元素子节点,并返回 * @param ele * @returns {Element|*|Node} */ function getFirstNode(ele){ var node = ele.firstElementChild || ele.firstChild; return node; } /** * 功能:给定元素查找他的最后一个元素子节点,并返回 * @param ele * @returns {Element|*|Node} */ function getLastNode(ele){ return ele.lastElementChild || ele.lastChild; } /** * 功能:给定元素查找他的下一个元素兄弟节点,并返回 * @param ele * @returns {Element|*|Node} */ function getNextNode(ele){ return ele.nextElementSibling || ele.nextSibling; } /** * 功能:给定元素查找他的上一个兄弟元素节点,并返回 * @param ele * @returns {Element|*|Node} */ function getPrevNode(ele){ return ele.previousElementSibling || ele.previousSibling; } /** * 功能:给定元素和索引值查找指定索引值的兄弟元素节点,并返回 * @param ele 元素节点 * @param index 索引值 * @returns {*|HTMLElement} */ function getEleOfIndex(ele,index){ return ele.parentNode.children[index]; } /** * 功能:给定元素查找他的所有兄弟元素,并返回数组 * @param ele * @returns {Array} */ function getAllSiblings(ele){ //定义一个新数组,装所有的兄弟元素,将来返回 var newArr = []; var arr = ele.parentNode.children; for(var i=0;i
    ``` 注意:上方代码中,我们引用到了`tools.js`这个工具类。 ``` ``` ``` ``` ``` ``` ================================================ FILE: 08-前端基本功:CSS和DOM练习/07-DOM操作练习:innerHTML的方式创建元素.md ================================================ --- title: 07-DOM操作练习:innerHTML的方式创建元素 publish: true --- ## 动态创建DOM元素的三种方式 - `document.write();` 不常用,因为容易覆盖原来的页面。 - `innerHTML = ();` 用的比较多。绑定属性和内容比较方便。(节点套节点) - `document.createElement();` 用得也比较多,指定数量的时候一般用它。 **1、方式一:** ```javascript document.write(); ``` 这种方式的好处是:比较随意,想创建就创建,可以直接在`write`里写属性都行。但是会把原来的标签给覆盖掉。所以不建议。 举例: ```html Title
      smyhvae
    ``` 效果如下: ![](http://img.smyhvae.com/20180129_1908.png) **方式二:**innerHTML 举例如下: ```html Title
      smyhvae
    ``` 注意,上方代码中,是用是用符号`+=`,不是`=`,前者是在原来的基础之上增加,后者是替换。 效果如下: ![](http://img.smyhvae.com/20180129_2017.png) **3、方式三:**利用DOM的api创建 利用DOM的api创建节点,有很多种: 比如: - createElement() - appendChild() - removeChild() - insertBefore() - replaceChild() 这个我们在上一篇文章的`DOM节点的操作`这一段中已经讲到了。 ```html ``` ## innerHTML举例:在线用户的获取 现在要做下面这样一个页面: ![](http://img.smyhvae.com/20180129_2151.png) 上图的意思是,每次刷新页面,都从服务器获取最新的在线人数的名单(我们先用本地的数组来模拟服务器上的数据)。 它的结构是:div > ul > li。每一个li就是一个头像。 如果在本地直接添加几个头像的话,代码是: ```html //往ul中添加li元素以及li元素中的内容 ul.innerHTML += '
  • '+ '千古壹号'+ '

    千古壹号

    '+ '
  • '; ul.innerHTML += '
  • '+ '千古壹号'+ '

    千古壹号

    '+ '
  • '; ul.innerHTML += '
  • '+ '千古壹号'+ '

    千古壹号

    '+ '
  • '; ``` 上方代码有两点比较重要: - 我们是通过`ul.innerHTML += 元素节点`的方式来不停地往ul里面加内容,比`createElement`的方式要方便。 - 元素的内容本身有双引号`"`,所以我们要用单引号`'`进行字符串的连接。 但是,当我们从服务器获取在线用户的时候,头像和用户的昵称是动态变化的,所以每个字符串要用变量进行表示: ```html ul.innerHTML += '
  • '+ ''+users[i].name+''+ '

    '+users[i].name+'

    '+ '
  • '; ``` 这里我们暂时用本地的数组来代表服务器的数据,最终的完整版代码如下: ```html
    当前在线用户
    ``` 工程文件:[2018-02-01-获取在线用户列表demo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-01-%E8%8E%B7%E5%8F%96%E5%9C%A8%E7%BA%BF%E7%94%A8%E6%88%B7%E5%88%97%E8%A1%A8demo.rar) ## innerHTML举例2:模拟百度搜索的下方提示 要求实现的效果如下: ![](http://img.smyhvae.com/20180201_2030.gif) 在这之前,我们先实现这样一个例子:**判断字符串以某个字符串为开头**。 **判断字符串是否以某个字符串为开头:** ```javascript var str = "smyhvae"; //判断str是否以sm为开头?(给定字符串,然后他的索引值为0) var num = str.indexOf("sm"); //只有返回值为0,那么字符串才是以参数为开头 //如果在任何位置都查询不到参数,则返回值为-1; ``` 代码解释:我们可以通过`indexOf("参数")`来实现。如果返回的索引值为0,说明字符串就是以这个参数开头的。 为了实现上方gif图的搜索功能,完整版代码如下: ```html
    ``` ## 动态操作表格 方式1: ``` createElement() ``` 方式2: - rows (只读,table和textarea能用) - insertRow() (只有table能调用) - deleteRow() (只有table能调用) - cells (只读,table和textarea能用) - insertCell() (只有tr能调用) - deleteCell() (只有tr能调用) PS:括号里可以带index。用的不多。 ================================================ FILE: 09-移动Web开发/01-Bootstrap入门.md ================================================ --- title: 01-Bootstrap入门 publish: true --- ## Bootstrap 介绍 Bootstrap 是非常流行的前端框架。特点是:灵活简洁、代码优雅、美观大方。它是由Twitter的两名工程师 Mark Otto 和 Jacob Thornton 在2011年开发的。 简单来说,Bootstrap 让 Web 开发**更简单、更快捷**。使用 Bootstrap 框架并不代表我们再开发时不用自己写 CSS 样式,而是不用写绝大多数常见的样式。 PS:[Amaze UI](http://amazeui.org/) 这个框架其实跟 Bootstrap 很像。 ### 官网网站 - 官方网站: - 中文网站: V3版本: ![](http://img.smyhvae.com/20180225_1033.png) V4版本: ![](http://img.smyhvae.com/20180225_1043.png) 列举几个用 Bootstrap 做的网站: - [Bootstrap 优站精选](http://www.youzhan.org/) - - ### Bootstrap 版本 目前市面上使用的最多的是 3.x.x 版本。各个版本的介绍: 2.3.2版本: - 2013年之后,停止维护; - 支持更广泛的浏览器 - 代码不够简洁, 功能不够多。 3.x.x 版本: - 目前最新的稳定版本。 - 不支持 IE7 和早期的 Firefox - 支持 IE8,单效果不好。 2015年8月发布 4.0.0-alpha 的内部测试版。 **版本号的普及:** - alpha 版:内部测试版。α 是希腊字母的第一个,表示最早的版本,bug很多。主要是给开发和测试人员找 bug 用的。 - beta 版:公开测试版。 主要是给“部落”用户和忠实用户测试用的。bug依然很多,但比 Alpha 版要稳定。这个阶段的版本还会不断增加新功能,如果你是发烧友,可以下载这个版本。 - rc 版:候选版本(Release Candidate)。该版本不再增加新的功能。类似于最终发行版之前的预览版(发行的候选版本)。此版本的发布,预示着最终发行版即将到来。作为普通用户,如果很着急,也可以下载 rc 版。 - stable 版:稳定版。在开源软件中,都有 stable版本,这个是开源软件的最终发行版,用户可以放心大胆地使用。 ### Bootstrap 库的下载 > 这里我们以 Bootstrap V3.3.7 为例。 进入[中文官网](https://v3.bootcss.com/),下载 `用于生产环境的 Bootstrap`,如下图所示: ![](http://img.smyhvae.com/20180225_1052.png) 下载之后,解压 `bootstrap-3.3.7-dist` ,有三个文件夹: ![](http://img.smyhvae.com/20180225_1053.png) 将其拷贝到工程文件的lib文件夹下即可。 PS:`dist`表示编译之后的文件,这在库文件中是很常见的。 因为 bootstrap.js依赖jQuery,所以要先引用jquery.js 然后引用bootstrap.js。 ### Bootstrap 基础模板的介绍 [Bootstrap](https://v3.bootcss.com/getting-started/)官网提供了基本模板,如下图所示: ![](http://img.smyhvae.com/20180225_1119.png) 其完整版代码 copy 如下: ```html 我的网站

    你好,世界!

    ``` 我们需要对上面的代码进行解释。 **(1)Compatible**: ```html ``` 解释:设置浏览器的兼容模式版本。表示如果在IE浏览器下则使用最新的标准,渲染当前文档。 **(2)viewport 视口**: ```html ``` 解释:声明当前网页在移动端浏览器中展示的相关设置。我们在做移动 web 开发时,就用上面这行代码设置 viewport。 视口的作用:在移动浏览器中,当页面宽度超出设备,浏览器内部虚拟的一个页面容器,将页面容器缩放到设备这么大,然后展示。 需要注意的是: - 目前大多数手机浏览器的视口(承载页面的容器)宽度都是980; - 此属性为移动端页面视口设置,上方代码设置的值,表示在移动端页面的宽度为设备的宽度,并且不缩放(缩放级别为1)。 属性解释: - width:设置viewport的宽度。 - initial-scale:初始化缩放比例。 - minimum-scale:最小缩放比例。 - maximum-scale:最大缩放比例。 - user-scalable:是否允许用户手动缩放(值可以写成yes/no,也可以写成1/0) PS:如果设置了不允许用户缩放,那么最小缩放和最大缩放就没有意义了。二者是矛盾的。 **(3)条件注释**: ```html ``` 条件注释的作用:当判断条件满足时,就会执行注释中的HTML代码,不满足时会当做注释忽略掉。 上方代码的条件注释中,引入了两个脚本: - [html5shiv](https://github.com/aFarkas/html5shiv):让浏览器可以识别 HTML5 的新标签。如header、footer、section等。 - [respond.js](https://github.com/scottjehl/Respond):让低版本浏览器可以使用 CSS3 的媒体查询。 另外,我们还需要引入下面这个库: - [jQuery](https://github.com/jquery/jquery):Bootstrap框架中的所有 JS 组件都依赖于 jQuery 实现。 我们可以把上面这三个库文件拷贝到 lib 文件夹中(注意引用的路径要写正确)。 ## 使用 Bootstrap 搭建项目 ### 1、工程文件的目录结构 ``` ├─ Demo ·························· 项目所在目录 └─┬─ /css/ ······················· 我们自己的CSS文件 ├─ /font/ ······················ 使用到的字体文件 ├─ /img/ ······················· 使用到的图片文件 ├─ /js/ ························ 自己写的JS脚步 ├─ /lib/ ······················· 从第三方下载回来的库【只用不改】 ├─ /favicon.ico ················ 站点图标 └─ /index.html ················· 入口文件 ``` ### 2、下载并引入 Bootstrap 库文件 见上一段的讲解。引入之后,另外还需要引入 html5shiv、respond、jQuery 这三个库文件。 ### 3、字符集、Viewport设置、浏览器兼容模式 我们将 Bootstrap 的基础模板代码 copy到项目的index.html中,这其中就包括最前面的三个meta标签: ```html ``` ### 4、favicon(站点图标) ```html ``` ### 5、引入相应的第三方文件 ```html ... ``` 注意,先引入第三方的文件,再引入我们自己写的文件。 ### 6、默认字体 在我们默认的样式表中将默认字体设置为: ```css body{ font-family: "Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif; } ``` ### 7、完成页面空结构 > 先划分好页面中的大容器,然后在具体看每一个容器中单独的情况。 完整代码如下: ```html 我的网站
    ``` ## CSS 样式 全局 CSS 样式在[官网](https://v3.bootcss.com/css/)有介绍: ![](http://img.smyhvae.com/20180225_1710.png) **如果需要哪个样式,直接根据文档的指引,在相应的元素里加指定的类名即可。** 我们选部分重要的来讲一下。 ### 布局容器:container 类 截图如下: ![](http://img.smyhvae.com/20180225_1720.png) **作用**:用于定义一个固定宽度且居中的版心。只不过,这个版心的宽度具有**响应式**的效果。 也就是说,在 Bootstrap 中,我们一般用 .container 类来表示版心。 格式举例: ```html
    ``` 这个 container 类我们自己其实也可以写,通过媒体查询即可实现。 ### 栅格参数 栅格系统最主要的操作是:利用 css 的响应式去做一套行列布局的预置样式。 栅格参数如下: ![](http://img.smyhvae.com/20180225_1732.png) 我们尤其要记住各个屏幕的尺寸和**类前缀**。 ## 组件 一个按钮称之为样式;两个按钮在一起,就可以称之为组件。 组件在[官网](https://v3.bootcss.com/components/)有介绍: ![](http://img.smyhvae.com/20180225_1738.png) 我们现在需要关注的不是组件怎么用,而是里面有哪些组件,避免**重复造轮子**:别人已经做得很好了,不需要我们再重复。 ## JS 组件 JS 组件在[官网](https://v3.bootcss.com/javascript/)有介绍: ![](http://img.smyhvae.com/20180225_1750.png) 这里面包含了很多带交互的组件。比如轮播图: ![](http://img.smyhvae.com/20180225_1841.png) ## 博主提供的下载链接 空结构的工程文件的下载地址:(lib文件夹里包含了Bootstrap相关的各种库和中文文档) - [2018-02-25-BootstrapDemo及文档.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-25-BootstrapDemo%E5%8F%8A%E6%96%87%E6%A1%A3.rar) 还是那句话:**如果需要哪个样式,直接根据文档的指引,在相应的元素里加指定的类名即可。** ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 09-移动Web开发/02-Bootstrap使用.md ================================================ --- title: 02-Bootstrap使用 publish: false --- ================================================ FILE: 09-移动Web开发/03-Less详解.md ================================================ --- title: 03-Less详解 publish: true --- ## CSS 预处理器 ### 为什么要有 CSS 预处理器 **CSS基本上是设计师的工具,不是程序员的工具**。在程序员的眼里,CSS是很头痛的事情,它并不像其它程序语言,比如说PHP、Javascript等等,有自己的变量、常量、条件语句以及一些编程语法,只是一行行单纯的属性描述,写起来相当的费事,而且代码难以组织和维护。 很自然的,有人就开始在想,能不能给CSS像其他程序语言一样,加入一些编程元素,让CSS能像其他程序语言一样可以做一些预定的处理。这样一来,就有了“**CSS预处器**(CSS Preprocessor)”。 ### 什么是 CSS 预处理器 - 是 CSS 语言的**超集**,比CSS更丰满。 CSS 预处理器定义了一种新的语言,其基本思想是:**用一种专门的编程语言,为CSS增加了一些编程的特性**,将CSS作为目标生成文件,然后开发者就只要使用这种语言进行编码工作。 通俗的说,**CSS预处理器用一种专门的编程语言,进行Web页面样式设计,然后再编译成正常的CSS文件**,以供项目使用。CSS预处理器为CSS增加一些编程的特性,无需考虑浏览器的兼容性问题,例如你可以在CSS中使用变量、简单的逻辑程序、函数等等在编程语言中的一些基本特性,可以让你的CSS更加简洁、适应性更强、可读性更佳,更易于代码的维护等诸多好处。 CSS预处理器技术已经非常成熟,而且也涌现出了很多种不同的CSS预处理器语言,比如说:**Sass(SCSS)、LESS**、Stylus、Turbine、Swithch CSS、CSS Cacheer、DT CSS等。如此之多的CSS预处理器,那么“我应该选择哪种CSS预处理器?”也相应成了最近网上的一大热门话题,在Linkedin、Twitter、CSS-Trick、知乎以及各大技术论坛上,很多人为此争论不休。相比过去我们对是否应该使用CSS预处理器的话题而言,这已经是很大的进步了。 到目前为止,在众多优秀的CSS预处理器语言中就属**Sass、LESS和Stylus最优秀**,讨论的也多,对比的也多。本文将分别从他们产生的背景、安装、使用语法、异同等几个对比之处向你介绍这三款CSS预处理器语言。相信前端开发工程师会做出自己的选择——我要选择哪款CSS预处理器。 ## less 的介绍 less 是一款比较流行的**预处理 CSS**,支持变量、混合、函数、嵌套、循环等特点。 - [官网](http://lesscss.org/) - [中文网(less 文档)](http://lesscss.cn/) - [Bootstrap网站的 less 文档](https://less.bootcss.com/) - 推荐文章: ## less 的语法 ### 注释 less 的注释可以有两种。 第一种注释:模板注释 ``` // 模板注释 这里的注释转换成CSS后将会删除 ``` 因为 less 要转换为 css才能在浏览器中使用。转换成 css 之后,这种注释会被删除(毕竟 css 不识别这种注释)。 第二种注释:CSS 注释语法 ```less /* CSS 注释语法 转换为CSS后让然保留 */ ``` 总结:如果在less中写注释,我们推荐写第一种注释。除非是类似于版权等内容,就采用第二种注释。 ### 定义变量 我们可以把**重复使用或经常修改的值**定义为变量,在需要使用的地方引用这个变量即可。这样可以避免很多重复的工作量。 (1)在less文件中,定义一个变量的格式: ```less @变量名: 变量值; //格式 @bgColor: #f5f5f5; //格式举例 ``` (2)同时,在 less 文件中引用这个变量。 最终,less文件的完整版代码如下: main.less: ```less // 定义变量 @bgColor: #f5f5f5; // 引用变量 body{ background-color: @bgColor; } ``` 我们将上面的less文件编译为 css 文件后(下一段讲less文件的编译),自动生成的代码如下: main.css: ```css body{ background-color: #f5f5f5; } ``` ### 使用嵌套 在 css 中经常会用到子代选择器,效果可能是这样的: ```css .container { width: 1024px; } .container > .row { height: 100%; } .container > .row a { color: #f40; } .container > .row a:hover { color: #f50; } ``` 上面的代码嵌套了很多层,写起来很繁琐。可如果用 less 的嵌套语法来写这段代码,就比较简洁。 嵌套的举例如下: main.less: ```less .container { width: @containerWidth; > .row { height: 100%; a { color: #f40; &:hover { color: #f50; } } } div { width: 100px; .hello { background-color: #00f; } } } ``` 将上面的less文件编译为 css 文件后,自动生成的代码如下: main.css ```css .container { width: 1024px; } .container > .row { height: 100%; } .container > .row a { color: #f40; } .container > .row a:hover { color: #f50; } .container div { width: 100px; } .container div .hello { background-color: #00f; } ``` ### Mixin Mixin 的作用是把**重复的代码**放到一个类当中,每次只要引用类名,就可以引用到里面的代码了,非常方便。 (1)在 less 文件中定义一个类:(将重复的代码放到自定义的类中) ```less /* 定义一个类 */ .roundedCorners(@radius: 5px) { -moz-border-radius: @radius; -webkit-border-radius: @radius; border-radius: @radius; } ``` 上方代码中,第一行里面,括号里的内容是参数:这个参数是**缺省值**。 (2)在 less 文件中引用上面这个类: ```less #header { .roundedCorners; } #footer { .roundedCorners(10px); } ``` 上方代码中,header 中的引用没有带参数,表示参数为缺省值; footer 中的引用带了参数,那就用这个参数。 ### Import 在开发阶段,我们可以将不同的样式放到多个文件中,最后通过@import 的方式合并。意思就是,当出现多个 less 文件时,怎么引用它们。 这个很好理解, css 文件可以有很多个,less文件也可以有很多个。 (1)定义一个被引用的less文件,名为`_button1.less`: `_button1.less:` ```less .btn{ line-height: 100px; color: @btnColor; } ``` PS1:被引用的less文件,我们习惯在前面加**下划线**,表示它是**部分文件**。 PS2:`_button1.less`里可以引用`main.css`里的自定义变量。 (2)在 `main.css` 中引用上面的 `_button1.less`:(代码的第二行) main.css: ```less @btnColor: red; @import url(`_button1.less:'); //这里的路径写的是相对路径 body{ width: 1024px; } ``` 将 上面的main.less 编译为 main.css之后,自动生成的代码如下: ```css .btn { line-height: 100px; color: red; } body { width: 1024px; } ``` ### 内置函数 less 里有一些内置的函数,这里讲一下 lighten 和 darken 这两个内置函数。 main.less: ```less body { background-color: lighten(#000, 10%); // 让黑色变亮 10% color: darken(#fff, 10%); // 让白色变暗 10% } ``` 将 上面的 main.less 编译为 main.css 之后,自动生成的代码如下: main.css: ```css body { background-color: #1a1a1a; color: #e6e6e6; } ``` 如果还有什么不懂的,可以看 api 文档,在上面的第二段附上了链接。 ## 在 index.html中直接引用 less.js - 做法一:写完 less文件后,将其编译为 css 文件,然后在代码中引用css文件。 - 做法二:在代码中直接用引用 less 文件。 产品上线后,当然是使用做法一,因为做法二会多出编译的时间。 平时开发或演示demo的时候可以用做法二。 这一段,我们讲一下做法二,其实是浏览器在本地在线地把 less 文件转换为 css 文件。 (1)在 less 官网下载 less.js 文件: ![](http://img.smyhvae.com/20180226_2131.png) 把下载好的文件放在工程文件的lib文件夹里: ![](http://img.smyhvae.com/20180226_2143.png) (2)在 index.html 中引入 less.js 和我们自己写的 main.less。位置如下: ![](http://img.smyhvae.com/20180226_2145.png) copy 红框那部分的代码如下: ```html ``` 我们可以在打开的网页中,通过控制台看到效果: ![](http://img.smyhvae.com/20180226_2150.png) 注意,我们要在服务器中打开 html 文件,否则,看不到效果。 这里也告诉了我们: > 不提倡将 less 引入页面,因为 less 需要编译,因此你就需要再引入一个less.js, 多了一个HTTP 请求,同时当浏览器禁用了 js 你的样式就不起作用了,less 编译应该在服务端或使用 grunt 自动编译。 工程文件:(我引用的less.js版本是 2.5.3) - [2018-02-27-LessDemo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-27-LessDemo.rar) 参考链接: - [知乎 | less文件如何引入页面](https://www.zhihu.com/question/26075208) ## less 的编译 less 的编译指的是将写好的 less 文件 生成为 css 文件。 less 的编译,依赖于 NodeJS 环境。因此,我们需要先安装 NodeJS。 ### 1、安装 Node.js 去 [Node.js](https://nodejs.org/zh-cn/)的官网下载安装包: ![](http://img.smyhvae.com/20180226_2153.png) 一路 next 进行安装。 安装完成后,配置环境变量: 在 path 变量中追加安装路径:`;C:\Program Files\nodejs`。重启资源管理器,即可生效。 PS:我发现,我安装的 node.js v8.9.4 版本,已经自动添加了环境变量。 在 cmd 命令行,输入`node.exe -v`,可以查看 node.js 的版本。 ### 2、安装 less 的编译环境 将 [npm.zip](http://download.csdn.net/download/smyhvae/10260414) 解压,将解压后的文件拷贝到路径`C:\Users\smyhvae\AppData\Roaming\npm`下: ![](http://img.smyhvae.com/20180226_2212.png) 然后重启资源管理器(或者重启电脑)。在 cmd 中输入 `lessc`,如果能看到下面的效果,说明 less 编译环境安装成功: ![](http://img.smyhvae.com/20180226_2217.png) 如果你用的是 linux 系统,可以输入下面的命令安装: ```bash npm install -g less ``` ### 3、将 less 文件编译为 css 文件 在 less 所在的路径下,输入 `lessc xxx.less`,即可编译成功。或者,如果输入 `lessc xxx.less > ..\xx.css`,表示输出到指定路径。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 10-MySQL数据库/01-数据库的基础知识.md ================================================ --- title: 01-数据库的基础知识 publish: true --- ## 数据库的概念 **数据库**:database(DB),是一种存储数据的仓库。具有如下特性: - 数据库是根据数据结构组织、存储和管理数据。 - 数据库能够长期、高效的管理和存储数据。 - 数据库的目的就是能够存储(写)和提供(读)数据。 ## 数据库分类 数据库分为两类: - **关系型数据库**:把复杂的数据结构归结为简单的二元关系,即二维表格形式(二维表)。注重数据存储的持久性。 - **非关系型数据库**:没有具体模型的数据结构。英文简称 NoSQL(Not Only SQL ),意为"不仅仅是SQL"。注重数据读取的效率。 我们具体来看看。 ### 1、关系型数据库 **关系型数据库**:把复杂的数据结构归结为简单的二元关系,即二维表格形式(二维表)。 关系型数据库有四层结构: - 数据库管理系统(DBMS):DataBase Management System。 - 数据库(DB):数据存储的管理者。 - 数据表(Table):数据关系管理者。 - 数据字段(Field):实际数据存储者。 常见的关系型数据库产品: - 大型:Oracle - 中型:MySQL、SQL Server - 小型:Sybase、Access ### 2、非关系型数据库 **非关系型数据库**:没有具体模型的数据结构。英文简称 NoSQL(Not Only SQL ),意为"不仅仅是SQL"。 常见的非关系型数据库产品:MongoDB、Redis、Memcached。 ## SQL 的介绍 **SQL**:全称 **Structured Query Language**,译为**结构化查询语言**。 **SQL**:是一种针对关系型数据库的标准化编程语言,能够实现用户数据库的查询和程序设计。 通俗来讲,**SQL 是关系型数据库的操作指令**。 根据操作类型不同,SQL 可分为几类: * DQL:Data Query Language,数据查询语言,用于查询和检索数据 * DML:Data Manipulation Language,数据操作语言,用于数据的写操作(增删改) * DDL:Data Definition Language,数据定义语言,用于创建数据结构 * DCL:Data Control Language,数据控制语言,用于用户权限管理 * TPL:Transaction Process Language,事务处理语言,辅助DML进行事务操作(因此也归属于DML) 补充: - SQL 虽然是编程语言,但通常只用来进行数据管理,逻辑部分交给其他编程语言。 - SQL 是针对关系型数据库的**通用语言**,所有关系型数据库都是基于SQL进行数据操作;而不同的数据库产品,在 SQL 操作指令上略有差异。 ## MySQL 的介绍 ### MySQL 数据库介绍 MySQL 是很有名的 关系型数据库产品,由瑞典MySQL AB 公司开发,现在属于 Oracle 旗下产品。 MySQL 在 2008 年被 Sun 公司以10亿美金所收购,而 Sun 公司在2009年被 Oracle 甲骨文公司收购。 MySQL 开源免费。 ### MySQL 访问原理 MySQL是一种C/S结构的软件,因此我们需要安装 MySQL 的客户端来访问远程的服务端。也就是说,数据是存放在服务器上的,客户端通过执行 sql 指令来操作服务端的数据。 具体步骤是: (1)客户端通过 主机(host) + 端口号(port) 服务端。 (2)输入 username 和 password 验证身份。 (3)客户端和服务端连接成功,通过 sql 指令开始操作数据库。 ================================================ FILE: 10-MySQL数据库/02-MySQL的安装和Navicat软件使用.md ================================================ --- title: 02-MySQL的安装和Navicat软件使用 publish: true --- ## MySQL 安装 ### MySQL(Mac版) ### 步骤1、下载安装包并安装: MySQL 下载地址:https://dev.mysql.com/downloads/mysql/ ![](http://img.smyhvae.com/20200415_1707.png) ![](http://img.smyhvae.com/20200415_1708.png) #### 步骤2、配置环境变量 打开 `~/.bash_profile` 文件,在文件的末尾,添加如下内容,即可配置环境变量: ```bash # mysql export PATH=${PATH}:/usr/local/mysql/bin #快速启动、结束MySQL服务, 可以使用alias命令 alias mysqlstart='sudo /usr/local/mysql/support-files/mysql.server start' alias mysqlstop='sudo /usr/local/mysql/support-files/mysql.server stop' ``` 配置好环境变量后,在终端输入 `source ~/.bash_profile` 命令,让配置生效。 在终端的任何位置,输入如下命令,即可进入 mysql 命令的执行窗口: ```sql mysql -u root -p ``` 参考链接: - [MySQL安装(Mac版)](https://juejin.im/post/5cc2a52ce51d456e7079f27f) ### 步骤3、继续配置环境变量 在 `~/.bash_profile` 中配置好环境变量后,发现每次重启终端后,配置都会失效,需要重新执行 `source ~/.bash_profile` 命令。 原因是,zsh加载的是 `~/.zshrc`文件,而 `.zshrc` 文件中并没有定义任务环境变量。 解决办法:打开 `~/.zshrc` 文件,在文件的末尾,添加如下内容即可: ```bash source ~/.bash_profile ``` 参考链接: ## Navicat Premium 软件初体验 Navicat Premium 软件是一种数据库管理的GUI软件,采用可视化的方式来查看和操作数据库,非常方便。支持的数据库有: MySQL、MongoDB、SQL Server、SQLite、Oracle 及 PostgreSQL等。 安装好 Navicat Premium 软件之后,我们来看看这个软件是怎么用的。 ### 新建表和数据 1、新建连接: 打开 Navicat Premium 软件,选择菜单栏「文件-新建连接-mysql」,然后选择如下配置,即可在本地新建一个数据库连接: ![](http://img.smyhvae.com/20200416_1157.png) 2、选中连接后,右键新建数据库: ![](http://img.smyhvae.com/20200416_1159.png) ![](http://img.smyhvae.com/20200416_1127.png) 3、选中数据库之后,新建表 `qiangu_student_table`: ![](http://img.smyhvae.com/20200416_1138.png) 4、在表中添加字段: ![](http://img.smyhvae.com/20200416_1202.png) 5、字段建好后,开始在表中插入数据: ![](http://img.smyhvae.com/20200416_1259.png) ### 导入外部 sql 文件 在 Navicat中,选中当前 database 之后,右键选择“运行sql文件”,即可导入外部sql文件。 ================================================ FILE: 10-MySQL数据库/03-MySQL的基本操作.md ================================================ --- title: 03-MySQL的基本操作 publish: true --- ## SQL 的一些简单语法规则 ### 结束符 SQL 指令需要语句结束符,默认是英文分号`;`。 当然,还有另外两个结束符: - `\g` 与英文分号`;`等效。 - `\G`:将查到的结构旋转90度变成纵向。 ### 反引号`` SQL语句中如果用到了关键字或者保留字,需要使用反引号``(Tab键上面的符号)来包裹,让系统忽略。 ## MySQL 数据库的操作分类 根据数据库的对象层级,可以将**SQL的基础操作分为四类**: - 数据库(DB)操作。 - 数据表(Table)操作。 - 数据字段(Field)操作。 - 数据操作。 下面来详细讲一讲。 ## 一、数据库(DB)的基本操作 在终端的任何位置,输入如下命令,即可进入 mysql 命令的执行窗口: ```sql mysql -u root -p ``` ### 1、创建数据库 **语法格式**: ```mysql create database 数据库名称 [数据库选项]; ``` **数据库名称的命名规范**: - 由数字、字母和下划线组成。 - 不区分大小写。 - 不能以数字开头。 - 建议使用下划线法创建复杂的数据库名字。比如 `db_qianguyihao`。 **举例**: 创建一个名为 db_qianguyihao1 的数据库: ```mysql create database db_qianguyihao1; ``` 创建一个指定字符集的数据库: ```mysql create database db_qianguyihao2 charset utf8MB4; ``` 创建一个指定校对集的数据库: ```mysql create database db_qianguyihao3 charset utf8MB4 collate utf8mb4_general_ci ``` ### 2、查看数据库 查看有哪些数据库:(显示所有的数据库列表) ```mysql show databases; ``` 查看 `db_qianguyihao1` 这个数据库的具体创建指令是怎样的: ```mysql show create database db_qianguyihao1; ``` 备注:由于系统会加工,所以看到的结果不一定是真实的创建指令。 ### 3、使用指定的数据库 使用指定的数据库:(也可以理解成:进入指定的数据库) ```mysql # 语法格式 use database_xxx; # 举例 use db_qianguyihao; ``` 假设当前服务器连接中有很多个数据库(db_qianguyihao1、db_qianguyihao2),此时,我输入 `use db_qianguyihao2`则代表我想使用 `db_qianguyihao2` 这个数据库。 ### 4、修改数据库的参数 我们一般很少修改数据库的名称,一般是去修改数据库的一些选项,比如: - 修改字符集 - 修改校对集 **语法格式**: ```mysql alter database 数据库名称 [库选项] ``` 举例1、修改数据库的字符集为gbk: ```mysql alter database db_qianguyihao1 charset gbk; ``` 举例2、修改数据库的校对集: ```sql alter database db_qianguyihao2 charset gbk collate gbk_chinese_ci; ``` 备注:因为校对集是和字符集有关的,所以上方指令是在修改字符集的同时,修改校对集。 ### 5、删除指定的数据库 **语法格式**: ```mysql drop database 数据库名称; ``` 备注:删除数据库时,会清空当前数据库里的所有数据表,所以删除数据库的操作一定要谨慎。 ## 二、数据表(Table)的基本操作 注意,我们最好先通过 `use xxx_database` 命令进入指定的数据库(DB),然后在当前数据库下,进行数据表(Table)的操作。 ### 1、创建数据表 **语法格式**: ```sql create table [数据库名].[表名] ( 字段名1 字段类型, ... ... 字段名2 字段类型 ) 表选项; ``` **举例**: 1、在当前数据库中创建数据表 `table_qiangu1`,并新增**主键** id 字段: ```sql CREATE TABLE table_qiangu1 ( id int NOT NULL AUTO_INCREMENT PRIMARY KEY ); ``` 2、在当前数据库中创建数据表 `t_student1`,并新增 name、age这连个字段: ```sql create table t_student1( name varchar(255), age int ); ``` 3、在指定的数据库 `db_2` 中创建数据表 `t_student2`: ```sql create table db_2.t_student2( name varchar(255), age int ); ``` 4、在当前数据库中创建数据表 `t_student3`(含表选项): ```sql create table t_student3( name varchar(255), age int )engine Innodb charset utf8MB4; ``` 举例4中的代码涉及到存储引擎,这里解释一下: **存储引擎**是指数据存储和管理的方式,MySQL中提供了多种存储引擎,一般使用默认存储引擎 InnoDB。 - InnoDB:默认存储引擎;支持事务处理和外键;数据统一管理。 - MyIsam:不支持事务和外键;数据、表结构、索引独立管理;MySQL5.6以后不再维护。 6、扩展:如果想创建一个与已有表一样的数据表,MySQL提供了一种便捷的复制模式 ### 2、复制数据表 如果想创建一个与已有表一样的数据表,MySQL提供了一种便捷的**复制**模式。 **语法格式**:(复制现有的表 `table_xx1` 到 `table_xx2`) ```sql create table table_xx1 like 数据库名.table_xx2; ``` 注意,这种复制模式,`table_xx2` 只会复制表 `table_xx1` 中的字段,不会复制表`table_xx1`中的数据。 **举例**: ```sql # 在当前数据库下,复制现有的表`t_qianguyihao1` 到表 `t_qianguyihao2` create table t_qianguyihao1 like t_qianguyihao2; # 复制现有的表`t_qianguyihao1` 到表 `t_qianguyihao2`,是复制到 `db2`这个数据库中 create table t_qianguyihao1 like db2.t_qianguyihao2; ``` ### 3、显示数据表的名称 在当前数据库下,显示所有的数据表: ```sql show tables; ``` 在指定的数据库中,显示所有的数据表: ```sql show tables from db_qianguyihao1; ``` 显示数据表的创建指令:(查看 `t_qianguyihao1` 这个数据表的具体创建指令是怎样的) ```mysql show create table t_qianguyihao1; # 备注:由于系统会加工,所以看到的结果不一定是真实的创建指令。 ``` ### 4、查询(查找)数据表的名称 > 根据 表名称 查询数据表,也可以理解成:按条件显示部分数据表。 根据数据表的**表名称**查找数据表时,需要用到关键词`like`,而且还要涉及到两个符号: - `%` 表示匹配任意**多个字符**。 - `_` 表示匹配任意**一个字符**(固定位置)。 上面这两个模糊查询的符号,大家要牢记。我们来看看具体的例子。 语法举例: ```mysql show tables like '%like_'; # _表示匹配一个字符(固定位置),%表示匹配N个字符 ``` **`%` 符号举例**: ```sql # 查询表名称中,包含 “qiangu” 这个关键字的表(“qiangu”这个关键字的前后可能都有内容) show tables like '%qiangu%'; # 查询表名称以“qiangu”开头的表(这个命令应该很实用) show tables like 'qiangu%'; ``` **`_`符号举例**: ```sql # 根据 表名称 来查询表,查询条件是:表名称以“qiangu”开头,而且要确保 qiangu 的后面有三个字符(因为我在 qiangu 的后面写了三个下划线)。 show tables like 'qiangu___'; ``` ### 5、desc:查看数据表的表结构 查看数据表的表结构,就是**查看这张表中定义了哪些字段**,以及这些字段是如何定义的。通过这种方式,我们可以清晰地了解数据的存储形式。 项目开发中,领导在检查我们的工作时,首先看的就是我们的表中定义了哪些字段。所以说,这种方式,还是很实用的。 **语法格式**: ```sql # 方式1 desc 表名称; # 方式2 describe 表名称; # 方式3 show columns from 表名称; ``` 上面的三种方式,效果都一样,三选一即可。 ### 6、修改数据表的表名称和表选项 **修改数据表的表名称**: 在当前数据库下,修改数据表的表名称: ```sql rename table 原表名 to 新表名; ``` 指定某个数据库,然后修改数据表的表名称: ```sql rename table 数据库名.原表名 to 数据库名.新表名; ``` **修改数据表的表名称**: ```sql alter table table1 charset gbk; ``` ### 7、删除数据表 语法格式: ```sql drop table 数据表名称; ``` ## 三、字段(Field)的基本操作 数据表 table 创建好了之后,我们就可以开始在这张表中新增字段了。 ### 1、新增字段 **语法格式**: ```sql alter table 表名 add [column] 字段名 字段类型 [字段属性] [字段位置]; ``` 注意事项: - 新增字段时,必须制指定字段类型。 - [column]、 [字段属性]、[字段位置] 这几个都是选填,其他是必填。 - 追加字段时,这个字段的顺序默认排在最后。 **举例**: 新增字段 `name`: ```sql alter table table_qiangu1 add name varchar(255); ``` 新增字段 `age`: ```sql alter table table_qiangu1 add age int; ``` ### 2、新增字段时,设置字段的位置(顺序) 在新增字段时,它的顺序是默认放在最后面的,当然,我们也可以人工指定它的顺序。 在修改字段的位置时,我们可以用到下面这两个关键字: - `first` 放到最前面 - `after` 放到某个字段的后面 **语法格式**: ```sql alter table 表名 add 新字段名 字段类型 字段位置; ``` **举例1**: 在 `name`字段的后面,新增一个 `sex` 字段: ```sql alter table t_qiangu1 add sex varchar(255) default null comment '性别' after name; ``` 注意,上方举例中,如果是新建 varchar 类型的字段,一定要指定 varchar 的长度(比如255),否则报错。 **举例2**: 新增一个 `id` 字段,放到最前面: ```sql alter table t_qiangu1 add id int first; ``` ### 3、change:修改现有字段的字段名 > 修改现有字段的字段名,是通过 change 关键字,不是通过 modify 关键字(后者会报错,执行失败)。 **语法格式**: ```sql # 格式1(精简版) alter table 表名 change 原字段名 新字段名 字段类型; # 格式2(完整版) alter table 表名 change 原字段名 新字段名 字段类型 [字段属性] [位置]; ``` 注意: - 修改字段名时,一定要设置新字段的字段类型。 - 虽然 change 关键字也可以修改现有字段的字段属性、字段位置,但我们一般是通过 modify 关键字来做(下面会讲)。 **举例**: 修改字段名 `sex` 为 `sexy`: ```sql alter table t_qiangu2 change sex sexy varchar(255); ``` ### 4、modify:修改现有字段的的字段类型、字段位置、字段属性 **语法格式**: ```sql alter table 表名 modify 现有字段的字段名 现有字段的字段类型 [字段属性] [位置]; ``` **举例1**、针对现有的字段 `name` 和 `age`,更换这两个字段的顺序: ```sql # 注意,这里的 age 后面一定要跟上它的字段类型,否则执行失败 alter table t_qiangu1 modify age int after name; ``` ### 修改字段的默认值 ```sql # 若本身存在默认值,则先删除 alter table 表名 alter column 字段名 drop default; # 若本身不存在则可以直接设定 alter table 表名 alter column 字段名 set default 默认值; ``` ### 5、删除字段 > 删除字段的同时,会删除字段对应的数据。删除字段的操作不可逆,请谨慎操作。 语法格式: ```sql alter table 表名 drop 字段名; ``` 举例:(删除 t_qiangu1 表中的 age 这个字段) ```sql alter table t_qiangu1 drop age; ``` ## 四、数据的基本操作 ### 1、新增数据 **方式1、全字段插入**: 语法格式: ```sql insert into 表名 values(值1, 值2, ... 最后一个值); ``` 解释: - 值的顺序必须与所有字段的顺序一致。 - 值的数据类型也必须与字段定义的数据类型一致。 举例(给 t_qiangu1 这个表中插入一条完整的数据): ```sql insert into t_qiangu1 values(3, 'qianguyihao', 28); ``` **方式1、部分字段插入**: 语法格式: ```sql insert into 表名 (字段1, 字段2, 字段3) values(值1, 值2, 值3); ``` 解释: -字段的顺序可以随意,但值的顺序必须要与前面的字段顺序**一一对应**,数据类型也要一致。 举例(给 t_qiangu1 这个表中的指定字段插入数据): ```sql insert into t_qiangu1 (id, name) values(4, 'xusong'); ``` ### 2、查询数据 > 查询数据的操作,占sql日常操作的95%以上。 **语法格式**: ```sql select xxx from 表名; ``` **举例**: 查询表中的所有数据: ```sql select * from t_qiangu1; ``` 查询表中 name、age 这两个字段的数据: ```sql select name, age from t_qiangu2; ``` 查询表中 id=2 的数据: ```sql select * from t_qiangu3 where id = 2; ``` ### 3、修改数据 **语法格式**: ```sql update 表名 set (字段1 = 新值1, 字段2 = 新值2) [where 条件筛选]; ``` **解释**: - 我们通常是结合 where 条件语句来修改数据。 - **修改数据之前,要先保证表里面有数据**。如果这张表是空表,那么,执行这个命令后,等于没执行。 **举例**: 将表中,name 这个字段的值全部修改为`千古壹号`: ```sql update t_qiangu1 set name = '千古壹号'; ``` id = 3 的这条记录中,修改 name 和 age 这两个字段的值: ```sql update t_qiangu1 set name = '许嵩', age = '34' where id = 3; ``` ### 4、删除数据 > 删除字段的操作不可逆,请谨慎操作。 **语法格式**: ```sql delete from 表名 [where 条件]; ``` **解释**: - 执行删除操作之后,匹配到的**整条记录**,都会删除。 - **删除数据之前,要先保证表里面有数据**。如果这张表是空表,那么,执行这个命令后,等于没执行。 **举例**: 删除表中`id = 2`的记录: ```sql delete from t_qiangu1 where id = 2; ``` ================================================ FILE: 10-MySQL数据库/04-MySQL字段的数据类型.md ================================================ --- title: 04-MySQL字段的数据类型 publish: true --- ## 前言 MySQL 中的字段,主要有四种数据类型: - 整型(整数) - 小数 - 字符串类型 - 时间日期类型 下面来详细讲一讲。 ## 整数类型 ### 整数类型的分类 MySQL中,整型有五种: * 迷你整型:tinyint,使用**1个字节**存储整数,最多存储256个整数(-128~127)。 * 短整型:smallint,使用**2个字节**存储整数。 * 中整型:mediumint,使用**3个字节**存储整数。 * 标准整型:int,使用**4个字节**存储整数。 * 大整型:bigint,使用**8个字节**存储。 **强调**: (1)如果你不知道用哪一种,或者懒得计算,那就用标准整型 `int`吧,这个用的最多。 (2)整型在 MySQL 中默认是有符号的,即有正负;无符号需要使用 `unsigned` 修饰整型,代表正整数。 **举例**: 在指定的表中新增 age 字段,要求 age 是正整数: ```sql alter table table_qiangu1 add age int unsigned; ``` ### 设计思路 如果需要新建整型的字段,设计思路如下: (1)确定需要存储的数据是整数。 (2)预估整数的范围,选择合适的整数类型。 (3)确定这个整数是否需要包含负数。 ### 整数类型的取值范围 todo。参考链接: ### 整数类型的显示宽度、零填充 > 我们在很多设计表中,可能会看到比如 `int(11)`这种数据类型,这里面的 `11`代表的就是`显示宽度`。 所谓的**显示宽度**,其实就是显示的时候,看到的最少数字个数。 比如 int(2) ,表示不管你的数值是多少,最少可以看到两个数字。假如你存的数值是9,没有满两位,就会在前面补零,显示为`09`;假如你的数值是120,超过了显示宽度,则直接显示原始值,不会做**零填充**。 **显示宽度的注意事项**: - 显示宽度只适用于 MySQL 的整数类型。 - 显示宽度只是指明 MySQL 整数类型最少显示的数字个数(可以通过desc查看表字段显示)。 - **显示宽度只是在显示的时候改变数值的样式,不会对原本的值进行更改**。 - 显示宽度和数值类型的取值范围无关。例如int(10) 他的取值范围依然是(-2 147 483 648,2 147 483 647)。 **零填充的注意事项**: - 要想让显示宽度自动进行**零填充**,必须要配合 `ZEROFILL`这个关键字一起使用。 - **零填充只能针对正整数**,也就是说,`ZEROFILL` 要求整型为无符号型。 **举例**: 1、新建一张表,然后在这张表中新增 num1 字段,要求 num1 显示3位,不够3位的自动进行零填充: ```sql # 新建一张表 CREATE TABLE table_qiangu1 ( id int NOT NULL AUTO_INCREMENT PRIMARY KEY ); # 显示宽度有效(正确写法) alter table table_qiangu1 add num1 int(3) zerofill; # 对比:普通写法,显示宽度无效 alter table table_qiangu1 add num2 int(3); # 对比:普通写法 alter table table_qiangu1 add num3 int; ``` 上述命令中,如果把 `zerofill` 这个关键字去掉,是达不到显示宽度的效果的。执行完上述命令后,我们执行 `desc table_qiangu1` 命令,对比一下 num1、num2、num3 的字段结构就知道了: ![](https://img.smyhvae.com/20200423_1050.png) 上方截图可以看到,只有 num1 才有显示宽度,它可以进行零填充,num2、num3不行。我们往表中插入整数 `6`,然后看看显示结果,就一目了然: ![](https://img.smyhvae.com/20200423_1055.png) 参考链接:[MySql数据库 数值类型的显示宽度](https://juejin.im/post/5b24a2c251882574d73c6f82) ## 小数 MySQL 中的小数分为两大类: 浮点型的数据分为两种: - 单精度:float,使用4个字节存储,精度范围为6-7位有效数字。 - 双精度:double,使用8个字节存储,精度范围为14-15位有效数字。 注意: - 浮点数超过精度范围会自动进行四舍五入。 - 精度可以指定整数和小数部分。比如 ================================================ FILE: 10-MySQL数据库/05-MySQL数据库的常用命令.md ================================================ --- title: 05-MySQL数据库的常用命令 publish: true --- ## MySQL 的一些简单命令 我们可以在 Navicat Premium 软件中,创建数据库和表,然后输入查询命令来查询数据。选择菜单栏「查询->新建查询->输入 sql 命令->运行」即可,效果如下: ![](https://img.smyhvae.com/20200417_1750.png) 我们还可以直接在终端输入命令行来操作。 注意,在 Mac 终端执行 sql 命令时,命令的末尾必须加上`;`(英文格式的分号)。效果如下: ![](https://img.smyhvae.com/20200417_1700.png) MySQL 命令行的一些简单命令如下。 **以 root 身份进入命令行**: ``` mysql -u root -p ``` **查看有哪些数据库**: ```sql show databases; ``` **选择进入指定的数据库**: ```sql use xxx_database; # 举例 use qianguyihao_database; ``` **在当前数据库中,查看有哪些表**: ```sql show tables; ``` **在当前数据库中,查询指定表的全部数据**: ```sql SELECT * FROM xxx_table; # 举例 SELECT * FROM qianguyihao_student_table ``` **删除指定的表**: ```sql drop table xxx; # 举例 drop table qianguyihao_student_table; ``` **删除指定的数据库**: ```sql drop database qianguyihao_student_table; ``` **创建一个数据库**: ```sql create database qianguyihao_database2; ``` ## where 条件查询 使用 `where` 子句可以对表中的数据进行筛选,结果为 true 的行会出现在查询结果中。 语法格式如下: ```sql SELECT * FROM 表名 where 条件; ``` 上面的语法格式中,`条件` 具体要怎么写呢?这个可能有很多种情况。我们继续往下看。 ### 比较运算符 - `=` 等于 - `>` 大于 - `>=` 大于等于 - `<` 小于 - `<=` 小于等于 - `!=`:不等于 - `age > 20`:查询 age 大于 30 的数据 **举例**: ```sql # 查询 age 大于 20 的数据 SELECT * FROM qianguyihao_table WHERE age > 20; ``` ### 逻辑运算符 - AND - OR - NOT **举例**: ```sql # 查询 age 在20至30之间的数据 SELECT * FROM qianguyihao_table WHERE age BETWEEN 20 AND 30; ``` ### 范围查询 - `in` 表示在一个非连续的范围内。 - `between ... and ...` 表示在一个连续的范围内 举例: ```sql # 查询 name 为 千古壹号 或者 许嵩的数据 SELECT * FROM qianguyihao_table WHERE name IN ['千古壹号', '许嵩']; SELECT * FROM qianguyihao_table WHERE age BETWEEN 20 AND 30; ``` ### 模糊查询 - `like` - `%` 表示任意多个任意字符 - `_` 表示一个任意字符 `%` 符号举例: ```sql # 查询标题中包含“前端”这两个字的数据(“前端”这两个字的前后可能都有内容) select * from qianguyihao_table where `title` like "%前端%"; # 查询标题以“前端”开头的数据 select * from qianguyihao_table where `title` like "前端%"; ``` `_`符号举例: ```sql # 查询标题,查询条件是:标题中至少有五个字符,而且,这五个字符中,前两个字符一定是“千古”开头的。 SELECT * FROM qianguyihao_table WHERE `title` LIKE "千古___"; ``` ### NULL 的判断 - `is null` 判断为空 - `is not null` 判断为非空 注意,`is null` 和**空字符串**`""` 是有区别的。学过 js 基础之后,你应该知道:空字符串并非 null,只不过是里面的值为空而已;空字符串也是会占有内存空间的。 举例: ```sql select * from qianguyihao_table where name is not NULL; ``` ## join 联表查询 ### 联表查询命令 - `tableA inner join tableB`:表 A 与表 B 匹配的行会出现在结果中。 - `tableA left join tableB`:表 A 与表 B 匹配的行会出现在结果中。表 A 中独有的数据,对应表 B 中用 null 填充。 - `tableA right join tableB`:表 A 与表 B 匹配的行会出现在结果中。表 B 中独有的数据,对应表 A 中用 null 填充。 光是这样看,不好理解,我们来举个例子。 ### 举例 现在有下面这两张表:作者表 author、图书表 book。 **表 1**、作者表 author: | id | authorId | authorName | | :-- | :------- | :--------- | | 1 | 11 | 王小波 | | 2 | 12 | 吴军 | | 3 | 88 | 千古壹号 | **表 2**、图书表 book: | id | bookId | bookName | authorId | | :-- | :----- | :--------- | -------- | | 1 | 201 | 黄金时代 | 11 | | 2 | 202 | 白银时代 | 11 | | 3 | 203 | 青铜时代 | 11 | | 4 | 204 | 浪潮之巅 | 12 | | 5 | 205 | 硅谷之谜 | 12 | | 6 | 206 | 数学之美 | 12 | | 7 | 777 | 设计心理学 | 99 | 注意,表2中的每本图书都有对应的 authorId,这个 authorId 就是对应表1中的 authorId。**通过 authorId 把两张表关联起来**。 通过联表查询上面的两张表,我们来对比一下查询结果。 **情况 0**:(inner join) ```sql SELECT * FROM author INNER JOIN book; ``` 查询结果: ![](https://img.smyhvae.com/20200418_2300.png) 上面这种查询,没有意义,因为没有加任何查询条件。 **情况 1**:(inner join) ```sql SELECT * FROM author INNER JOIN book ON author.authorId = book.authorId; ``` 查询结果: ![](https://img.smyhvae.com/20200418_2305.png) 上面这行命令,跟下面这行命令等价: ```sql SELECT * FROM author, book WHERE author.authorId = book.authorId; ``` **情况 2**:(left join) ```sql SELECT * FROM author LEFT JOIN book ON author.authorId = book.authorId; ``` 查询结果: ![](https://img.smyhvae.com/20200418_2310.png) **情况 3**:(right join) ```sql SELECT * FROM author RIGHT JOIN book ON author.authorId = book.authorId; ``` 查询结果: ![](https://img.smyhvae.com/20200418_2315.png) ### 参考链接 - [Mysql 联表查询](https://blog.csdn.net/qmhball/article/details/8000003) ## 自关联查询 涉及到层级关系时可以用自关联查询。 ## 子查询 当一个查询结果是另一个查询的条件时,这个查询称之为子查询。 ================================================ FILE: 10-MySQL数据库/MySQL设计三大范式.md ================================================ --- title: 01-Bootstrap入门 publish: false --- ## 前言 范式即规范。MySQL 范式的作用是: - 让我们建的表更佳简洁和高效。 - 让功能独立化,避免耦合。 ## MySQL 设计三大范式 ### 第一范式(1NF):原子性 表的每一列具有原子性,不可再分。 ### 第二范式:唯一性 第二范式是建立在第一范式基础上的;外要求所有非主键字段必须完全依赖主键,而不是部分依赖。 ### 第三范式 第三范式是建立在第二范式基础上的;且要求没有传递依赖。 ## 参考链接 - [MySql--数据库设计三范式](https://www.jianshu.com/p/3e97c2a1687b) ================================================ FILE: 10-MySQL数据库/事务.md ================================================ --- title: 01-Bootstrap入门 publish: false --- ## 事务语句 - 开启:begin - 提交:commit - 回滚:rollback ================================================ FILE: 11-Node.js/01-Node.js介绍.md ================================================ --- title: 01-Node.js介绍 publish: true --- ## todo - rpc 和 Node.js 的关系 - [《吊打面试官》系列 Node.js 全栈秒杀系统](https://mp.weixin.qq.com/s/uWeAsJ-P253je15A49uKIQ) ## 前言 Node 的重要性已经不言而喻,很多互联网公司都已经有大量的高性能系统运行在 Node 之上。Node 凭借其单线程、异步等举措实现了极高的性能基准。此外,目前最为流行的 Web 开发模式是前后端分离的形式,即前端开发者与后端开发者在自己喜欢的 IDE 上独立进行开发,然后通过 HTTP 或是 RPC 等方式实现数据与流程的交互。这种开发模式在 Node 的强大功能的引领下变得越来越高效,也越来越受到各个互联网公司的青睐。 ### 前端同学为什么要学习后端/后端同学为什么要学习前端 - 了解前后端交互流程。 - 前端同学能够和后台开发的程序员更佳紧密地结合、更顺畅地沟通。 - 当网站的业务逻辑需要前置时,前端人员需要学习一些后台开发的技术,以完成相应的任务;;反过来也一样。 - 拓宽知识视野和技术栈,能够站在全局的角度审视整个项目。 ### 前端同学为什么要学 Node.js 1、Node.js 使用 JavaScript 语言开发服务器端应用,**便于前端同学上手**(一些公司甚至要求前端工程师掌握 Node.js 开发)。 2、实现了前后端的语法统一,**有利于和前端代码整合**,甚至共用部分代码。 比如说,针对接口返回的各种字段,前后端都必须要做校验。此时,如果用 Node.js 来做后台开发的话,前后端可以共用校验的代码。 3、Node.js 性能高、生态系统活跃,提供了大量的开源库。 4、Jeff Atwood 在 2007 年提出了著名的 Atwood 定律:**任何能够用 JavaScript 实现的应用系统,最终都必将用 JavaScript 实现**。 Jeff Atwood 是谁不重要(他是 Stack Overflow 网站的联合创始人),重要的是这条定律。 ### 后端同学为什么要学 Node.js 因为前端同学在学 Node.js。 ## 什么是 Node.js ### 官方定义 [Node.js](https://nodejs.org/zh-cn/) 是一个基于 **Chrome V8 引擎**的 JavaScript 运行环境。Node.js 使用了一个**事件驱动**、**非阻塞式 I/O**的模型,使其轻量又高效。Node.js 的包管理工具 npm 是全球最大的开源库生态系统。 Node.js 不是一门语言,也不是 JavaScript 的框架,也不是像Nginx一样的Web服务器 ,**Node.js 是 JavaScript 在服务器端的运行环境(平台)**。 ### Node.js 的组成 在 Node.js 里运行 JavaScript,跟在 Chrome 里运行 JavaScript 有什么不同? 二者采用的是同样的 JS 引擎。在 Node.js 里写 JS,和在前端写 JS,几乎没有不同。在写法上的区别在于:Node.js 没有浏览器、页面标签相关的 API,但是新增了一些 Node.js 相关的 API。通俗来说,对于开发者而言,在前端写 JS 是用于控制浏览器;而 Node.js 环境写 JS 可以控制整个计算机。 我们知道,JavaScript 的组成分为三个部分: - ECMAScript - DOM:标签元素相关的API - BOM:浏览器相关的API ECMAScript 是 JS 的语法;DOM 和 BOM 浏览器端为 JS 提供的 API。 而 Node.js 的组成分为: - **ECMAScript**。ECMAScript 的所有语法在 Node 环境中都可以使用。 - **Node 环境**提供的一些**附加 API**(包括文件、网络等相关的 API)。 如下图所示: ![](http://img.smyhvae.com/20200409_1545.png) ### 补充 与 PHP、JSP、Python、Perl、Ruby 的“既是语言,也是平台”不同,Node.js 的使用 JavaScript 进行编程,运行在 Chrome 的 V8 引擎上。 与 PHP、JSP 等相比(PHP、JSP、.net 都需要运行在服务器程序上,Apache、Nginx、Tomcat、IIS。 ),Node.js 跳过了 Apache、Nginx、IIS 等 HTTP 服务器,它自己不用建设在任何服务器软件之上。Node.js 的许多设计理念与经典架构(LAMP = Linux + Apache + MySQL + PHP)有着很大的不同,可以提供强大的伸缩能力。Node.js 没有 web 容器。 JS 语言非常灵活,使得它在严谨性方面不如 Java 等传统的静态语言。JS 是一门动态语言,而且融合了面向对象和函数式编程这两种编程范式。 随着 ES6、ES7 等 JS 语法规范的出现,以及浏览器对这些规范的支持,使得我们可以用更为现代化的 JS 语言特性,来编写现代化的应用。 ## Node.js 的架构和依赖 Node.js 的架构如下: ![](http://img.smyhvae.com/20180301_1540.png) Node.js 内部采用 Google Chrome 的 V8 引擎,作为 JavaScript 语言解释器;同时结合自行开发的 libuv 库,**扩展了 JS 在后端的能力(比如 I/O 操作、文件读写、数据库操作等)**。使得 JS 既可以在前端进行 DOM 操作(浏览器前端),又可以在后端调用操作系统资源,是目前最简单的全栈式语言。 其次,Node 生态系统活跃,提供了大量的开源库,使得 JavaScript 语言能与操作系统进行更多的交互。 ### Node.js 运行环境的核心:V8 引擎 和 libuv 库 Node.js 是 JavaScript 在服务器端的运行环境,在这个意义上,Node.js 的地位其实就是 JavaScript 在服务器端的虚拟机,类似于 Java 语言中的 Java 虚拟机。 - [V8 引擎](https://v8.dev/) :编译和执行 JS 代码、管理内存、垃圾回收。V8 给 JS 提供了运行环境,可以说是 JS 的虚拟机。V8 引擎本身是用 C++ 写的。 - [libuv](https://zh.wikipedia.org/wiki/Libuv): libuv 是一个专注于异步 I/O 的跨平台类库,目前主要在 Node.js 上使用。它是 Node.js 最初的作者 Ryan Dahl 为 Node.js 写的底层类库,也可以称之为虚拟机。libuv 本身是用 C 写的。 ### Java 虚拟机和 V8 引擎,是由同一人开发 Chrome 浏览器成功的背后,离不开 JS 的 V8 引擎。作为虚拟机,V8 的性能表现优异,它的开发者是 Lars Bak。在 Lars 的工作履历里,绝大部分都是与虚拟机相关的工作。在开发 V8 之前,他曾经在 Sun 公司工作,担任 HotSpot 团队的技术领导,主要致力于开发高性能的 Java 虚拟机。在这之前,他也曾为 Self、Smalltalk 语言开发过高性能虚拟机。这些无与伦比的经历让 V8 一出世就超越了当时所有的 JS 虚拟机。 ![](http://img.smyhvae.com/20200617_1120.png) V8 的性能优势使得用 JavaScript 写高性能后台服务程序成为可能。在这样的契机下,Ryan Dahl 选择了 JavaScript,选择了 V8,在事件驱动、非阻塞 I/O 模型的设计下实现了 Node。 ### V8 的内存限制 在一般的后端开发语言中,在基本的内存使用上没有什么限制,然而在 Node 中通过 JavaScript 使用内存时就会发现只能使用部分内存(64 位系统下约为 1.4GB,32 位系统下约为 0.7GB)。在这样的限制下,将会导致 Node 无法直接操作大内存对象。 造成这个问题的主要原因在于 Node 基于 V8 构建,所以在 Node 中使用的 JavaScript 对象基本上都是通过 V8 自己的方式来进行分配和管理的。V8 的这套内存管理机制在浏览器的应用场景下使用起来绰绰有余,足以胜任前端页面中的所有需求。但在 Node 中,这却限制了开发者随心所欲使用大内存的想法。 ## Node 的发展历史 - 2008 年左右,随着 Ajax 的逐渐普及,Web 开发逐渐走向复杂化,系统化; - Node.js 诞生于 2009 年,由 Joyent 的员工 Ryan Dahl 开发而成。2009 年 5 月,Ryan Dahl 在 GitHub 中开源了 Node 的最初版本,同年 11 月,在 JSConf 大会上展示了 Node 项目; - 2010 年 1 月,NPM 包管理工具诞生,使得程序员能够更方便地发布和分享 Node.js 的第三方库。 - Node.js 最初只支持 Linux 和 Mac OS 操作系统。2011 年 7 月,微软参与合作,Node.js 终于支持了 Windows 平台。PS:不过,node 的生产环境基本是在 Linux 下。 - 目前官网最新版本已经更新到 14.x.x 版本,最新稳定的是 12.18.0。 据 Node.js 创始人 Ryan Dahl 回忆,他最初希望采用 Ruby,但是 Ruby 的虚拟机效率不行。 注意:是 Node 选择了 JavaScript,不是 JavaScript 发展出来了一个 Node。 ## Node.js 的应用 Node.js 拥有强大的开发者社区,现在已经发展出比较成熟的技术体系,以及庞大的生态。它被广泛地应用在 Web 服务、开发工作流、客户端应用等诸多领域。其中,在 **Web 服务**领域,业界对 Node.js 的接受程度最高。 ### 1、BFF 中间层 BFF,即 Backend For Frontend(服务于前端的后端)。玉伯在《[从前端技术进化到体验科技](https://mp.weixin.qq.com/s/IYddaaw2ps1wR2VT1dZWPg)》这篇文章中点出了 BFF 层的概念: > BFF 模式下,整体分工很清晰,**后端通过 Java/C++ 等语言负责服务实现,理想情况下给前端提供的是基于领域模型的 RPC 接口,前端则在 BFF 层直接调用服务端 RPC 接口拿到数据**,按需加工消费数据,并实现人机交互。基于 BFF 模式的研发,很适合拥有前端技术背景的全栈型工程师。这种模式的好处很明显,后端可以专注于业务领域,更多从领域模型的视角去思考问题,页面视角的数据则交给前端型全栈工程师去搞定。**领域模型与页面数据是两种思维模式,通过 BFF 可以很好地解耦开,让彼此更专业高效**。 在 Web 服务里,搭建一个中间层,前端访问中间层的接口,中间层再访问后台的 Java/C++ 服务。这类服务的特点是不需要太强的服务器运算能力,但对程序的灵活性有较高的要求。这两个特点,正好和 Node.js 的优势相吻合。Node.js 非常适合用来做 BFF 层,优势如下: - 对于前端来说:让前端**有能力自由组装后台数据**,这样可以减少大量的业务沟通成本,加快业务的迭代速度;并且,前端同学能够**自主决定**与后台的通讯方式。 - 对于后台和运维来说,好处是:安全性(不会把主服务器暴露在外面)、降低主服务器的复杂度等。 ### 2、服务端渲染 **客户端渲染**(CSR / Client side render):前端通过一大堆接口请求数据,然后通过 JS 动态处理和生成页面结构和展示。优点是**前后端分离**、减小服务器压力、局部刷新。缺点是不利于 SEO(如果你的页面然后通过 Ajax 异步获取内容,抓取工具并不会等待异步完成后再行抓取页面内容)、首屏渲染慢。 **服务端渲染**(SSR / Server Side Render):服务器返回的不是接口数据,而是一整个页面(或整个楼层)的 HTML 字符串,浏览器直接显示即可。也就是说,在服务器端直接就渲染好了,然后一次性打包返回给前端。优点是**有利于 SEO、首屏渲染很快**。 **总结: 搜索引擎优化 + 首屏速度优化 = 服务端渲染**。 备注:这里的「服务端渲染」只是让 Node.js 做中间层,不会替代后端的,后台同学请放心。 参考链接: - [Vue 服务端渲染的概念](https://ssr.vuejs.org/zh/) - - - [方应杭](https://www.zhihu.com/question/59578433/answer/326694511) 历史回顾: (1)一开始,页面很简单,html 是后端渲染的(比如PHP、ASP、JSP等方式)。后端发现页面中的 js 好麻烦(虽然简单,但是坑多),于是让公司招聘专门写 js 的人,简称「前端切图仔」。 (2)随着 Node.js 和前端 MVC 的兴起,以及前端越来越复杂,慢慢演变成了「前后端分离」。 (3)前端的 SPA 应用流行之后,发现 SEO 问题很大,而且首屏渲染速度很慢,但是自己选的路再难走也要走下去,于是用 Node.js 在服务端渲染被看成是一条出路。 (4)以前在一起的时候,是后端做部分前端的工作;现在在一起的时候,是前端做部分后端的工作。 ### 3、做小型服务、小型网站的后端(基于 Express、Koa 框架) 现在很多公司的后台管理系统,都是用 Node.js 来开发接口,毕竟,后台管理系统对性能和并发的要求不是太高。有了 Node.js 之后,通过 JS 直接操作 DB,做增删改查,生成接口,极大降低了前端同学的学习门槛。 当然,有时候做 Node.js 开发,是因为:后台人力不够,所以把后台开发的一部分工作量,转移给前端同学。 ### 4、做项目构建工具 前端正在广泛使用的构建工具 gulp、Webpack,就是基于 Node.js 来实现的。 ### 5、 做 PC 客户端软件(基于 Electron 框架) Electron 框架就是基于 Node.js 的,可以用来开发客户端软件。 Electron 原名为 Atom Shell,是由 GitHub 开发的一个开源框架。Electron 以 Node.js 作为运行时(runtime),以 chromium 作为渲染引擎,使开发者可以使用 JS 这种前端技术栈开来发跨平台的桌面GUI应用程序。 有一点你可能会感到惊讶:程序员们都在用的代码编辑器 VS Code 软件, 就是基于 Electron 框架来开发的。其他使用 Electron 进行开发的知名应用还有:Skype、GitHub Desktop、Slack、WhatsApp等。 还有一个例子是:电子游戏直播网站 [Twitch](https://www.twitch.tv/),号称是国外游戏直播的鼻祖,它在 PC 端的客户端软件,就是用 Electron 框架的。你会发现,Twitch 的网站视觉,和 PC 端的视觉,几乎是一样的。如果两端都采用 JS 语言,就可以极大的复用现有的工程。 ### 知名度较高的 Node.js 开源项目 ![](http://img.smyhvae.com/20180301_2009.png) - express:Node.js 中著名的 web 服务框架。 - Koa:下一代的 Node.js 的 Web 服务框架。所谓的“下一代”是相对于 Express 而言的。 - [Egg](https://eggjs.org/zh-cn/):2016 年,阿里巴巴研发了知名的 Egg.js 开源项目,号称企业级 Web 服务框架。Egg.js 是基于 Koa 开发的。 * mocha:是现在最流行的 JavaScript 测试框架,在浏览器和 Node 环境都可以使用。 * PM2:node 多进程管理。 * jade:非常优秀的模板引擎,不仅限于 js 语言。 * CoffeeScript:用简洁的方式展示 JavaScript 优秀的部分。 * Atom:编辑器。 * VS Code:最酷炫的编辑器。 * socket.io:实时通信框架。 ### 总结 或许,能用 Node.js 做的后台应用,Java/C++ 也能做;但是 Node.js 可以让我们多一种选择。 短期来看,Node.js 很难像 Java/C++ 那样,成为后台的主力开发语言。这并非是因为 Node.js 的性能问题,主要是因为,Node.js 还比较年轻,经验积累太少,框架的支持度不够。搞企业级服务,Node.js 敌不过 Java/C++,所以目前只能搞「轻量级」;但未来可期。 限制语言能力的不是语言本身,而是生态。 ## 最后一段:前端同学会 Node.js 就真的全栈了吗? 一个人的精力是有限的,既擅长前端、又精通后端的人,毕竟是极少数。 林肯说过:“你可以在所有的时间欺骗一部分人,也可以在一段时间欺骗所有的人,但你不可能在所有的时间欺骗所有的人”。 同样的,我也说过:“你可以在这一段时间擅长前端技术,也可以在另一段时间擅长后台技术,但你不可能在**同一段时间**同时擅长前端和后台,更不可能在**所有的时间**同时擅长前端和后台。” 所谓的全栈,只是一个伪命题。个人不一定需要全栈,企业和项目也不强制要求全栈,分工协作,才最高效。 对于个人而言,虽然全栈很难,但是 Node.js 的出现,**让 JS 语言实现了前后端语法的统一,让 JS 语言的技术栈更佳全面**。 涉及到后台开发相关的技术,无论如何,也绕不开**框架设计、开发调试、数据库操作、高并发处理、大规模存储、性能优化、容灾方案、RPC 调用、进程管理、操作系统调度、网络安全、系统运维、日常维护、甚至是 Linux 内核、驱动开发**等过硬的知识技能和经验积累。等你亲身经历过这些,才算明白:语言只是一种工具。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 11-Node.js/02-Node.js的特点.md ================================================ --- title: 02-Node.js的特点 publish: true --- ## Node.js 的特点 - 异步、非阻塞 IO 模型 - 事件循环 - 单线程 - 总结:轻量和高效 Node.js 的性能和效率非常高。 传统的 Java 语言是一个请求开启一个线程,当请求处理完毕后就关闭这个线程。而 Node.js 则完全没有采用这种模型,它本质上就是一个单线程。 你可能会疑问:一个线程如何服务于大量的请求、如何处理高并发的呢?这是因为,Node.js 采用的是异步的、非阻塞的模型。 这里所谓的“单线程”,指的是 Node 的主线程只有一个。为了确保主线程不被阻塞,主线程是用于接收客户端请求。但不会处理具体的任务。而 Node 的背后还有一个线程池,线程池会处理长时间运行的任务(比如 IO 操作、网络操作)。线程池里的任务是通过队列和事件循环的机制来执行。 ## 使用 Node.js 时的劣势 - 程序运行不稳定,可能会出现服务不可用的情况 - 程序运行效率较低,每秒的请求数维持在一个较低的水平 - 前端同学对服务器端的技术不太熟悉。 ================================================ FILE: 11-Node.js/03-Node.js开发环境安装.md ================================================ --- title: 03-Node.js开发环境安装 publish: true --- ## Node.js 版本常识 - 偶数版本为稳定版(0.6.x ,8.10.x) - 奇数版本为非稳定版(0.7.x ,9.11.x) - LTS(Long Term Support) 参考链接:[node.js 中 LTS 和 Current 的区别](https://blog.csdn.net/u012532033/article/details/73332099) ## Node.js 运行环境配置:通过 Node.js 安装包(不推荐) 去 Node.js 的[官网](https://nodejs.org/en/)下载安装包: ![](http://img.smyhvae.com/20180301_1505.png) 我们也可以在 里下载历史版本。 ![](http://img.smyhvae.com/20180301_1507.png) 注意,我们以一定要用偶数版(V4、V6 等),不要用奇数版(比如 V5),因为奇数版不稳定。 后续如果需要安装其他版本,可以这样做:重新下载最新的安装包,覆盖安装即可。 但我们并不推荐直接采用 Node.js.msi(windows)或者 Node.js.pkg(Mac) 安装包进行安装,因为会产生如下问题。 **通过 Node.js 安装包产生的问题**: - 安装新版本时,需要覆盖就版本;而且以前版本安装的很多全局工具包,需要重新安装。 - 无法回滚到之前的旧版本。 - 无法在多个版本之间切换(很多时候,不同的项目需要使用特定版本。或者,我想临时尝鲜一下新版本的特性) 因此,我们暂时先不用安装 Node.js,稍后用 NVM 的方式来安装 Node.js。通过 NVM 的方式,可以让多个版本的 Node.js 共存,并灵活切换。 ## Node.js 运行环境安装:通过 NVM(推荐) **[NVM](https://github.com/nvm-sh/nvm)**:node.js version manager,用来管理 node 的版本。 **我们可以先安装 NVM,然后通过 NVM 安装 Node.js**。这是官方推荐的做法。 Windows 和 Mac 下安装的 Node.js 的步骤如下。 ### Windows 系统安装 Node.js **1、安装 NVM**: (1)我们去 下载 NVM 的安装包: ![](http://img.smyhvae.com/20180301_1603.png) 下载下来后,直接解压到 `D:\web`目录下: ![](http://img.smyhvae.com/20180301_1610.png) (2)在上面的目录中,新建一个`settings.txt`文件,里面的内容填充如下: ```bash root: D:\web\nvm path: D:\web\nodejs arch: 64 proxy ``` 上方内容的解释: - root 配置为:当前 nvm.exe 所在的目录 - path 配置为:node 快捷方式所在的目录 - arch 配置为:当前操作系统的位数(32/64) - proxy 不用配置 (3)配置环境变量: - `NVM_HOME` = `D:\web\nvm`(当前 nvm.exe 所在目录) - `NVM_SYMLINK` = `D:\web\nodejs` (node 快捷方式所在的目录) - PATH += `;%NVM_HOME%;%NVM_SYMLINK%` 配置成功后,重启资源管理器。 **2、验证:**(在 cmd 命令行中输入命令) (1)输入`nvm`命令查看环境变量是否配置成功: ![](http://img.smyhvae.com/20180301_1645.png) (2)输入 `nvm ls`,查看已安装的所有 node 版本。 (3)输入 `nvm -v`,查看 已安装的 nvm 版本。 (4)输入 `node -v`,查看正在使用的 node 版本。 如果 Node 安装失败,可以参考上面这个链接。 **3、安装指定版本的 Node.js**: ```bash nvm install 版本号 # 安装举例 nvm install 12.20.0 # 使用该版本 nvm use 12.20.0 ``` 输入 `node -v`,查看当前使用的 node 版本。 关于 NVM 的常用命令,详见下一段。 补充: 如果 Node 安装失败,可以在上方的 `settings.txt`文件中,新增如下两行,修改镜像源: ``` node_mirror: https://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.taobao.org/mirrors/npm/ ``` - 参考链接:[安装 npm,nvm,node](https://segmentfault.com/a/1190000011114680) ### Mac 系统安装 Node.js **1、安装 [NVM](https://github.com/nvm-sh/nvm)**: (1)打开 终端.app,输入: ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash ``` 安装成功的界面: ![](http://img.smyhvae.com/20180302_2126.png) 完成后,nvm 就被安装在了`~/.nvm`下。我们可以点开 home目录,然后按快捷键「Cmd + Shift + .」,看看 `.nvm`这个文件夹在不在。 问题1、如果安装时提示 `Failed to connect to raw.githubusercontent.com port 443: Connection refused`。解决办法:我们直接访问 https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh ,将安装文件保存到本地,然后`bash install.sh` 执行这个本地文件,即可安装成功。 问题2、如果发现安装失败: ![](http://img.smyhvae.com/20180302_2111.png) 原因:Xcode 软件进行过更新。解决办法:打开 Xcode 软件,同意相关内容即可。 (2)配置环境变量: 编辑器打开`~/.bash_profile`文件,如果不会就在命令行输入`open ~/.bash_profile`。 (补充:如果你的 Mac 电脑里找不到`~/.bash_profile`文件,那就找找有没有`~/.profile`文件,或者`~/.bashrc`文件,或者`~/.zshrc`文件。如果还是没有,那你就手动创建一个`~/.bash_profile`文件)。 在最后一行输入: ```bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm ``` 如果你发现文件中已经存在了上面这行代码,就不用往里面加了。这一步的作用是每次新打开一个 bash,nvm 都会被自动添加到环境变量中。 最后,输入 `source ~/.bash_profile`重启环境变量的配置。 PS:NVM 现在已经不支持 Homebrew 的方式来安装了。 参考链接: 参考链接: **2、验证:**(在 终端命令行中输入命令) (1)输入 `nvm` 命令查看环境变量是否配置成功。 (2)输入 `nvm ls`,查看已安装的所有 node 版本。 (3)输入 `nvm -v`,查看 已安装的 nvm 版本。 (4)输入 `node -v`,查看正在使用的 node 版本。 **3、安装指定版本的 Node.js**: 和 Windows 下一样,也是执行如下命令: ```bash nvm install 版本号 # 举例 nvm install 12.18.0 ``` 网速有点慢,要稍等。 ![](http://img.smyhvae.com/20180302_2148.png) 输入 `node -v`,查看当前使用的 node 版本。 安装好 `Node` 之后,`npm` 也会自动安装的,输入 `npm -v`,查看 npm 的版本。 关于 NVM 的常用命令,详见下一段。 **4、让 `.bash_profile` 环境变量永久生效**: 在 `~/.bash_profile` 中配置好环境变量后,发现每次重启终端后,配置都会失效,需要重新执行 `source ~/.bash_profile` 命令。 原因是,zsh加载的是 `~/.zshrc`文件,而 `.zshrc` 文件中并没有定义任务环境变量。 解决办法:打开 `~/.zshrc` 文件,在文件的末尾,添加如下内容即可: ```bash source ~/.bash_profile ``` ## NVM 的常用命令 > 注意,这一段说的是 NVM 的常用命令,不是 Node 的常用命令。 查看当前使用的 nvm 版本: ```bash nvm --version ``` 查看本地安装的所有的 Node.js 版本: ```bash # 方式1 nvm ls # 方式2 nvm list ``` **安装指定版本的 Node.js:** ```bash nvm install 版本号 # 举例 nvm install 8.10.0 ``` 卸载指定版本 Node.js: ```bash nvm uninstall 版本号 ``` **切换使用指定版本的 node**: ```bash nvm use 版本号 ``` **设置node的默认版本**: ```bash nvm alias default 版本号 ``` **查看全局npm包的安装路径**: ``` npm root -g ``` 查看远程服务器端的所有 Node 版本: ```bash nvm ls-remote ``` 执行上面的命令后,在列出的版本清单中,凡是用 `Latest LTS`标注的版本,则表明是**长期维护**的版本。我们在安装时,建议安装这些版本。当然,我们也可以在网址 查看 LTS 的历史版本。 ## Node.js 的常用命令 查看 node 的版本: ```bash $ node -v ``` 执行脚本字符串: ```bash $ node -e 'console.log("Hello World")' ``` 运行脚本文件: ```bash $ node index.js $ node path/index.js $ node path/index ``` 查看帮助: ```bash $ node --help ``` **进入 REPL 环境:** ```bash $ node ``` REPL 的全称:Read、Eval、 Print、Loop。类似于浏览器的控制台。 ![](http://img.smyhvae.com/20180301_1900.png) 如果要退出 REPL 环境,可以输入`.exit` 或 `process.exit()`。 在 VS Code 里,我们可以在菜单栏选择“帮助->切换开发人员工具”,打开 console 控制台。 ## 包和 NPM ### 什么是包 由于 Node 是一套轻内核的平台,虽然提供了一系列的内置模块,但是不足以满足开发者的需求,于是乎出现了包(package)的概念: 与核心模块类似,就是将一些预先设计好的功能或者说 API 封装到一个文件夹,提供给开发者使用。 Node 本身并没有太多的功能性 API,所以市面上涌现出大量的第三方人员开发出来的 Package。 ### 包的加载机制 如果 Node 中自带的包和第三方的包名冲突了,该怎么处理呢?原则是: - 先在系统核心(优先级最高)的模块中找; - 然后到当前项目中 node_modules 目录中找。 比如说: ```javascript requiere(`fs`); ``` 那加载的肯定是系统的包。所以,我们尽量不要创建一些和现有的包重名的包。 ### NPM 的概念 **NPM**:Node Package Manager。官方链接: Node.js 发展到现在,已经形成了一个非常庞大的生态圈。包的生态圈一旦繁荣起来,就必须有工具去来管理这些包。NPM 应运而生。 举个例子,当我们在使用 Java 语言做开发时,需要用到 JDK 提供的内置库,以及第三方库。同样,在使用 JS 做开发时,我们可以使用 NPM 包管理器,方便地使用成熟的、优秀的第三方框架,融合到我们自己的项目中,极大地加速日常开发的构建过程。 随着时间的发展,NPM 出现了两层概念: - 一层含义是 Node 的开放式模块登记和管理系统,亦可以说是一个生态圈,一个社区。 - 另一层含义是 Node 默认的模块管理器,是一个命令行下的软件,用来安装和管理 Node 模块。 ### NPM 的安装(不需要单独安装) NPM 不需要单独安装。默认在安装 Node 的时候,会连带一起安装 NPM: ![](http://img.smyhvae.com/20180302_1105.png) NVM、Node、NPM 安装之后,目录分布如下: ![](http://img.smyhvae.com/20180302_1134.png) ![](http://img.smyhvae.com/20180302_1137.png) ![](http://img.smyhvae.com/20180302_1138.png) 输入 `npm -v`,查看 npm 的版本: ![](http://img.smyhvae.com/20180302_1139.png) 如果上方命令无效,可能是之前的 node 并没有完全安装成功。解决办法: 另外,Node 附带的 NPM 可能不是最新版本,可以用下面的命令,更新到最新版本: ```bash $ npm install npm -g ``` ### 配置 NPM 的全局目录(暂略) NPM 默认安装到当前正在使用 Node 版本所在目录下。我们建议重新配置 NPM 的全局目录。 输入`npm config ls`,查看: ![](http://img.smyhvae.com/20180302_1210.png) ### NPM包的版本管理 NPM包的管理都是通过项目根目录的 `package.json`文件实现。 当你使用 npm 安装一个包或者更新一个包的时候,package.json 里会自动添加**包名和包的版本**。npm 默认安装**符合条件**的最新版本,然后在版本号之前添加`^`符号。 NPM包的版本号,是用三位数表示。版本号前面的符号,代表开发者想要更新的的最新版本条件: - 符号`^`:固定第一位数。表示主版本固定的情况下,可更新至最新版。例如 `vue: "^2.6.0"` 表示 2.6.0及其以上的2.x.x 都是满足的。 - 符号`~`:固定前两位数。表示次版本固定的情况下,可更新至最新版。例如 `vuex: "~2.6.0"`,2.6.0及其以上的2.6.x都是满足的。 - 无符号:三位数都固定。无符号表示固定版本号。例如 `vue: "2.6.0"`,此时一定是安装`2.6.0`版本。 参考链接: - [请将你的npm依赖版本锁定](https://juejin.cn/post/6960928446826741796) ## NPM 的常用命令 查看 npm 当前版本: ```bash npm -v ``` 更新 npm: ```bash npm install npm@latest -g ``` 项目初始化:(执行完成后,会生成`package.json`文件) ```bash npm init # 快速跳过问答式界面,选择默认配置 npm init --yes ``` 只在当前工程下安装指定的包: ```bash npm install [package] ``` 在全局安装指定的包: ``` npm install -g [package] ``` 安装的包只用于开发环境,不用于生产环境:(会出现在 package.json 文件中的 devDependencies 属性中) ```bash npm install [package] --save-dev # 或者 npm install [package] -D ``` 安装的包需要发布到生产环境:(会出现在 package.json 文件中的 dependencies 属性中) ```bash npm install [package] --save # 或者 npm install [package] -S ``` 查看当前目录下已安装的 node 包: ```bash npm list ``` 查看全局已经安装的 node 包: ```bash npm list -g ``` 查看 npm 帮助命令: ```bash npm --help ``` 查看指定命令的帮助: ```bash npm [指定命令] --help ``` 更新指定的包: ```bash npm update [package] ``` 卸载指定的包: ```bash npm uninstall [package] ``` 查看配置信息: ```bash npm config list ``` 查看本地安装的指定包的信息,没有则显示 empty: ```bash npm ls [package] ``` 查看全局安装的指定包的信息,没有则显示 empty: ```bash npm ls [package] -g ``` 查看远程 npm 上指定包的所有版本信息: ```bash npm info [package] ``` 查看当前包的安装路径: ```bash npm root ``` 查看全局包的安装路径: ```bash npm root -g ``` ## 配置 npm 镜像源 由于 npm 默认的下载地址在国外(npmjs.com),有时候会被墙,导致无法下载或者下载很慢。因此,我们可以尝试切换成,从其他的镜像源下载 npm 包。 切换镜像源,有下面这几种方式: - 方式 1:临时切换镜像源。 - 方式 2:切换镜像源 - 方式 3:通过 NRM 切换镜像源(最为推荐的方式)。 - 方式 4:cnpm。 下面来分别讲一下。 ### 方式 1:临时切换镜像源 安装指定包的时候,通过追加 `--registry`参数即可。格式如下: ```bash # 格式 npm install [package] --registry [https://xxx] # 举例:在下载安装 express 这个包的时候,临时指定镜像源为 https://registry.npm.taobao.org npm install express --registry https://registry.npm.taobao.org ``` ### 方式 2:切换镜像源 ```bash npm config set registry https://registry.npm.taobao.org ``` 执行上述命令后,以后下载所有 npm 包的时候,都会改为使用淘宝的镜像源。 ### 方式 3:通过 NRM 切换镜像源(推荐) **NRM**:Node Registry Manager。作用是:**切换和管理 npm 包的镜像源**。 - 项目地址: - GitHub 地址: **安装 NRM**: ```bash npm install -g nrm ``` ![](http://img.smyhvae.com/20180302_1208.png) **NRM 的常用命令:** ```bash # 显示全部的镜像 nrm ls # 使用淘宝的镜像 nrm use taobao ``` 效果如下: ![](http://img.smyhvae.com/20180302_1215.png) 推荐的国内加速镜像淘宝: ## 方式 4:安装 cnpm - 项目地址: 安装`cnpm`替换 npm(npm 由于源服务器在国外,下载包的速度较慢,cnpm 会使用国内镜像): ```bash npm install -g cnpm --registry=https://registry.npm.taobao.org ``` ![](http://img.smyhvae.com/20180302_2204.png) 以后我们就可以通过 cnpm 命令去安装一个包。举例如下: ```bash # 安装 vue 这个包 cnpm install vue ``` 这里的单词 `install` 可以简写成 `i`。 ## Node.js 的简单使用 我们可以输入`node`命令,然后在里面写 js 的代码。 或者,也可以 通过 node 运行 指定的 js 文件。比如,编写好一个 js 文件`01.js`,然后在命令行输入: ```bash node 01.js ``` 就可以执行这个 js 程序,直接在命令行查看运行结果。 ## 赞赏作者 创作不易,你的赞赏和认可,是我更新的最大动力: ![](https://img.smyhvae.com/20220401_1800.jpg) ================================================ FILE: 11-Node.js/04-Node.js模块化规范:CommonJS.md ================================================ --- title: 04-Node.js模块化规范:CommonJS publish: true --- ## 前言 网站越来越复杂,js代码、js文件也越来越多,会遇到**一些问题**: - 文件依赖 - 全局污染、命名冲突 程序模块化包括: - 日期模块 - 数学计算模块 - 日志模块 - 登陆认证模块 - 报表展示模块等。 所有这些模块共同组成了程序软件系统。 一次编写,多次使用,才是提高效率的核心。 ## 模块化的理解 ### 什么是模块化 **概念**:将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并组合在一起。 模块的内部数据、实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。 最早的时候,我们会把所有的代码都写在一个js文件里,那么,耦合性会很高(关联性强),不利于维护;而且会造成全局污染,很容易命名冲突。 ### 模块化的好处 - 避免命名冲突,减少命名空间污染 - 降低耦合性;更好地分离、按需加载 - **高复用性**:代码方便重用,别人开发的模块直接拿过来就可以使用,不需要重复开发类似的功能。 - **高可维护性**:软件的声明周期中最长的阶段其实并不是开发阶段,而是维护阶段,需求变更比较频繁。使用模块化的开发,方式更容易维护。 - 部署方便 ## 模块化规范 ### 模块化规范的引入 假设我们引入模块化,首先可能会想到的思路是:在一个文件中引入多个js文件。如下: ```html ``` 但是这样做会带来很多问题: - 请求过多:引入十个js文件,就有十次http请求。 - 依赖模糊:不同的js文件可能会相互依赖,如果改其中的一个文件,另外一个文件可能会报错。 以上两点,最终导致:**难以维护**。 于是,这就引入了模块化规范。 ### 模块化的概念解读 模块化起源于 Node.js。Node.js 中把很多 js 打包成 package,需要的时候直接通过 require 的方式进行调用(CommonJS),这就是模块化的方式。 那如何把这种模块化思维应用到前端来呢?这就产生了两种伟大的 js:RequireJS 和 SeaJS。 ### 模块化规范 服务器端规范: - [**CommonJS规范**](http://www.commonjs.org/):是 Node.js 使用的模块化规范。 CommonJS 就是一套约定标准,不是技术。用于约定我们的代码应该是怎样的一种结构。 浏览器端规范: - [**AMD规范**](https://github.com/amdjs/amdjs-api):是 **[RequireJS](http://requirejs.org/)** 在推广过程中对模块化定义的规范化产出。 ``` - 异步加载模块; - 依赖前置、提前执行:require([`foo`,`bar`],function(foo,bar){}); //也就是说把所有的包都 require 成功,再继续执行代码。 - define 定义模块:define([`require`,`foo`],function(){return}); ``` - **[CMD规范]()**:是 **[SeaJS](http://seajs.org/)** 在推广过程中对模块化定义的规范化产出。淘宝团队开发。 ``` 同步加载模块; 依赖就近,延迟执行:require(./a) 直接引入。或者Require.async 异步引入。 //依赖就近:执行到这一部分的时候,再去加载对应的文件。 define 定义模块, export 导出:define(function(require, export, module){}); ``` PS:面试时,经常会问AMD 和 CMD 的区别。 另外,还有ES6规范:import & export。 这篇文章,我们来讲一下`CommonJS`,它是 Node.js 使用的模块化规范。 ## CommonJS 的基本语法 ### CommonJS 的介绍 [CommonJS](http://www.commonjs.org/):是 Node.js 使用的模块化规范。也就是说,Node.js 就是基于 CommonJS 这种模块化规范来编写的。 CommonJS 规范规定:每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口对象。加载某个模块,其实是加载该模块的 module.exports 对象。 在 CommonJS 中,每个文件都可以当作一个模块: - 在服务器端:模块的加载是运行时同步加载的。 - 在浏览器端: 模块需要提前编译打包处理。首先,既然同步的,很容易引起阻塞;其次,浏览器不认识`require`语法,因此,需要提前编译打包。 ### 模块的暴露和引入 Node.js 中只有模块级作用域,两个模块之间的变量、方法,默认是互不冲突,互不影响,这样就导致一个问题:模块 A 要怎样使用模块B中的变量&方法呢?这就需要通过 `exports` 关键字来实现。 Node.js中,每个模块都有一个 exports 接口对象,我们可以把公共的变量、方法挂载到这个接口对象中,其他的模块才可以使用。 接下来详细讲一讲模块的暴露、模块的引入。 ### 暴露模块的方式一: exports `exports`对象用来导出当前模块的公共方法或属性。别的模块通过 require 函数调用当前模块时,得到的就是当前模块的 exports 对象。 **语法格式**: ```js // 相当于是:给 exports 对象添加属性 exports.xxx = value ``` 这个 value 可以是任意的数据类型。 **注意**:暴露的关键词是`exports`,不是`export`。其实,这里的 exports 类似于 ES6 中的 export 的用法,都是用来导出一个指定名字的对象。 **代码举例**: ```js const name = 'qianguyihao'; const foo = function (value) { return value * 2; }; exports.name = name; exports.foo = foo; ``` ### 暴露模块的方式二: module.exports `module.exports`用来导出一个默认对象,没有指定对象名。 语法格式: ```javascript // 方式一:导出整个 exports 对象 module.exports = value; // 方式二:给 exports 对象添加属性 module.exports.xxx = value; ``` 这个 value 可以是任意的数据类型。 代码举例: ```js // 方式1 module.exports = { name: '我是 module1', foo(){ console.log(this.name); } } // 我们不能再继续写 module.exports = value2。因为重新赋值,会把 exports 对象 之前的赋值覆盖掉。 // 方式2 const age = 28; module.exports.age = age; ``` `module.exports` 还可以修改模块的原始导出对象。比如当前模块原本导出的是一个对象,我们可以通过 module.exports 修改为导出一个函数。如下: ```js module.exports = function () { console.log('hello world') } ``` ### exports 和 module.exports 的区别 最重要的区别: - 使用exports时,只能单个设置属性 `exports.a = a;` - 使用module.exports时,既单个设置属性 `module.exports.a`,也可以整个赋值 `module.exports = obj`。 其他要点: - Node中每个模块的最后,都会执行 `return: module.exports`。 - Node中每个模块都会把 `module.exports`指向的对象赋值给一个变量 `exports`,也就是说 `exports = module.exports`。 - `module.exports = XXX`,表示当前模块导出一个单一成员,结果就是XXX。 - 如果需要导出多个成员,则必须使用 `exports.add = XXX; exports.foo = XXX`。或者使用 `module.exports.add = XXX; module.export.foo = XXX`。 ### 问题: 暴露的模块到底是谁? **答案**:暴露的本质是`exports`对象。【重要】 比如,方式一的 `exports.a = a` 可以理解成是,**给 exports 对象添加属性**。方式二的 `module.exports = a`可以理解成是给整个 exports 对象赋值。方式二的 `module.exports.c = c`可以理解成是给 exports 对象添加属性。 Node.js 中每个模块都有一个 module 对象,module 对象中的有一个 exports 属性称之为**接口对象**。我们需要把模块之间公共的方法或属性挂载在这个接口对象中,方便其他的模块使用。 ### 引入模块的方式:require require函数用来在一个模块中引入另外一个模块。传入模块名,返回模块导出对象。 **语法格式**: ```js const module1 = require('模块名'); ``` 解释: - 内置模块:require的是**包名**。 - 下载的第三方模块:require的是**包名**。 - 自定义模块:require的是**文件路径**。文件路径既可以用绝对路径,也可以用相对路径。后缀名`.js`可以省略。 **代码举例**: ```js const module1 = require('./main.js'); const module2 = require('./main'); const module3 = require('Demo/src/main.js'); ``` **require()函数的两个作用**: - 执行导入的模块中的代码。 - 返回导入模块中的接口对象。 ### 主模块 主模块是整个程序执行的入口,可以调度其他模块。 ```bash # 运行main.js启动程序。此时,main.js就是主模块 $ node main.js ``` ### 模块的初始化 一个模块中的 JS 代码仅在模块**第一次被使用时**执行一次,并且在使用的过程中进行初始化,然后会被缓存起来,便于后续继续使用。 代码举例: (1)calModule.js: ```js var a = 1; ​ function add () { return ++a; } ​ exports.add = add; ``` (2)main.js:(在 main.js 中引入 hello.js 模块) ```js var addModule1 = require('./calModule') var addModule2 = require('./calModule') ​ console.log(addModule1.add()); console.log(addModule2.add()); ``` 在命令行执行 `node main.js` 运行程序,打印结果: ```bash 2 3 ``` 从打印结果中可以看出,`calModule.js`这个模块虽然被引用了两次,但只初始化了一次。 ## CommonJS 在服务器端的实现举例 ### 1、初始化项目 在工程文件中新建如下目录和文件: ``` modules | module1.js | module2.js | module3.js app.js ``` 然后在根目录下新建如下命令: ``` npm init ``` 然后根据提示,依次输入如下内容: - **包名**:可以自己起包名,也可以用默认的包名。注意,包名里不能有中文,不能有大写。 - **版本**:可以用默认的版本 1.0.0,也可以自己修改包名。 其他的参数,一路回车即可。效果如下: ![](http://img.smyhvae.com/20180410_1425.png) 于是,根目录下会自动生成`package.json`这个文件。点进去看一下: ```json { "name": "commonjs_node", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "smyhvae", "license": "ISC" } ``` ### 2、导入第三方包 `uniq`这个第三方包的作用是保证唯一性(我们拿它来举例)。我们在当前工程目录下,输入如下命令进行安装: ``` npm install uniq ``` 安装成功后,根目录下会自动生成相应的文件: ![](http://img.smyhvae.com/20180410_1450.png) 需要说明的是,我的node版本是 v8.10.0(v8以上),对应的 npm 版本是 v5.6.0,版本比较高,因此,当我输入完`npm install uniq`之后,`package.json`中就会自动添加`uniq`包的依赖: ![](http://img.smyhvae.com/20180410_1855.png) 如果有些童鞋的npm版本较低,就需要手动去添加依赖;另一种方式是,可以使用`npm install uniq --save`命令,这个多出来的`--save`就可以自动添加依赖。 我们去[官网](https://www.npmjs.com/package/uniq)看一下`uniq`的用法: ```javascript let uniq = require('uniq'); let arr = [1, 1, 2, 2, 3, 5]; uniq(arr); console.log(arr); //输出结果:[ 1, 2, 3, 5 ] ``` 可以看出,这个包可以起到数组去重的作用。 ### 3、自定义模块 (1)module1.js: ```javascript //暴露方式一:module.exports = value //暴露一个对象出去 module.exports = { name: '我是 module1', foo(){ console.log(this.name); } } //我们不能再继续写 module.exports = xxx。因为重新赋值,会把之前的赋值覆盖掉。 ``` (2)module2.js: ```javascript //暴露方式一:module.exports = value //暴露一个函数出去 module.exports = function(){ console.log('我是 module2'); } ``` 注意,此时暴露出去的 exports 对象 等价于整个函数。 (3)module3.js: ```javascript //暴露方式二:exports.xxx = value //可以往 export 对象中不断地添加属性,进行暴露 exports.foo1 = function(){ console.log('module3 中的 foo1 方法'); } exports.foo2 = function(){ console.log('module3 中的 foo2 方法'); } exports.arr = [1,1,2,2,3,5,11]; ``` (4)app.js:(将其他模块汇集到主模块) ```javascript //将其他模块汇集到主模块 let uniq = require('uniq'); //引入时,第三方模块要放在自定义模块的上面 let module1 = require('./modules/module1'); let module2 = require('./modules/module2'); let module3 = require('./modules/module3'); //调用module1对象的方法 module1.foo(); //调用module2的函数 module2(); //注意,在定义时,module2对象等价于整个函数function。所以,module2()的意思是,直接调用了函数。 //调用module3中的属性 module3.foo1(); module3.foo2(); uniq(module3.arr); //将module3中的数组进行去重操作 console.log(module3.arr); //打印数组去重后的结果 ``` 这样的话,我们的代码就写完了。 我们在命令行中输入`node app.js`,就可以把代码跑起来了。打印结果如下: ```bash 我是 module1 我是 module2 module3 中的 foo1 方法 module3 中的 foo2 方法 [ 1, 11, 2, 3, 5 ] ``` ## CommonJS 基于浏览器端的实现举例 ### 1、初始化项目 在工程文件中新建如下目录和文件: ``` js dist //打包生成文件的目录 src //源码所在的目录 | module1.js | module2.js | module3.js | app.js //应用主源文件 index.html //因为CommonJS是基于浏览器端,js文件要跑在浏览器的页面上,所以要有这个html页面 ``` 然后在根目录下新建如下命令: ``` npm init ``` 然后根据提示,依次输入如下内容: - **包名**:可以自己起包名,也可以用默认的包名。注意,包名里不能有中文,不能有大写。 - **版本**:可以用默认的版本 1.0.0,也可以自己修改包名。 其他的参数,一路回车即可。 于是,根目录下会自动生成`package.json`这个文件。点进去看一下: ```json { "name": "commonjs_browser", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ``` ### 2、下载第三方包:Browserify 这里需要用到[Browserify](http://browserify.org/)这个工具进行编译打包。Browserify 称为 CommonJS 的浏览器端的打包工具。 输入如下命令进行安装:(两个命令都要输入) ```javascript npm install browserify -g //全局 npm install browserify --save-dev //局部。 ``` 上面的代码中,`-dev`表示开发依赖。这里解释一下相关概念: - 开发依赖:当前这个包,只在开发环境下使用。 - 运行依赖:当前这个包,是在生产环境下使用。 ### 3、自定义模块 & 代码运行 (1)module1.js: ```javascript //暴露方式一:module.exports = value //暴露一个对象出去 module.exports = { name: '我是 module1', foo(){ console.log(this.name); } } //我们不能再继续写 module.exports = xxx。因为重新赋值,会把之前的赋值覆盖掉。 ``` (2)module2.js: ```javascript //暴露方式一:module.exports = value //暴露一个函数出去 module.exports = function(){ console.log('我是 module2'); } ``` 注意,此时暴露出去的 exports 对象 等价于整个函数。 (3)module3.js: ```javascript //暴露方式二:exports.xxx = value //可以往export对象中不断地添加属性,进行暴露 exports.foo1 = function(){ console.log('module3 中的 foo1 方法'); } exports.foo2 = function(){ console.log('module3 中的 foo2 方法'); } ``` (4)app.js:(将其他模块汇集到主模块) ```javascript let module1 = require('./module1'); // ./ 指的是当前路径 let module2 = require('./module2'); let module3 = require('./module3'); module1.foo(); module2(); module3.foo1(); module3.foo2(); ``` 引入的路径解释: - `./`是相对路径,指的是当前路径(app.js的当前路径是src) 到此,我们的主要代码就写完了。 但是,如果我们直接在index.html中,像下面这样写,是不行的:(因为浏览器不认识 require 关键字) ```html Document ``` 为了能够让index.html引入app.js,我们需要输入如下命令: 打包处理js: ``` browserify js/src/app.js -o js/dist/bundle.js ``` 然后在index.html中引入打包后的文件: ```html ``` ================================================ FILE: 11-Node.js/05-Node.js内置模块:fs文件模块.md ================================================ --- title: 05-Node.js内置模块:fs文件模块 publish: true --- ## Node.js 的官方API文档 - Node.js 的API文档(英文): - Node.js 的API文档(中文): 关于 Node.js 的内置模块和常见API,可以看官方文档。 查阅文档时,稳定指数如下: - 红色:废弃。 - 橙色:实验。表示当前版本可用,其他版本不确定。也许不向下兼容,建议不要在生产环境中使用该特性。 - 绿色:稳定。与 npm 生态系统的兼容性是最高的优先级。 ## Node.js 中模块的分类 Node.js 应用由模块组成,采用 CommonJS 模块规范。Node.js中的模块分为三种: - 内置模块 - 第三方模块 - 自定义模块 下面简单介绍一下。 ### 1、内置模块 ```js const process = require('process'); const path = require('path'); console.log(process.version); console.log(path.resolve('../')); ``` require方法用于加载模块。 常见的内置模块包括: - FS:文件系统模块 - path:路径模块 - OS:操作系统相关 - net:网络相关 - http - ... 你可能会有疑问:Node.js 这么牛吗?还能直接和操作系统做交互? 带着这个疑问,我们不妨简单看看 Node.js 的源码,以 os 模块举例: - 打开os模块的源码:,翻到最底部,找到 `cpus`这个方法 - 进而找到 `getCPUs()` - internalBinding('os'):通过 internalBinding 可以调用系统底层的方法。internalBinding 主要是 JS 虚拟机在做的事情。 - `internalBinding('os')` 的实现,在 里,里面都是 C++ 的代码。比如有一个`getCPUs`方法。 现在你知道了,JS本身是没有能力获取底层系统资源的,这一切都是 JS虚拟机在和底层做交互,然后通过 JS 的表现形式,暴露给应用层。 另外,还有很多库,是直接使用C/++编写的,通过编译之后,再提供给 JS 应用层调用,或者直接提供给 Node.js层使用。 **所有的编程语言底层都会回归C/C++**,甚至是汇编语言。 ### 2、require 加载第三方包的机制 ```js const express = require('express'); ``` require 加载第三方包的机制: (1)第三方包安装好后,这个包一般会存放在当前项目的 node_modules 文件夹中。我们找到这个包的 package.json 文件,并且找到里面的main属性对应的入口模块,这个入口模块就是这个包的入口文件。 (2)如果第三方包中没有找到package.json文件,或者package.json文件中没有main属性,则默认加载第三方包中的index.js文件。 (3)如果在 node_modules 文件夹中没有找到这个包,或者以上所有情况都没有找到,则会向上一级父级目录下查找node_modules文件夹,查找规则如上一致。 (4)如果一直找到该模块的磁盘根路径都没有找到,则会报错:can not find module xxx。 ### 3、自定义模块(module): 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。 举例: ``` var example = require('./example.js'); console.log(example.x); // 5 console.log(example.addX(1)); // 6 ``` ## 读取文件 今天这篇文章,重点讲一下 Node 内置模块中的 **fs(文件处理模块)**。 在使用文件模块之前,记得先导入: ```js // 导入文件模块 const fs = require('fs'); ``` fs 的英文全称是 File System。fs 模块提供了很多 api 方法,我们首先应该学习的方法是**文件读取**。 Node中文件读取的方式主要有以下几种。 ### 异步读取文件 fs.readFile() 语法格式: ```js fs.readFile(file[, options], callback(error, data)) ``` 代码举例: ```javascript const fs = require('fs'); fs.readFile('hello.txt', 'utf8', (err, data) => { if (err) { // 失败 console.log(err) } else { // 成功 console.log('异步读取数据:' + data2) } }); ``` 如果需要嵌套读取多个文件,可以用 promise 或者 async ... await 进行封装。代码举例如下。 ### promise 封装 fs.readFile() ```js const fs = require('fs'); function fsRead(path) { return new Promise((resolve, reject) => { fs.readFile(path, { flag: 'r', encoding: "utf-8" }, (err, data) => { if (err) { //失败执行的内容 reject(err) } else { //成功执行的内容 resolve(data) } }) }) } var promise1 = fsRead('hello1.txt') promise1.then(res1 => { console.log(res1); return fsRead('hello2.txt'); }).then(res2 => { console.log(res2); return fsRead('hello3.txt'); }).then(res3 => { console.log(res); }) ``` ### async ... await 封装 fs.readFile() 这个写法更为简洁,推荐。 ```js var fs = require('fs'); function fsRead(path) { return new Promise((resolve, reject) => { fs.readFile(path, { flag: 'r', encoding: "utf-8" }, (err, data) => { if (err) { //失败执行的内容 reject(err) } else { //成功执行的内容 resolve(data) } }) }) } async function ReadList() { var res1 = await fsRead('hello1.txt'); var res2 = await fsRead('hello2.txt'); var res3 = await fsRead('hello3.txt'); } // 执行方法 ReadList(); ``` ### 同步读取文件 fs.readFileSync() 语法格式: ```js fs.readFileSync(file[, options]) ``` 代码举例: ```javascript const fs = require('fs'); try { const data = fs.readFileSync('hello.txt', 'utf8'); console.log(data); } catch(e) { // 文件不存在,或者权限错误 throw e; } ``` ### Node.js 中的同步和异步的区别 fs模块对文件的几乎所有操作都有同步和异步两种形式。例如:readFile() 和 readFileSync()。 区别: - 同步调用会阻塞代码的执行,异步则不会。 - 异步调用会将 读取任务 下达到任务队列,直到任务执行完成才会回调。 - 异常处理方面:同步必须使用 try catch 方式,异步可以通过回调函数的第一个参数。【重要】 ## 写入文件 语法格式: ```js fs.write(fd, string[, position[, encoding]], callback) ``` async ... await 封装: ```js let fs = require('fs') function writeFs(path, content) { return new Promise(function (resolve, reject) { fs.writeFile(path, content, { flag: "a", encoding: "utf-8" }, function (err) { if (err) { //console.log("写入内容出错") reject(err) } else { resolve(err) //console.log("写入内容成功") } }) }) } async function writeList() { await writeFs('1.html', "

    qianguyihao

    "); await writeFs('2.html', "

    hello world

    "); await writeFs('3.html', "

    永不止步

    "); } writeList() ``` ## 删除文件 语法格式: ```js fs.unlink(path, callback) ``` 参数说明: - path:文件路径。 - callback:回调函数。 代码举例: ```js fs.unlink('path/file.txt', (err) => { if (err) throw err; console.log('文件删除成功'); }); ``` 备注:`fs.unlink()` 不能用于删除目录。 如果要删除目录,可以使用 `fs.rmdir()`。 ## Buffer 通过 Buffer 开辟的内存空间,都是连续的内存空间,所以效率比较高。 代码举例1: ```js // 将字符串转成 buffer 对象 const str = 'qianguyihao'; let buffer = Buffer.from(str); console.log(buffer); // 输出16进制编码 console.log(buffer.toString()); // 输出字符串:qianguyihao ``` 代码举例2: ```js // 从内存中开辟一个新的缓冲区 let buffer = Buffer.alloc(20); buffer[0] = 'a'; console.log(buffer); ``` ## 读取目录 语法格式: ```js fs.mkdir(path[, options], callback) ``` 参数说明: - path:文件路径。 - options参数可以是: - recursive:是否以递归的方式创建目录,默认为 false。 - mode:设置目录权限,默认为 0777。 代码举例: ```js var fs = require("fs"); ​ console.log("查看 /tmp 目录"); fs.readdir("/tmp/",function(err, files){ if (err) { return console.error(err); } files.forEach( function (file){ console.log( file ); }); }); ``` 其他的还有:(暂略) - 删除目录 - 输入输出 ================================================ FILE: 11-Node.js/06-Node.js内置模块:path路径模块.md ================================================ --- title: 06-Node.js内置模块:path路径模块 publish: true --- ## path 路径模块 Node.js 通过`path`这个内置模块,提供了一些路径操作的API,具体可以参考官方的api文档。这里列举一些常用的API。 ### path.extname() 获取文件/路径的扩展名 语法格式: ```js path.extname(myPath); ``` 代码解释: - 获取 `myPath` 这个文件或者路径的扩展名。 - `myPath` 这个参数要求是字符串。如果 `myPath` 不是字符串,则抛出 TypeError。 代码举例: ```js const path = require('path'); path.extname('hello.txt'); // 返回 '.txt' path.extname('www.qianguyihao.com'); // 返回 '.com' path.extname('index.coffee.md'); // 返回 '.md' path.extname('index.'); // 返回 '.' path.extname('index'); // 返回 '' path.extname('.index'); // 返回 '' path.extname('.index.md'); // 返回 '.md' ``` ### path.resolve() 生成完成的绝对路径 语法格式: ```js path.resolve([...myPaths]) ``` 解释: - 将路径或路径片段的序列解析为绝对路径。 - 返回的路径是**从右往左**处理,后面的每个 myPath 被依次解析,直到构造出一个完整的绝对路径。 代码举例: ```js const path = require('path'); let arr1 = ['/foo1/foo2', 'qianguyihao', 'foo3']; let result1 = path.resolve(...arr1); console.log(result1); // 打印结果:/foo1/foo2/qianguyihao/foo3 let arr2 = ['/foo1/foo2', '/qianguyihao', 'foo3']; let result2 = path.resolve(...arr2); console.log(result2); // 打印结果:/qianguyihao/foo3 ``` ### 几个常见路径 - `__dirname`:这是一个常量,表示:当前执行文件所在**完整目录**。 - `__filename`:这是一个常量。表示:当前执行文件的**完整目录 + 文件名**。 - `process.cwd`:获取当前执行 Node命令 时的目录名。 代码举例: ```js console.log(__dirname); console.log(__filename); console.log(process.cwd()); ``` 运行结果: ```bash $ node app.js /Users/smyhvae/qianguyihao /Users/smyhvae/qianguyihao/app.js /Users/smyhvae/qianguyihao ``` ### path.join() 将多个路径进行拼接 如果是我们手动拼接路径,容易出错。这个时候,可以利用 path.join() 方法将路径进行拼接。 语法格式: ```js path.join([...paths]); ``` 解释:使用平台特定的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径。 代码举例: ```js const path = require('path'); const result1 = path.join(__dirname, './app.js'); console.log(result1); // 返回:/Users/smyhvae/qianguyihao/app.js const result2 = path.join('/foo1', 'foo2', './foo3'); console.log(result2); // 返回:/foo1/foo2/foo3 const result3 = path.join('/foo1', 'foo2', '/foo3'); console.log(result3); // 返回:/foo1/foo2/foo3 ``` ## OS 系统模块 - os.networkInterfaces() 查看网络地址 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](https://img.smyhvae.com/20200102.png) ================================================ FILE: 11-Node.js/07-Node.js操作MySQL数据库.md ================================================ --- title: 07-Node.js操作MySQL数据库 publish: true --- ## Node.js 连接 MySQL (1)安装 mysql 包: ```bash $ npm install mysql ``` (2)引入 mysql 包: ```js const mysql = require("mysql"); ``` (3)建立连接: ```js let mysql = require("mysql"); let options = { host: "localhost", //port:"3306", //可选,默认3306 user: "root", password: 'xxx', // 这里改成你自己的数据库连接密码 database: "qiangu_database", }; //创建与数据库进行连接的连接对象 let connection = mysql.createConnection(options); //建立连接 connection.connect((err) => { if (err) { // 数据库连接失败 console.log(err); } else { // 数据库连接成功 console.log("数据库连接成功"); } }); ``` 正常来说,运行程序后,应该会提示`数据库连接成功`。 如果在运行时提示错误`Client does not support authentication protocol requested by server`,解决办法如下:(在终端进入 sql 之后,输入如下命令) ```sql # 注意,这里的 'root' 请填你的user账号, 'localhost' 请填 你的 host, 'password' 请填你的密码 ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password'; # 然后执行如下命令 flush privileges; ``` ## Node.js 增删改查 MySQL 针对下面这张表: ![](https://img.smyhvae.com/20200418_1728.png) 通过 Node.js可以对其进行一些增删改查操作。代码举例如下。 ### 1、查询表 ```js let mysql = require('mysql'); let options = { host: 'localhost', //port:"3306",//可选,默认3306 user: 'root', password: 'xxx', // 这里改成你自己的数据库密码 database: 'qiangu_database' } //创建与数据库进行连接的连接对象 let connection = mysql.createConnection(options); //建立连接 connection.connect((err) => { if (err) { // 数据库连接失败 console.log(err) } else { // 数据库连接成功 console.log('数据库连接成功') } }); // 1、查询表 let strSql1 = 'select * from qiangu_student_table'; connection.query(strSql1, (err, result, fields) => { if (err) { // 表查询失败 console.log(err); } else { // 表查询成功 console.log('qiangu_student_table 表查询结果:' + JSON.stringify(result)); console.log('fields:' + JSON.stringify(fields)); } }) ``` 打印结果如下: ```bash qiangu_student_table 表查询结果: [{"id":1,"name":"千古壹号","age":28},{"id":2,"name":"许嵩","age":34},{"id":3,"name":"邓紫棋","age":28}] fields:[ {"catalog":"def","db":"qiangu_database","table":"qiangu_student_table","orgTable":"qiangu_student_table","name":"id","orgName":"id","charsetNr":63,"length":11,"type":3,"flags":0,"decimals":0,"zeroFill":false,"protocol41":true}, {"catalog":"def","db":"qiangu_database","table":"qiangu_student_table","orgTable":"qiangu_student_table","name":"name","orgName":"name","charsetNr":33,"length":765,"type":253,"flags":0,"decimals":0,"zeroFill":false,"protocol41":true}, {"catalog":"def","db":"qiangu_database","table":"qiangu_student_table","orgTable":"qiangu_student_table","name":"age","orgName":"age","charsetNr":63,"length":11,"type":3,"flags":0,"decimals":0,"zeroFill":false,"protocol41":true} ] ``` ### 删除表 ```js // 2、删除表 let strSql2 = 'drop table test2_table'; connection.query(strSql2, (err, result) => { if (err) { // 表删除失败 console.log(err); } else { // 表删除成功 console.log('表删除成功:' + result); } }); ``` 打印结果: ```bash 表删除成功: OkPacket { fieldCount: 0, affectedRows: 0, insertId: 0, serverStatus: 2, warningCount: 0, message: '', protocol41: true, changedRows: 0 } ``` ### 删除数据库 将上方的sql语句换一下即可: ```sql let strSql3 = 'drop database qiangu_database'; ``` ### 2、新建数据库 ```js let mysql = require('mysql'); let options = { host: 'localhost', //port:"3306",//可选,默认3306 user: 'root', password: 'smyhvae001', // database: 'qiangu_database' // 注意,因为代码里是创建新的数据库,所以这里不需要填其他的数据库名 } //创建与数据库进行连接的连接对象 let connection = mysql.createConnection(options); //建立连接 connection.connect((err) => { if (err) { // 数据库连接失败 console.log(err); } else { // 数据库连接成功 console.log('数据库连接成功') } }); // 创建新的数据库 const strSql4 = 'create database qiangu_database3'; connection.query(strSql4, (err, result) => { if (err) { console.log(err); } else { console.log('新建数据库成功:' + JSON.stringify(result)); } }); ``` 打印结果: ```bash 数据库连接成功 新建数据库成功:{ "fieldCount":0,"affectedRows":1,"insertId":0,"serverStatus":2,"warningCount":0,"message":"","protocol41":true,"changedRows":0 } ``` ### 3、新建表 新建表的sql语句举例: ```sql CREATE TABLE `qiangu_table5` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `age` int DEFAULT NULL, PRIMARY KEY (`id`) ); ``` 如果是在 js 代码中执行上面这样命令的话,要记得把 sql 语句存放在字符串里的同一行。 代码举例如下: ```js let mysql = require('mysql'); let options = { host: 'localhost', //port:"3306",//可选,默认3306 user: 'root', password: 'smyhvae001', database: 'qiangu_database' } //创建与数据库进行连接的连接对象 let connection = mysql.createConnection(options); //建立连接 connection.connect((err) => { if (err) { // 数据库连接失败 console.log(err); } else { // 数据库连接成功 console.log('数据库连接成功') } }); // 新建表 // 注意,在 js 代码中,sql 语句要存放在字符串里的同一行。 const strSql5 = 'CREATE TABLE `qianguyihao_table5` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`age` int DEFAULT NULL,PRIMARY KEY (`id`));'; connection.query(strSql5, (err, result) => { if (err) { // 新建表失败 console.log(err); } else { // 新建表成功 console.log('qianguyihao 新建表成功:' + JSON.stringify(result)); } }) ``` 打印结果: ```bash 数据库连接成功 qianguyihao 新建表成功: { "fieldCount":0,"affectedRows":0,"insertId":0,"serverStatus":2,"warningCount":0,"message":"","protocol41":true,"changedRows":0 } ``` ### 在指定的表中插入数据 在指定的表中插入数据: ```js // 在指定的表中插入数据 const strSql6 = "insert into qianguyihao_table5 (name, age) values ('千古壹号', '28')"; connection.query(strSql6, (err, result) => { if (err) { // 插入数据失败 console.log(err); } else { // 在指定的表中插入数据成功 console.log('qianguyihao 在指定的表中插入数据成功:' + JSON.stringify(result)); } }); ``` 打印结果: ```bash qianguyihao 在指定的表中插入数据成功: { "fieldCount":0,"affectedRows":1,"insertId":1,"serverStatus":2,"warningCount":0,"message":"","protocol41":true,"changedRows":0 } ``` 如果插入的数据是变量(比如是用户提交上来的数据),那么,sql 语句可以这样写: ```js // 在指定的表中插入数据(数据作为变量) const strSql7 = "insert into qianguyihao_table5 (name, age) values (?, ?)"; connection.query(strSql7, ['许嵩', '34'], (err, result) => { if (err) { // 插入数据失败 console.log(err); } else { // 在指定的表中插入数据成功 console.log('qiangauyihao 在指定的表中插入数据成功:' + JSON.stringify(result)); } }); ``` ================================================ FILE: 11-Node.js/CommonJS.md ================================================ --- title: 01-数据库的基础知识 publish: false --- ## 全局对象 ### global 类似于客户端 JavaScript 运行环境中的 window。 ## process 用于获取当前的 Node 进程信息,一般用于获取环境变量之类的信息。 ### console Node 中内置的 console 模块,提供操作控制台的输入输出功能,常见使用方式与客户端类似。 ## 全局函数 - setInterval(callback, millisecond) - clearInterval(timer) - setTimeout(callback, millisecond) - clearTimeout(timer) - Buffer:Class - 用于操作二进制数据 - 以后介绍 ## Node 调试 ### 最简单的调试 最方便也是最简单的调试:console.log() ### Node 原生的调试 网址: ### 第三方模块提供的调试工具 ``` $ npm install node-inspector –g //方式一 $ npm install devtool -g //方式二 ``` ### 开发工具的调试 - Visual Studio Code - WebStorm ## 模块化结构 Node 实现 CommonJS 规范,所以可以使用模块化的方式组织代码结构。 - Node 采用的模块化结构是按照 CommonJS 规范。 - 模块与文件是一一对应关系,即加载一个模块,实际上就是加载对应的一个模块文件。 ### CommonJS 规范 CommonJS 就是一套约定标准,不是技术。用于约定我们的代码应该是怎样的一种结构。 参考链接: - ### 常用内置模块 - `path`:处理文件路径。 - `fs`:操作(CRUD)文件系统。 - `child_process`:新建子进程。 - `util`:提供一系列实用小工具。 - `http`:提供 HTTP 服务器功能。 - `url`:用于解析 URL。 - `querystring`:解析 URL 中的查询字符串。 - `crypto`:提供加密和解密功能。 总结:更多内容可以参考 api文档: ## 文件系统操作 ### 相关模块 - fs:基础的文件操作 API - path:提供和路径相关的操作 API - readline:用于读取大文本文件,一行一行读 - fs-extra(第三方): ================================================ FILE: 11-Node.js/ES6.md ================================================ --- title: 01-数据库的基础知识 publish: false --- ## 前言 ECMAScript 是 JS 的语言标准。而 ES6 是新的 JS 语法标准。 ### 发展历史 20180303_1633.png - 2015年6月,ES6正式发布。 ### ES6 的其他优势 - 使用 babel 语法转换器,支持低端浏览器 - 流行的库基本都是基于 ES6 构建。 React 默认使用 ES6 新源发开发。 ## ES6 的常用语法 ### ES6语法概览 - 块级作用域、字符串 - 对象扩展、解构 - 类、模块化等。 ### 作用域:let 和 const - 用 `let`定义变量 ,替代 var - 用const 定义常量(定义后,不可修改) - 作用域和 {} 举例: ```javascript let a1 = 'haha'; const name = `smyhvae`; ``` ### 模板字符串 我们以前让字符串进行拼接的时候,是这样做的:(传统写法的字符串拼接) ```javascript var name = 'smyhvae'; var age = '26'; console.log('name:'+name+',age:'+age); //传统写法 ``` 这种写法,比较繁琐,而且容易出错。 现在有了 ES6 语法,字符串拼接可以这样写: ```javascript var name = 'smyhvae'; var age = '26'; console.log('name:'+name+',age:'+age); //传统写法 console.log(`name:${name},age:${age}`); //ES6 写法 ``` 注意,上方代码中,倒数第二行用的是单引号,最后一行用的是反引号(在tab键的上方)。 ### 函数扩展 ES6 中函数的用法: - 参数默认值 - 箭头函数 - 展开运算符 定义和调用函数:(传统写法) ```javascript function fn1(name) { console.log(name); } fn1('smyhvae'); ``` 定义和调用函数:(ES6写法) ```javascript var fn2 = (name)=>{ console.log(name); } fn2('smyhvae'); ``` 上面两端代码,执行的结果是一样的。 当然,也可以给上面这个函数的参数加一个默认值: ```javascript var fn2 = (name='enen')=>{ console.log(name); } fn2(); //参数用默认值 enen fn2('smyhvae'); ``` 比如说,1秒后执行一段代码,可以用箭头函数: ```javascript setTimeout(()=>{ console.log('something'); },1000); ``` 如果函数体只有一条 return 语句,那么大括号可以省略: ```javascript const myDouble = x=>x*2; console.log(myDouble(5)); //打印结果为10 ``` 箭头函数的好处: - 简写代码 - 保持 this 的作用域 ## ================================================ FILE: 11-Node.js/JavaScript模块化:AMD.md ================================================ --- title: JavaScript模块化:AMD publish: true --- ## AMD的基本语法 ### AMD的概念 **AMD**(Asynchronous Module Definition):异步模块定义。AMD专门用于浏览器端,模块的加载是异步的。 [**AMD规范**](https://github.com/amdjs/amdjs-api):是 **[RequireJS](http://requirejs.org/)** 在推广过程中对模块化定义的规范化产出。 RequireJS:一个基于AMD规范实现的模块化开发解决方案。 ### 暴露模块的方式 **定义没有依赖的模块**:(参数只有一个 function) ```javascript define(function () { return 模块 }) ``` **定义有依赖的模块**:(参数有两个:模块名、function) ```javascript //定义有依赖的模块:第一个参数为数组 define(['module1', 'module2'], function (m1, m2) { return 模块 }) ``` 代码解释: - 第一个参数必须是数组,里面存放的是,需要依赖的其他的模块。 - 第二个参数是function,里面带了形参 m1 和 m2,分别代表了 module1 和 module2。这个形参的作用是,前面依赖的模块一旦声明了,就可以一一对应地注入到 function中去,从而在 function 内部使用依赖的模块。这种方式称之为**显式声明依赖注入**。 ### 引入模块的方式 在主模块中引入其他的模块: ```javascript //在主模块中引入其他的模块 require(['module1', 'module2'], function (m1, m2) { 使用m1 / m2 }) ``` ### RequireJS:是AMD的实现 - - ## RequireJS的使用举例(自定义模块) ### 1、创建项目结构 在工程文件中新建如下目录: ``` js | libs | modules | alerter.js | dataService.js | main.js index.html ``` 所有的代码写完之后,项目结构如下: ![](http://img.smyhvae.com/20180411_1331.png) ### 2、下载require.js,并导入 - 官网: - GitHub: 在官网下载`require.js`文件: ![](http://img.smyhvae.com/20180411_1127.png) 然后将`require.js`文件拷贝到项目的`js/libs/`目录中。 这样的话,就导入成功了。 ### 3、自定义模块 (1)dataService.js: ```javascript //定义没有依赖的模块 define(function () { let name = '我是 dataService.js中的内容'; function getName() { return name; } //暴露模块 return { getName }; }); ``` 这模块没有依赖。 (2)alerter.js: ```javascript //定义有依赖的模块 define(['myDataService'], function (dataService) { let msg = '我是 aleter.js中的内容'; function showMsg() { console.log(dataService.getName()); //调用了 myDataService 模块中的内容 console.log(msg); } //暴露模块 return { showMsg }; }); ``` 这个模块,依赖了`myDataService`这个模块,模块名是我自己起的。稍后,我们会在main.js中做映射,将`myDataService`这个名字和`dataService.js`文件关联起来。 (3)main.js: > 这个是主模块。 ```javascript requirejs.config({ //baseUrl: 'js/', //基本路径 paths: { //配置路径 myDataService: './modules/dataService', myAlerter: './modules/alerter' } }); requirejs(['myAlerter'], function (alerter) { alerter.showMsg(); })(); ``` 这个模块,依赖了`myAlerter`这个模块,模块名是我自己起的。并且,我们在文件的上方做了映射,将`myAlerter`这个名字和`alerter.js`文件关联了起来。 我们来讲一下最上方的几行代码(即`requirejs.config`里的内容)的意思: - 我们可以看到,文件(3)依赖了文件(2),文件(2)依赖了文件(1)。 - `paths`里做的就是映射:将键`myDataService`和文件`dataService.js`进行关联,将键`myAlerter`和文件`alerter.js`进行关联。 另外,再讲一下注释里的`baseUrl`的用法:如果没有这个注释,那么`paths`里的路径,是从**当前这个文件**(main.js)的角度出发的;如果加了一行`baseUrl`,表明它是 paths 里所有路径的最开头的部分,`baseUrl`的路径是从**项目的根目录**的角度出发的。 (4)index.html: 这个是入口文件。 ```html Document ``` 注意,上面的代码中,我们直接通过`src`属性引入`requre.js `文件,一旦这个文件发挥作用了,会去找`data-main`属性里的指向,它正好指向的是主模块。 有了上面这种引入的方式,我们就不用再老土地引入多个` ``` 打印结果: ![](http://img.smyhvae.com/20180412_1955.png) ## others ### SeaJS 的介绍 SeaJS:一个基于CMD规范实现的模块化开发解决方案。 作者:Alibaba 玉伯。 官网: GitHub: 现在官网变成了: 特性: - 简单友好的模块定义规范。 - 自然直观的代码组织方式。 ![](http://img.smyhvae.com/20180303_2107.png) ### RequireJS(AMD)、SeaJS(CDM)、CommonJS、ES6 的对比 1、RequireJS 和 AMD: ![](http://img.smyhvae.com/20180303_1653.png) 异步模块定义,特点是依赖前置。 2、SeaJS 和 CMD: 同步模块定义。 ```javascript // 所有模块都通过 define 来定义 define(function(require, exports, module) { //通过 require 引入依赖 var $ require(`jquery`); var Spinning = require(`./spinning`); }) ``` 3、CommonJS: ![](http://img.smyhvae.com/20180303_1701.png) 以上三个都是 ES5里面的规范。 4、ES6: ES6的特性:export/import ![](http://img.smyhvae.com/20180303_1704.png) ================================================ FILE: 11-Node.js/JavaScript模块化:ES6.md ================================================ --- title: JavaScript模块化:ES6 publish: true --- ## 模块化开发的引入 ### JS开发的弊端 JS 在使用时存在两大问题,而 Node.js 可以很好地避免这两个问题: - 文件依赖。比如 a 文件依赖 b 文件,b 文件依赖 c 文件。而 Node.js 中的文件依赖,不需要人工维护和人为分析。 - 命名冲突。js 的各个文件是相互开放的,容易导致命名冲突。而 Node.js 是属于半封闭的状态,可以指定哪些内容是开放的,哪些内容是封闭的。 Node.js 在解决这两个问题时,用到的就是模块化开发。 ### 软件开发中的模块化开发 一个功能就是一个模块,多个模块可以组成完整的应用,抽离一个模块不会影响其他功能的运行。 效果如下: ![](http://img.smyhvae.com/20200409_1934.png) ### Node.js 中的模块化开发 Node.js 规定,一个 JS 文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法访问。 模块内部可以使用 `exports` 对象进行成员导出, 使用 `require` 方法导入其他模块。效果如下: ![](http://img.smyhvae.com/20200409_1932.png) ## ES6模块化的基本语法 ### ES6模块化的说明 **依赖模块需要编译打包处理**。原因如下: - (1)有些浏览器不支持 ES6 的语法,写完 ES6 的代码后,需要通过`Babel`将 ES6 转化为 ES5。 - (2)生成了ES5之后,里面仍然有`require`语法,而浏览器并不认识`require`这个关键字。此时,可以用 `Browserify`编译打包 js,进行再次转换。 推荐学习链接: - ### 基本语法: **导出模块**: ``` export ``` **引入模块**: ``` import xxx from '路径' ``` ## ES6模块化的使用举例(自定义模块) ### 1、初始化项目 (1)在工程文件中新建如下目录: ``` js | src | module1.js | module2.js | module3.js | main.js index.html ``` (2)在工程的根目录下,新建文件`package.json`,内容如下: ```json { "name": "es6-babel-browserify", "version": "1.0.0" } ``` ### 2、环境配置:安装babel 和 browserify等 (1)安装babel 和 browserify: ```bash npm install babel-cli -g npm install babel-preset-es2015 --save-dev npm install browserify -g ``` 安装 babel 的详细解释,可以参考本人的另外一篇文章:[ES6的介绍和环境配置](https://github.com/smyhvae/Web/blob/master/10-ES6/03-ES6%E7%9A%84%E4%BB%8B%E7%BB%8D%E5%92%8C%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.md) (2)新建.babelrc: 在根目录下新建文件`.babelrc`,输入如下内容: ``` { "presets":[ "es2015" ], "plugins":[] } ``` ### 3、编写代码 (1)module1.js: ```javascript //暴露模块:采用分别暴露的方式 export function foo1() { console.log('我是 module1 中的 foo1'); } export function foo2() { console.log('我是 module2 中的 foo2'); } export let arr = [1, 2, 3, 4, 5]; ``` (2)module2.js: ```javascript //暴露模块:采用统一暴露的方式 function fn1() { console.log('我是 module2 中的 fn1'); } function fn2() { console.log('我是 module2 中的 fn2'); } //统一暴露 export { fn1, fn2 }; ``` (3)module3.js: ```javascript //暴露模块:采用默认暴露的方式。 //默认暴露的方式可以暴露任意数据类型,暴露的是什么数据,接收到的就是什么数据 //语法格式:export default value; export default () => { console.log('我是 module3 中 default 方式暴露的函数'); }; ``` 这里,我们采取了一种新的暴露方式(默认暴露),在暴露时,加上了`default`这个关键字。代码里暴露了一个箭头函数,稍后,我们注意在main.js里是怎么引入module3.js的。 注意,我们只能写一次 default,也就是说,只能进行一次默认暴露。 (4)module4.js:(default方式暴露多个属性) ```javascript //暴露模块:采用默认暴露的方式。 //默认暴露的方式可以暴露任意数据类型,暴露的是什么数据,接收到的就是什么数据 //语法格式:export default value; export default { name: '我是 module4 中 default 方式暴露的属性 name', foo() { console.log('我是 module4 中 default 方式暴露的函数 foo'); } } ``` 这里,我们依旧采取了默认暴露的方式,只能写一次 default。代码里暴露了一个对象(对象里存放了一个属性、一个方法)。稍后,我们注意在main.js里是怎么引入module4.js的。 如果我想暴露多个属性、多个对象怎呢?很简单,把你想要暴露的所有内容,都放在default里,包成一个对象。你看module4.js就是如此, 同时暴露了多个属性&方法。 (5)main.js: 这个是主模块。现在,我们来看一下,它如何引入上面的四个模块。 ```javascript //主模块。引入其他的模块 import { foo1, foo2 } from './module1'; //采用解构赋值的形式进行导入。注意,括号里的对象名,要和 module1 中的对象名一致。 import { fn1, fn2 } from './module2'; //采用解构赋值的形式进行导入。注意,括号里的对象名,要和 module2 中的对象名一致。 import myModule3 from './module3'; //module3 模块是采用 default 方式进行暴露的,myModule3 这个名字是我随便起的 import myModule4 from './module4'; //module4 模块是采用 default 方式进行暴露的,myModule4 这个名字是我随便起的 //调用module1、module2中的内容 foo1(); foo2(); fn1(); fn2(); //调用module3中的内容 myModule3(); //调用module4中的内容 console.log(myModule4.name); //module4中的属性 myModule4.foo(); //module4中的方法 ``` 我们可以看出:(具体请看注释,非常重要) - module1和module2是采用**常规暴露**的形式,在引入它们时,模块名要一致。而且,要求用**对象解构赋值**的形式,而不是用 `import myModule from ...`这种形式(否则会报错 undefined)。 - module2和module3是采用**默认暴露**的形式,在引入它们时,模块名随便起。 (6)index.html: 在这里引入main.js即可。 ```html Document ``` ### 4、编译转换 如果我们不进行转换,而是直接在 index.html 中加载 js/src/main.js,是会报错的: 接下来,我们就进行转换。 (1)利用 babel 将 ES6 转换为 ES5: ``` babel src -d build //build目录会自动生成 ``` 上方命令的意思是,将`src`目录下的所有ES6文件转化为ES5文件,并放在`build`目录下(`build`目录会被自动创建)。 转化成ES5之后,我们发现,如果直接在 index.html 中加载`build`目录下的ES5文件,也是会报错的,因为浏览器不认识`main.js`里的`require`关键字: ![](http://img.smyhvae.com/20180414_1410.png) 于是,我们还要进行一次转换。 (2)利用`Browserify`编译打包 `build`目录下的 ES5 文件: ```bash browserify build/main.js -o dist/main.js //dist目录需要手动创建 ``` dist/main.js就是我们需要引入到 index.html 里的文件。 以后,我们每次修改完ES6的代码,就要执行上面的两个命令,重新生成新的js文件。 运行效果: ![](http://img.smyhvae.com/20180414_1615.png) 工程文件:[2018-04-13-ES6Demo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-04-13-ES6Demo.rar) ## ES6模块化的使用举例(引入第三方模块) 下载 jQuery 包: ``` npm install jquery@1 //下载jQuery 1.X 的版本里最新的 ``` 在main.js 中引入上面的 jQuery: ``` import $ from 'jQuery'; ``` 然后我们就可以通过`$`这个符号去写jQuery的代码了。 ================================================ FILE: 11-Node.js/KOA2.md ================================================ --- title: 01-数据库的基础知识 publish: false --- ## KOA2 简介 KOA已经发展到了第二个版本,简称 KOA2。突出的特点是插件和中间件, ================================================ FILE: 11-Node.js/Node.js代码举例.md ================================================ --- title: 01-数据库的基础知识 publish: false --- ## 在 Node.js 上建一个 http 服务器 (1)新建一个文件 `server01.js`,然后在里面输入如下代码: ```javascript const http = require('http'); //引入 node.js里面的一个http包。因为引入之后,我们不会去修改它,所以用常量来表示 // 创建一台服务器 var server = http.createServer(function (){ //当有人来访问这台服务器时,就会执行 function 回调函数 console.log('有人来访问我了'); }); server.listen(8080); //要让服务器设置为监听状态,端口设置为8080 ``` 注意看注释。 我们把上面这个 js 文件跑起来,然后在浏览器端输入`http://localhost:8080/`,每请求一次,服务器的控制台就会打印 `有人来访问我了`。 (2)write()函数和 end()函数: 将上面的代码修改如下: server02.js: ```javascript const http = require('http'); // 创建一台服务器 var server = http.createServer(function (request, response) { //当有人来访问这个服务器时,就会执行function 这个回调函数 console.log('有人来访问我了'); response.write('smyhvae'); //向浏览器输出内容 response.end(); //结束了,浏览器你走吧。 }); server.listen(8080); ``` function 回调函数里可以设置两个参数:request 和 response。`response.write()`表示向浏览器输出一些内容。 将上面的 js 代码跑起来,产生的问题是,无论我们在浏览器端输入`http://localhost:8080/1.html`,还是输入`http://localhost:8080/2.jpg`,浏览器上显示的都是`smyhvae`。 ================================================ FILE: 11-Node.js/WebSocket.md ================================================ --- title: 01-数据库的基础知识 publish: false --- ## WebSocket 的引入 ### 背景分析 HTTP协议是无状态的,服务器只会响应来自客户端的请求,但是它与客户端之间不具备持续连接。 当用户在浏览器上进行操作时,可以请求服务器上的api;但是反过来却不可能:服务器端发生了一个事件,无法将这个事件的信息**实时主动**地通知客户端。只有在客户端查询服务器当前状态时,所发生事件的信息才会从服务器传递到客户端。 那怎么去实时地知道服务器的状态呢?方法有两个: (1)**轮询**:客户端每隔很短的时间,都会对服务器发出请求,查看是否有新的消息,只要轮询速度足够快,例如1秒,就能给人造成交互是实时进行的印象。这种做法是无奈之举,实际上对服务器、客户端双方都造成了大量的性能浪费。 (2)**长连接**:客户端只请求一次,但是服务器会将连接保持,不会返回结果。当服务器有了新数据时,实时地发给客户端,而一直保持挂起状态。这种做法的也造成了大量的性能浪费。 ### WebSocket 协议 最新的 HTML5协议,制定了 WebSocket 协议标准,允许客户端和服务器端以**全双工**的方式进行通信。 WebSocket 的原理非常简单:利用HTTP请求产生握手,HTTP头部含有 WebSocket 协议的请求,**握手之后,二者转用TCP协议进行交流*(QQ的协议)。 WebSocket协议需要浏览器和服务器都支持才可以使用: - 支持WebSocket协议的浏览器有:Chrome 4、火狐4、IE10、Safari5 - 支持WebSocket协议的服务器有:Node 0、Apach7.0.2、Nginx1.3 ### http 长连接和 websocket 的长连接区别 HTTP1.1通过使用Connection:keep-alive进行长连接,HTTP 1.1默认进行持久连接。在一次 TCP 连接中可以完成多个 HTTP 请求,但是对每个请求仍然要单独发 header,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。 websocket是一个真正的全双工。长连接第一次tcp链路建立之后,后续数据可以双方都进行发送,**不需要发送请求头**。 keep-alive双方并没有建立正真的连接会话,服务端可以在任何一次请求完成后关闭。WebSocket 它本身就规定了是正真的、双工的长连接,两边都必须要维持住连接的状态。 ### Socket.IO 的引入 Node.js上需要写一些程序,来处理TCP请求。 Node.js从诞生之日起,就支持 WebSocket 协议。不过,从底层一步一步搭建一个Socket服务器很费劲(想象一下Node写一个静态文件服务都那么费劲)。所以,有大神帮我们写了一个库 Socket.IO。 Socket.IO 是业界良心,新手福音。它屏蔽了所有底层细节,让顶层调用非常简单。并且还为不支持 WebSocket 协议的浏览器,提供了长轮询的透明模拟机制。 Node的单线程、非阻塞I/O、事件驱动机制,使它非常适合Socket服务器。 ### Socket.IO 的安装 Socket.IO 的官网是: 安装方式: ``` npm install socket.io ``` ================================================ FILE: 11-Node.js/事件驱动和非阻塞机制.md ================================================ --- title: 01-数据库的基础知识 publish: false --- ## 异步编程 ### 异步操作 - Node 采用 Chrome V8 引擎处理 JavaScript 脚本。V8 最大特点就是**单线程运行**,一次只能运行一个任务。 - Node 大量采用异步操作(asynchronous operation),即任务不是马上执行,而是插在任务队列的尾部,等到前面的任务运行完后再执行。 - 提高代码的响应能力。 异步IO也叫非阻塞IO。例如读文件,传统的语言,基本都是读取完毕才能进行下一步操作。非阻塞就是Node的callback,不会影响下一步操作,等到文件读取完毕,回调函数自动被执行,而不是在等待。 ### 异步操作回调 由于系统永远不知道用户什么时候会输入内容,所以代码不能永远停在一个地方。 Node 中的操作方式就是以异步回调的方式解决无状态的问题。 ### 回调函数的设计:错误优先 异步操作中,无法通过 try catch 捕获异常。 这是因为回调函数主要用于异步操作,当回调函数运行时,前期的操作早结束了,错误的执行栈早就不存在了,传统的错误捕捉机制try…catch对于异步操作行不通,所以只能把错误交给回调函数处理。 **统一约定:** 回调函数的第一个参数默认接收错误信息,第二个参数才是真正的回调数据(便于外界获取调用的错误情况): ``` foo1('赵小黑', 19, function(error, data) { if(error) throw error; console.log(data); }); ``` ### 异步回调的问题 相比较于传统的代码: - 异步事件驱动的代码 - 不容易阅读 - 不容易调试 - 不容易维护 另外还有个问题是**回调地狱:** ```javascript do1(function() { do2(function() { do3(function() { do4(function() { do5(function() { do6() }); }); }); }); }); ``` ## 进程和线程 ### 进程(进行中的程序) - 每一个 **正在运行** 的应用程序都称之为进程。 - 每一个应用程序运行都至少有一个进程。 - 进程是用来给应用程序提供一个运行的环境。 - 进程是操作系统为应用程序分配资源的一个单位。 ### 线程 - 用来执行应用程序中的代码 - 在一个进程内部,可以有很多的线程 - 在一个线程内部,同时只可以干一件事 - 传统的开发方式大部分都是 I/O 阻塞的,所以需要多线程来更好的利用硬件资源。 线程并不是越多越好。 ### 多线程的弊端 缺点一: - 创建线程耗费。 - 线程数量有限。 - CPU 在不同线程之间转换,有个上下文转换,这个转换非常耗时。 所谓的多线程其实都是假的,对于单核CPU而言,它们无非是在抢占 CPU 资源。线程和线程之间需要**切换和调度**,这是很耗费资源的。 缺点二: - 线程之间共享某些数据,同步某个状态都很麻烦。 就算 CPU 是多核的,现在的问题是,线程与线程之间如果要共享数据,该怎么办?比如 A 线程要访问 B 线程的变量。 ## 事件驱动和非阻塞机制 参考链接: 总结: - Node 中将所有的阻塞操作交给了内部线程池实现。 - Node 主线程本身,主要就是不断的**往返调度**。 ### 平台实现差异 由于 Windows 和 *nix 平台(其他平台)的差异,Node 提供了 libuv 作为抽象封装层,保证上层的 Node 与下层的自定义线程池及 IOCP 之间各自独立。 如下图所示: ![](http://img.smyhvae.com/20180301_2252.png) ================================================ FILE: 12-Vue基础/01-Vue的介绍和vue-cli.md ================================================ --- title: 01-Vue的介绍和vue-cli publish: true --- ## MVVM模式 ![](http://img.smyhvae.com/20180420_2150.png) - Model:负责数据存储 - View:负责页面展示 - View Model:负责业务逻辑处理(比如Ajax请求等),对数据进行加工后交给视图展示 ## 关于框架 ### 为什么要学习流行框架 **1、企业为了提高开发效率**:在企业中,时间就是效率,效率就是金钱;企业中,使用框架,能够提高开发的效率。 **提高开发效率的发展历程**: 原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js(能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念) 2、在Vue中,一个核心的概念就是:数据驱动,避免手动操作DOM元素。这样的话,可以让前端程序员可以更多的时间去关注数据的业务逻辑,而不是关心 DOM 是如何渲染的了。 ### 框架和库的区别 **框架**: 框架是一套完整的解决方案。 对项目的**侵入性**较大,项目如果需要更换框架,则需要重新架构整个项目。但是优点也很明显:功能完善、提供了一整套的解决方案。 **库(插件)**: 只是提供某一个小功能。 对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。 举例: - 从Jquery 切换到 Zepto - 从 EJS 切换到 art-template ## 前端的各种框架 ### Vue 和 React 的相同点 - 利用虚拟DOM实现快速渲染 - 轻量级 - 响应式组件 - 支持服务器端渲染 - 易于集成路由工具、打包工具以及状态管理工具 PS:Vue 在国内很受欢迎;React 在国内和国外都很受欢迎,适合做大型网站。 ### 什么是虚拟 DOM 传统的web开发,是利用 jQuery操作DOM,这是非常耗资源的。 我们可以在 JS 的内存里构建类似于DOM的对象,去拼装数据,拼装完整后,把数据整体解析,一次性插入到html里去。这就形成了虚拟 DOM。 Vue1.0没有虚拟DOM,Vue2.0改成了基于虚拟DOM。 ### 前端框架回顾 ![](http://img.smyhvae.com/20180302_1645.png) ![](http://img.smyhvae.com/20180302_1651.png) ![](http://img.smyhvae.com/20180302_1652.png) Vue框架中,没有控制器。 ## Vue 框架 ### 发展历史 - 2013年底作为尤雨溪个人实验项目开始开发 - 2014年2月公开发布。 - 2014年11月发布0.11版本 - 2016年10月发布2.0版本。 ### 相关网址 - [中文官网](https://cn.vuejs.org/) - [vuejs官方论坛](https://forum.vuejs.org/) - GitHub地址: - Vue1.0 在线文档: - Vue2.x 在线文档: - Vue1下载地址: - Vue2下载地址: ![](http://img.smyhvae.com/20180302_1658.png) 上方截图的时间:2018-03-02。 ### 介绍 Vue 本身并不是一个框架,Vue结合周边生态构成一个灵活的、渐进式的框架。 Vue 以及大型 Vue 项目所需的周边技术,构成了生态。 渐进式框架图: ![](http://img.smyhvae.com/20180302_1701.png) ### Vue框架的特点 - 模板渲染:基于 html 的模板语法,学习成本低。 - 响应式的更新机制:数据改变之后,视图会自动刷新。【重要】 - 渐进式框架 - 组件化/模块化 - 轻量:开启 gzip压缩后,可以达到 20kb 大小。(React 达到 35kb,AngularJS 达到60kb)。 ## Vue 的环境搭建 > 我们首先要安装好 NVM、Node.js环境,然后再来做下面的操作。 ### 常见的插件 - Webpack:代码模块化构建打包工具。 - Gulp:基于流的自动化构建工具。 - Babel:使用最新的 规范来编写 js。 - Vue:构建数据驱动的Web界面的渐进式框架 - Express:基于 Node.js 平台,快速、开放、极简的 Web 开发框架。 以上这些包,都可以通过 NPM 这个包管理工具来安装。 ### 引用 Vue.js 文件 1、**方式一**:(CDN的方式进行引用) ```html Title ``` 2、方式二:(下载 vue.js 文件) 去网站 下载 vue.js 文件,直接放到工程文件里,然后引用。 3、方式三:(NPM的方式安装vue) ```bash # 最新稳定版 $ npm install vue ``` 如果网络不稳定,可以采用下面的方式安装: ``` $ cnpm i vue --save ``` 然后在代码中通过下面这种方式进行引用: ```javascript import Vue from 'vue' ``` ## 利用 vue-cli 新建一个空的项目 Vue 提供一个官方命令行工具,可用于快速搭建大型单页应用。该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目。 ### 官方代码参考 ``` npm install -g @vue/cli vue create my-app cd my-app npm run serve ``` 我们根据上方的参考代码,来看看“利用 vue-cli 新建一个空的项目”的步骤。 ### 安装 vue-cli(命令行工具) 安装命令如下: ```bash # 全局安装 vue-cli $ npm install -g @vue/cli ``` ### 初始化一个 simple 项目 (1)首先执行: ``` vue create my-app ``` 输入上方命令后,会弹出一个选项: ![](http://img.smyhvae.com/20190624_163626.png) 如果是初学者,直接选`default`就行。之后会自动生成一个空的初始化项目,包含了项目目录、以及项目依赖的脚本。 这个空项目的工程文件如下:(请务必仔细研究这个项目的写法和目录结构) - [2019-06-21-vue-my-app.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-06-21-vue-my-app.zip) 我们可以看到这个项目的结构: ![](http://img.smyhvae.com/20190624_160726.png) - src:项目源码 - .babelrc:ES6编译插件的配置 - index.html:单页面的入口 上方截图中,`npm install `指的是下载各种依赖包,`npm run dev`指的是打开发包,`npm run build`指的是打生产包。 (2)本地运行项目: ``` cd my-app npm run serve ``` 浏览器输入`http://localhost:8080/`,就可以让这个空的项目在本地跑起来: ![](http://img.smyhvae.com/20190624_160229.png) 备注:我们在 GitHub上下载的任何Vue有关的项目,第一步都是要首先执行 npm install,安装依赖的 mode_modules,然后再运行。我们发给同事的工程文件,建议不要包含 `node_modules`。 ### 构建一个 非 simple 项目 构建一个空的项目,首先执行: ``` $ vue create vuedemo2 ``` ![](http://img.smyhvae.com/20190624_163726.png) 上图中,选择 `Manually select features`,然后根据提示依次输入: ![](http://img.smyhvae.com/20190624_164305.png) - project name:**要求小写**。 - description:默认即可。 - vue-router:需要。 - ESlint:语法检查,初学者可以暂时不需要。 - 单元测试:暂时也不需要。 - e2e test:不需要。 选择 eslint 的配置: ![](http://img.smyhvae.com/20190624_165001.png) 然后让这个空的项目就可以在浏览器上跑起来。 ## vue 项目结构分析 ![](http://img.smyhvae.com/20180501_2100.png) - buid:打包配置的文件夹 - config:webpack对应的配置 - src:开发项目的源码 - App.vue:入口组件。`.vue`文件都是组件。 - main.js:项目入口文件。 - static:存放静态资源 - `.babelrc`:解析ES6的配置文件 - `.editorcofnig`:编辑器的配置 - `.postcssrc.js`:html添加前缀的配置 - `index.html`:单页面的入口。通过 webpack打包后,会把 src 源码进行编译,插入到这个 html 里面来。 - `package.json`:项目的基础配置,包含版本号、脚本命令、项目依赖库、开发依赖库、引擎等。 ### 图片的base64编码 默认是10k以下,建议都通过 base64编码。在配置文件`webpack.base.conf.js`中进行修改: ``` { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } ``` ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/20160401_01.jpg) ================================================ FILE: 12-Vue基础/02-Vue的系统指令.md ================================================ --- title: 02-Vue的系统指令 publish: true --- > 本文最初发表于[博客园](),并在[GitHub](https://github.com/smyhvae/Web)上持续更新**前端的系列文章**。欢迎在GitHub上关注我,一起入门和进阶前端。 > 以下是正文。 ## 本文主要内容 - 插值表达式 {{}} - v-cloak - v-text - v-html - v-bind - v-on - 举例:文字滚动显示(跑马灯效果) - v-on的事件修饰符 ## Vue初体验 新建一个空的项目,引入`vue.js`文件。写如下代码: ```html Title
    {{name}}
    ``` 显示效果: ![](http://img.smyhvae.com/20180313_0955.png) 如果我们在控制台输入`myVue.$data.name = 'haha'`,页面会**自动更新**name的值。意思是,当我们直接修改data数据,页面会自动更新,而不用去操作DOM。 下面来讲一下Vue的各种系统指令。 ## 插值表达式 {{}} 数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值。例如: ```html Message: {{ msg }} ``` Mustache 标签将会被替代为对应数据对象上 msg 属性(msg定义在data对象中)的值。 无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会**自动更新**。 `{{}}`对JavaScript 表达式支持,例如: ```javascript {{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ name == 'smyhvae' ? 'true' : 'false' }} {{ message.split('').reverse().join('') }} ``` 但是有个限制就是,每个绑定都**只能包含单个表达式**,如下表达式无效: ```html {{ var a = 1 }} {{ if (ok) { return message } }} ``` 代码举例: ```html Document
    content:{{name}}
    ``` 运行结果: ![](http://img.smyhvae.com/20180506_2240.png) ## v-cloak `v-cloak`:保持和元素实例的关联,直到结束编译后自动消失。 v-cloak指令和CSS 规则一起用的时候,能够**解决插值表达式闪烁的问题**(即:可以隐藏未编译的标签直到实例准备完毕)。 就拿上一段代码来举例,比如说,`{{name}}`这个内容,**在网速很慢的情况下,一开始会直接显示`{{name}}`这个内容**,等网络加载完成了,才会显示`smyhvae`。那这个**闪烁的问题**该怎么解决呢? 解决办法是:通过`v-cloak`隐藏`{{name}}`这个内容,当加载完毕后,再显示出来。 ```html Document
    {{name}}
    ``` ## v-text v-text可以将一个变量的值渲染到指定的元素中。例如: ```html Title
    ``` 结果: ![](http://img.smyhvae.com/20180313_1645.png) ### 插值表达式和 v-text 的区别 ```html content:{{name}} /span> ``` **区别1**: v-text 没有闪烁的问题,因为它是放在属性里的。 **区别2** :插值表达式只会替换自己的这个占位符,并不会把整个元素的内容清空。v-text 会**覆盖**元素中原本的内容。 为了解释区别2,我们来用代码举例: ```html

    content:++++++{{name}}------

    ------++++++

    ``` 上方代码的演示结果: ![](http://img.smyhvae.com/20180506_2320.png) 其实,第二行代码中,只要浏览器中还没有解析到`v-text="name"`的时候,会显示`------++++++`;当解析到`v-text="name"`的时候,name的值会直接替换`------++++++`。 ## v-html `v-text`是纯文本,而`v-html`会被解析成html元素。 注意:使用v-html渲染数据可能会非常危险,因为它很容易导致 XSS(跨站脚本) 攻击,使用的时候请谨慎,能够使用{{}}或者v-text实现的不要使用v-html。 代码举例: ```html Document

    {{msg}}

    ``` 运行结果: ![](http://img.smyhvae.com/20180506_2330.png) ## v-bind:属性绑定机制 `v-bind`:用于绑定**属性**。 比如说: ```html
    ``` 上方代码中,给属性加了 v-bind 之后,属性值里的整体内容是**表达式**,属性值里的`imageSrc`和`size`是Vue实例里面的**变量**。 也就是说, v-bind的属性值里,可以写合法的 js 表达式。 上面两行代码也可以简写成: ```html
    ``` **举例:** ```html Title ``` 上面的代码中,我们给`value`这个属性绑定了值,此时这个值是一个变量。 效果: ![](http://img.smyhvae.com/20180313_1745.png) ## v-on:事件绑定机制 ### `v-on:click`:点击事件 ```html Title
    {{name}}
    ``` 上方代码中,我们给button按钮绑定了点击事件。注意,这个button标签要写在div区域里(否则点击事件不生效),因为下方的View module接管的是div区域。 ### `v-on`的简写形式 例如: ```html ``` 可以简写成: ```html ``` ### v-on的常用事件 v-on 提供了click 事件,也提供了一些其他的事件。 - v-on:click - v-on:keydown - v-on:keyup - v-on:mousedown - v-on:mouseover - v-on:submit - .... ## 举例:文字滚动显示(跑马灯效果) 我们利用上面几段所学的内容,做个跑马灯的小例子。要实现的效果是:类似于LED屏幕上,滚动显示的文字。 **文字滚动显示的思路**: (1)每次点击按钮后,拿到 msg 字符串,然后调用字符串的`substring`来进行字符串的截取操作,把第一个字符截取出来,放到最后一个位置即可。这就实现了滚动的效果。 (2)为了实现文字**自动连续滚动**的效果,需要把步骤(1)中点击按钮的操作,放到**定时器**中去。 我们先来看一下 点击事件里的代码改怎么写。 **步骤 1**:每次点击按钮,字符串就滚动一次。代码如下: ```javascript startMethod: function () { // 获取 msg 的第一个字符 var start = this.msg.substring(0, 1); // 获取 后面的所有字符 var end = this.msg.substring(1); // 重新拼接得到新的字符串,并赋值给 this.msg this.msg = end + start; } ``` **步骤2**:给上面的操作添加定时器。代码如下: ```javascript startMethod: function () { var _this = this; //添加定时器:点击按钮后,让字符串连续滚动 setInterval(function () { // 获取 msg 的第一个字符 var start = _this.msg.substring(0, 1); // 获取 后面的所有字符 var end = _this.msg.substring(1); // 重新拼接得到新的字符串,并赋值给 this.msg // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去 _this.msg = end + start; console.log(_this.msg); }, 400); } ``` 上面的代码中,我们发现,如果在定时器中直接使用this,这个this指向的是window。为了解决这个问题,我们是通过`_this`来解决了这个问题。 另外,我们还可以利用箭头函数来解决this指向的问题,因为箭头函数总的this指向,会继承外层函数的this指向。如下。 **步骤2的改进版**:用箭头函数来改进定时器,解决this指向的问题。代码如下: ```javascript startMethod: function () { //添加定时器:点击按钮后,让字符串连续滚动 setInterval(() => { // 获取 msg 的第一个字符 var start = this.msg.substring(0, 1); // 获取 后面的所有字符 var end = this.msg.substring(1); // 重新拼接得到新的字符串,并赋值给 this.msg // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去 this.msg = end + start; console.log(_this.msg); }, 400); } ``` **步骤3**:停止定时器。如下: 我们还需要加一个按钮,点击按钮后,停止文字滚动。也就是停止定时器。 提示:我们最好把定时器的id放在全局的位置(放到data里),这样的话,开启定时器的方法和停止定时器的方法,都可以同时访问到这个定时器。 代码如下: ```javascript data: { msg: '千古壹号,前端教程~~~', intervalId: null }, methods: { startMethod: function () { //添加定时器:点击按钮后,让字符串连续滚动 this.intervalId = setInterval(() => { //【注意】这个定时器的this,一定不要忘记了 // 获取 msg 的第一个字符 var start = this.msg.substring(0, 1); // 获取 后面的所有字符 var end = this.msg.substring(1); // 重新拼接得到新的字符串,并赋值给 this.msg // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去 this.msg = end + start; console.log(_this.msg); }, 400); }, stopMethod: function () { //停止定时器:点击按钮后,停止字符串的滚动 clearInterval(this.intervalId); } } ``` **【重要】步骤4**:一开始的时候,还需要判断是否已经存在定时器。如下: 步骤3中的代码,虽然做了停止定时器的操作,但是有个问题:在连续多次点击“启动定时器”按钮的情况下,此时再点击“停止定时器”的按钮,是没有反应的。因此,我们需要改进的地方是: - **在开启定时器之前,先做一个判断**:如果定时器不为 null,就不继续往下执行了(即不再开启新的定时器),防止开启了多个定时器。 - **停止定时器的时候,虽然定时器停止了,但定时器并不为 null**。因此,最后我们还需要手动将定时器设置为null。这样,才能恢复到最初始的状态。 **完整版代码**: 针对上面的四个步骤,为了实现这个案例,完整版代码如下: ```html Document

    {{msg}}

    ``` **上方代码的总结**: - 在Vue的实例中,如果想要获取data里的属性、methods里面的方法,都要通过`this`来访问。这里的**this指向的是Vue的实例对象**。 - VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把最新的数据,从 data 上同步到页面中去。这样做 的好处是:**程序员只需要关心数据,不需要考虑如何重新渲染DOM页面;减少DOM操作**。 - 在调用定时器 id 的时候,代码是`this.intervalId`,这个`this`一定不要漏掉了。 ## 我的公众号 想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: ![](http://img.smyhvae.com/2016040102.jpg) ================================================ FILE: 12-Vue基础/03-v-on的事件修饰符.md ================================================ --- title: 03-v-on的事件修饰符 publish: true --- ## v-on的事件修饰符 ### v-on的常见事件修饰符 `v-on` 提供了很多事件修饰符来辅助实现一些功能。事件修饰符有如下: - `.stop` 阻止冒泡。本质是调用 event.stopPropagation()。 - `.prevent` 阻止默认事件(默认行为)。本质是调用 event.preventDefault()。 - `.capture` 添加事件监听器时,使用捕获的方式(也就是说,事件采用捕获的方式,而不是采用冒泡的方式)。 - `.self` 只有当事件在该元素本身(比如不是子元素)触发时,才会触发回调。 - `.once` 事件只触发一次。 - `.{keyCode | keyAlias}` 只当事件是从侦听器绑定的元素本身触发时,才触发回调。 - `.native` 监听组件根元素的原生事件。 PS:一个事件,允许同时使用多个事件修饰符。 写法示范: ```html
    ``` ### `.stop`的举例 我们来看下面这个例子: ```html Document
    ``` 上方代码中,存在冒泡的现象,父标签中包含了一个子标签。当点击子标签时,父标签也会被触发。打印顺序是: ``` child 被点击了 father 被点击了 ``` 那么问题来了,如果我不想让子标签的点击事件冒泡到父亲,该怎么做呢?办法是:给子标签加一个**事件修饰符**`.stop`,阻止冒泡。代码如下: ```html
    ``` 阻止冒泡后,当点击子标签时,打印结果是: ``` child 被点击了 ``` PS:我发现一个有意思的现象。上方的这行代码中,如果把`.stop`改为`:stop`,造成的现象是,父标签被触发了,而子标签没有被触发。 ### `.capture`举例 `.capture`:触发事件时,采用捕获的形式,而不是冒泡的形式。 还是采用上面的例子:当按钮点击时,如果想要采取捕获的方式,而不是冒泡的方式,办法是:可以直接在父标签上加事件修饰符`.capture`。代码如下: ```html
    ``` 当点击子标签时,打印结果是: ``` father 被点击了 child 被点击了 ``` ### `.prevent`的举例1 比如说,超链接``默认有跳转行为,那我可以通过事件修饰符`.prevent`阻止这种跳转行为。 ```html Document ``` 上方代码中: - 如果去掉`.prevent`,点击按钮后,既会打印log,又会跳转到百度页面。 - 现在加上了`.prevent`,就只会打印log,不会跳转到百度页面。 ### `.prevent`的举例2 现在有一个form表单: ```html
    ``` 我们知道,上面这个表单因为`type="submit"`,因此它是一个提交按钮,点击按钮后,这个表单就会被提交到form标签的action属性中指定的那个页面中去。这是表单的默认行为。 现在,我们可以用`.prevent`来阻止这种默认行为。修改为:点击按钮后,不提交到服务器,而是执行我们自己想要的事件(在submit方法中另行定义)。如下: ```html Document
    ``` 上方代码中,我们通过`.prevent`阻止了提交按钮的默认事件,点击按钮后,执行的是`mySubmit()`方法里的内容。这个方法名是可以随便起的,我们甚至可以起名为`submit`,反正默认的submit已经失效了。 ### `.self`举例 - `.self` 只有当事件在该元素本身(比如不是子元素)触发时,才会触发回调。 我们知道,在事件触发机制中,当点击子标签时,父标签会通过冒泡的形式被触发(父标签本身并没有被点击)。可如果我给父标签的点击事件设置`.self`修饰符,达到的效果是:子标签的点击事件不会再冒泡到父标签了,只有点击符标签本身,父标签的事件才会被触发。代码如下: ```html
    ``` **疑问**:既然`.stop`和`.self`都可以阻止冒泡,那二者有什么区别呢?区别在于:前者能够阻止整个冒泡行为,而后者只能阻止自己身上的冒泡行为。 ================================================ FILE: 12-Vue基础/04-Vue的系统指令(二).md ================================================ --- title: 03-v-on的事件修饰符 publish: true --- ## 前言 本文主要内容: - v-model - v-for - v-if - v-show ## v-model:双向数据绑定 > 重点:**双向数据绑定,只能用于表单元素,或者用于自定义组件**。 之前的文章里,我们通过v-bind,给``标签绑定了`data`对象里的`name`属性。当`data`里的`name`的值发生改变时,``标签里的内容会自动更新。 可我现在要做的是:我在``标签里修改内容,要求`data`里的`name`的值自动更新。从而实现双向数据绑定。该怎么做呢?这就可以利用`v-model`这个属性。 **区别**: - v-bind:只能实现数据的**单向**绑定,从 M 自动绑定到 V。 - v-model:只有`v-model`才能实现**双向**数据绑定。注意,v-model 后面不需要跟冒号, **注意**:v-model 只能运用在**表单元素**中,或者用于自定义组件。常见的表单元素包括:input(radio, text, address, email....) 、select、checkbox 、textarea。 代码举例如下: ```html Document
    ``` 此时,便可实现我们刚刚要求的双向数据绑定的效果。 ## v-model举例:实现简易计算器 题目:现在两个输入框,用来做加减乘除,将运算的结果放在第三个输入框。 实现代码如下: ```html Document
    ``` 注意上方代码中的注释,可以了解下`eval()`的用法。 ## Vue中通过属性绑定为元素设置class 类样式 注意,是**类样式**。 ### 引入 我们先来看下面这段代码: ```html Document

    我是千古壹号,qianguyihao

    ``` 上面的代码中,我们直接通过正常的方式,给`

    `标签设置了两个 class 类的样式。代码抽取如下: ```html

    我是千古壹号,qianguyihao

    ``` 上面的效果,我们还可以用Vue来写。这就引入了本段要讲的方式。 ### 方式一:数组 **方式一**:直接传递一个数组。注意:这里的 class 需要使用 v-bind 做数据绑定。 代码如下: ```html Document

    我是千古壹号,qianguyihao

    我是qianguyihao,千古壹号

    ``` 代码抽取如下: ```html

    我是qianguyihao,千古壹号

    ``` 上方代码中,注意,数组里写的是字符串;如果不加单引号,就不是字符串了,而是变量。 演示效果如下: ![](http://img.smyhvae.com/20180509_1058.png) ### 写法二:在数组中使用三元表达式 ```html

    我是qianguyihao,千古壹号

    ``` 上方代码的意思是,通过data中布尔值 flag 来判断:如果 flag 为 true,就给 h1 标签添加`my-active`样式;否则,就不设置样式。 注意,三元表达式的格式不要写错了。 ### 写法三:在数组中使用 对象 来代替 三元表达式(提高代码的可读性) 上面的写法二,可读性较差。于是有了写法三。 **写法三**:在数组中使用**对象**来代替**三元表达式**。 代码如下: ```html

    我是qianguyihao,千古壹号

    ``` ### 写法四:直接使用对象 写法四:直接使用对象。代码如下: ```html

    我是qianguyihao,千古壹号

    ``` 上方代码的意思是,给`

    `标签使用样式`style1`,不使用样式`style2`。注意: 1、既然class样式名是放在对象中的,这个样式名不能有中划线,比如说,写成`:class="{my-red:true, my-active:false}`,是会报错的。 2、我们也可以对象通过存放在 data 的变量中。也就是说,上方代码可以写成: ```html

    我是qianguyihao,千古壹号

    ``` ## Vue中通过属性绑定为元素设置 style 行内样式 注意,是行内样式(即内联样式)。 ### 写法一 **写法一**:直接在元素上通过 `:style` 的形式,书写样式对象。 例如: ```html

    我是千古壹号,qianguyihao

    ``` ### 写法二 **写法二**:将样式对象,定义到 `data` 中,并直接引用到 `:style` 中。 也就是说,把写法一的代码改进一下。代码如下: ```html

    我是千古壹号,qianguyihao

    ``` ### 写法三 写法二只用到了一组样式。如果想定义**多组**样式,可以用写法三。 **写法三**:在 `:style` 中通过数组,引用多个 `data` 上的样式对象。 代码如下: ```html

    我是千古壹号,qianguyihao

    ``` ## v-for:for循环的四种使用方式 **作用**:根据数组中的元素遍历指定模板内容生成内容。 ### 引入 比如说,如果我想给一个`ul`中的多个`li`分别赋值1、2、3...。如果不用循环,就要挨个赋值: ```html
    • {{list[0]}}
    • {{list[1]}}
    • {{list[2]}}
    ``` 效果: ![](http://img.smyhvae.com/20180329_1713.png) 为了实现上面的效果,如果我用`v-for`进行赋值,代码就简洁很多了: ```html
    • {{item}}
    ``` 接下来,我们详细讲一下`v-for`的用法。需要声明的是,Vue 1.0的写法和Vue 2.0的写法是不一样的。本文全部采用Vue 2.0的写法。 ### 方式一:普通数组的遍历 针对下面这样的数组: ```html ``` 将数组中的**值**赋给li: ```html
  • {{item}}
  • ``` 将数组中的**值和index**赋给li: ```html
  • 值:{{item}} --- 索引:{{index}}
  • ``` 效果如下: ![](http://img.smyhvae.com/20180329_1856.png) ### 方式二:对象数组的遍历 ```html Document
    • 姓名:{{item.name}} --- 年龄:{{item.age}} --- 索引:{{index}}
    ``` 效果如下: ![](http://img.smyhvae.com/20180509_1500.png) ### 方式三:对象的遍历 针对下面这样的对象: ```html ``` 将上面的`obj1`对象的数据赋值给li,写法如下: ```html
    • 值:{{value}} --- 键:{{key}}
    • ---分隔线---

    • 值:{{value}} --- 键:{{key}} --- index:{{index}}
    ``` 效果如下: ![](http://img.smyhvae.com/20180329_1850.png) ### 方式四:遍历数字 `in`后面还可以直接放数字。举例如下: ```html
    • 这是第 {{myCount}}次循环
    ``` 效果如下: ![](http://img.smyhvae.com/20180509_1505.png) ### v-for中key的使用注意事项 **注意**:在 Vue 2.2.0+ 版本里,当在**组件中**使用 v-for 时,key 属性是必须要加上的。 这样做是因为:每次 for 循环的时候,通过指定 key 来标示当前循环这一项的**唯一身份**。 > 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “**就地复用**” 策略。如果数据项的顺序被改变,Vue将**不是移动 DOM 元素来匹配数据项的顺序**, 而是**简单复用此处每个元素**,并且确保它在特定索引下显示已被渲染过的每个元素。 > 为了给 Vue 一个提示,**以便它能跟踪每个节点的身份,从而重用和重新排序现有元素**,你需要为每项提供一个唯一 key 属性。 key的类型只能是:string/number,而且要通过 v-bind 来指定。 代码举例: ```html Document

    {{item.id}} --- {{item.name}}

    ``` ## v-if:设置元素的显示和隐藏(添加/删除DOM元素) **作用**:根据表达式的值的真假条件,来决定是否渲染元素,如果为false则不渲染(达到隐藏元素的目的),如果为true则渲染。 在切换时,元素和它的数据绑定会被销毁并重建。 举例如下:(点击按钮时,切换和隐藏盒子) ```html Document
    我是盒子
    ``` 效果如下: ![](http://img.smyhvae.com/20180329_1920.gif) ## v-show:设置元素的显示和隐藏(在元素上添加/移除`style="display:none"`属性) **作用**:根据表达式的真假条件,来切换元素的 display 属性。如果为false,则在元素上添加 `display:none`属性;否则移除`display:none`属性。 举例如下:(点击按钮时,切换和隐藏盒子) 我们直接把上一段代码中的`v-if`改成`v-show`就可以了: ```html Document
    我是盒子
    ``` 效果如下: ![](http://img.smyhvae.com/20180329_2040.gif) ### v-if和v-show的区别 `v-if`和`v-show`都能够实现对一个元素的隐藏和显示操作。 区别: - v-if:每次都会重新添加/删除DOM元素 - v-show:每次不会重新进行DOM的添加/删除操作,只是在这个元素上添加/移除`style="display:none"`属性,表示节点的显示和隐藏。 优缺点: - v-if:有较高的切换性能消耗。这个很好理解,毕竟每次都要进行dom的添加/删除操作。 - v-show:**有较高的初始渲染消耗**。也就是说,即使一开始`v-show="false"`,该节点也会被创建,只是隐藏起来了。而`v-if="false"`的节点,根本就不会被创建。 **总结**: - 如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show - 如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if ================================================ FILE: 12-Vue基础/05-Vue的举例:列表功能.md ================================================ --- title: 05-Vue的举例:列表功能 publish: true --- ## 列表功能举例 ### 步骤 1:列表功能 完整的代码如下: ```html Document
    编号 名称 创建时间 操作
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` **代码分析**:数据是存放在data的list中的,将data中的数据通过`v-for`遍历给表格。 上方代码运行的效果: ![](http://img.smyhvae.com/20180401_1517.png) ### 步骤 2:无数据时,增加提示 如果list中没有数据,那么表格中就会只显示表头``,这样显然不太好看。 为此,我们需要增加一个`v-if`判断:当数据为空时,显示提示。如下: ```html 列表无数据 ``` 代码解释:`colspan="4"`指的是让当前这个``横跨4个单元格的位置。如下: ![](http://img.smyhvae.com/20180401_1535.png) ### 步骤 3:item的添加 具体实现步骤如下: (1)用户填写的数据单独存放在data属性里,并采用`v-model`进行双向绑定。 (2)用户把数据填好后,点击add按钮。此时需要增加一个点击事件的方法,将data中的数据放到list中(同时,清空文本框中的内容)。 (3)将数据展示出来。`v-for`有个特点:当list数组发生改变后,vue.js就会自动调用`v-for`重新将数据生成,这样的话,就实现了数据的自动刷新。 完整的代码如下: ```html Document
    编号: 名称:
    编号 名称 创建时间 操作
    列表无数据
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` ### 步骤 4:item的删除 html部分: ```html 删除 ``` js部分: ```javascript delData: function (id) { // 0 提醒用户是否要删除数据 if (!confirm('是否要删除数据?')) { //当用户点击的取消按钮的时候,应该阻断这个方法中的后面代码的继续执行 return; } // 1 调用list.findIndex()方法根据传入的id获取到这个要删除数据的索引值(在数组中的索引值) var index = this.list.findIndex(function (item) { return item.id == id }); // 2.0 调用方法:list.splice(待删除的索引, 删除的元素个数) this.list.splice(index, 1); } ``` 代码解释:`find()`和`findIndex()`是ES6中为数组新增的函数。详细解释如下: ```javascript // 根据id得到下标 // 默认去遍历list集合,将集合中的每个元素传入到function的item里, var index = this.list.findIndex(function(item){ //根据item中的id属性去匹配传进来的id //如果是则返回true ;否返回false,继续下面的一条数据的遍历,以此类推 return item.id ==id; //如果返回true,那么findIndex方法会将这个item对应的index }); ``` 也就是说,我们是根据 item.id 找到这个 item 是属于list 数组中的哪个index索引。找到了index,就可以根据index来删除数组中的那个元素了。 当item被删除后,v-for会被自动调用,进而自动更新view。 完整版代码: ```html Document
    编号: 名称:
    编号 名称 创建时间 操作
    列表无数据
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` ### 步骤 5:按条件筛选item 现在要求实现的效果是,在搜索框输入关键字 keywords,列表中仅显示匹配出来的内容。也就是说: - 之前, v-for 中的数据,都是直接从 data 上的list中直接渲染过来的。 - 现在, 我们在使用`v-for`进行遍历显示的时候,不能再遍历全部的 list 了;我们要自定义一个 search 方法,同时,把keywords作为参数,传递给 search 方法。即`v-for="item in search(keywords)"`。 在 search(keywords) 方法中,为了获取 list 数组中匹配的item,我们可以有两种方式实现。如下。 **方式一**:采用`forEach + indexOf()` ```javascript search(keywords) { // 根据关键字,进行数据的搜索,返回匹配的item //实现方式一:通过 indexOf() 进行匹配。 var newList = []; this.list.forEach(item => { if (item.name.indexOf(keywords) != -1) { //只要不等于 -1,就代表匹配到了 newList.push(item) } }) return newList } ``` 上方代码中, 我们要注意 indexOf(str) 的用法。举例如下: ```javascript var str = 'smyhvae'; console.log(str.indexOf('s')); //打印结果:0 console.log(str.indexOf('')); //打印结果:0。(说明,即使去匹配空字符串,也是返回0) console.log(str.indexOf('h')); //打印结果:3 console.log(str.indexOf('x')); //打印结果:-1 (说明,匹配不到任何字符串) ``` 上方代码中,也就是说,如果参数为空字符串,那么,每个item都能匹配到。 **方式二**: filter + includes()方法 ```javascript search(keywords) { // 根据关键字,进行数据的搜索,返回匹配的item var newList = this.list.filter(item => { // 注意 : ES6中,为字符串提供了一个新方法,叫做 String.prototype.includes('要包含的字符串') // 如果包含,则返回 true ,否则返回 false if (item.name.includes(keywords)) { return item } }) return newList } ``` 注意:forEach some filter findIndex,这些都属于数组的新方法,都会对数组中的每一项,进行遍历,执行相关的操作。这里我们采用数组中的 filter 方法, 总的来说,方式二的写法更优雅,因为字符串的 includes()方法确实很实用。 完整版代码如下: ```html Document
    编号: 名称: 搜索:
    编号 名称 创建时间 操作
    列表无数据
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` 备注:在1.x 版本中可以通过filterBy指令来实现过滤,但是在2.x中已经被废弃了。 ================================================ FILE: 12-Vue基础/06-自定义过滤器:时间格式化举例.md ================================================ --- title: 06-自定义过滤器:时间格式化举例 publish: true --- ## 前言 > 我们接着上一篇文章01-04来讲。 ### 过滤器的概念 **概念**:Vue.js 允许我们自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache **插值表达式**、 **v-bind表达式**。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示。 ### Vue1.X中的系统过滤器 Vue提供了一系列的固定逻辑来使程序员更加容易的实现这些功能,这些过滤器称之为系统过滤器。 系统过滤器是Vue1.0中存在的,在Vue2.0中已经删除了。 系统过滤器的使用,可以参考参考文档: Vue也提供了一个接口用来供程序员定义属于自己的特殊逻辑,Vue称之为自定义过滤器。我们接下来讲一讲。 ## 自定义全局过滤器 文档地址: ### 全局过滤器的基本使用 我们可以用全局方法`Vue.filter()`自定义一个全局过滤器。这样的话,每一个Vue的对象实例(每一个VM实例)都可以拿到这个过滤器。它接收两个参数:过滤器的名称 、过滤器函数。 比如说,我要将`曾经,我也是一个单纯的少年,单纯的我,傻傻的问,谁是世界上最单纯的男人`这句 msg 中的“单纯”改为“邪恶”。可以这样做: (1)在插值表达式中这样调用: ```html

    {{ msg | msgFormat }

    ``` 上方代码的意思是说: - **管道符前面**的`msg`:要把 `msg` 这段文本进行过滤, - **管道符后面**的`msgFormat`:是通过`msgFormat`这个过滤器进行来操作。 (2)定义过滤器`msgFormat`: ```javascript // Vue.filter 中的第一个参数是过滤器的名称,第二个参数是具体的过滤器函数 // 定义一个 Vue 全局的过滤器,名字叫做 msgFormat Vue.filter('msgFormat', function (myMsg) { // function 的第一个参数指的是管道符前面的 msg // 字符串的 replace 方法,第一个参数,除了可写一个 字符串之外,还可以定义一个正则 return myMsg.replace(/单纯/g, '邪恶') }) ``` 上方代码解释: - `Vue.filter(‘过滤器的名称’, 具体的过滤器函数)`中的第一个参数指的就是过滤器的名称(必须和**管道符后面**的名称**完全一致**),第二个参数是具体的过滤器函数 - 过滤器函数function中,第一个参数指的**管道符前面的**msg。 - `replace()`方法是用来做字符串的替换的。第一个参数如果只写成`单纯`,那么就会只修改 msg 中的第一个`单纯`字样。所以这里就用正则去匹配msg 中所有的`单纯`字样。 最终,完整版代码如下: ```html Document

    {{ msg | msgFormat }}

    ``` 网页显示效果如下: ![](http://img.smyhvae.com/20180522_1240.png) ### 给过滤器添加多个参数 上面的举例代码中,`{{ msg | msgFormat }}`中,**过滤器的调用并没有加参数**,其实它还可以添加多个参数。 接下来,我们在上面的举例代码中进行改进。 **改进一**:过滤器加一个参数。如下: 将 msg 这个字符串中的“单纯”改为 xxx 变量。代码如下: ```html Document

    {{ msg | msgFormat('xxx') }}

    ``` ![](http://img.smyhvae.com/20180525_2135.png) 注意代码中那行重要的注释:括号里的参数代表 function中的 arg2。 **改进二**:过滤器加两个参数。如下: ```html Document

    {{ msg | msgFormat('【牛x】', '【参数arg3】') }}

    ``` 效果如下: ![](http://img.smyhvae.com/20180525_2150.png) **改进3:同时使用多个过滤器** 对 msg 同时使用多个过滤器。例如: ```html Document

    {{ msg | msgFormat('【牛x】', '【参数arg3】') | myFilter2}}

    ``` 效果如下: ![](http://img.smyhvae.com/20180525_2200.png) 上方代码中,添加了多个过滤器,实现的思路是:**将 msg 交给第一个过滤器来处理,然后将处理的结果交给第二个过滤器来处理** 。 ### 举例1:时间格式化 ```html Document
    {{ time }}
    {{ time | datefmt }}
    {{ time | datefmt }}
    ``` 运行效果: ![](http://img.smyhvae.com/20180525_2230.png) ### 举例2:时间格式化 上面的举例1,时间格式化的过滤器,我们还有个更高端的写法:(字符串模板) ```html Document
    2018-05-25T14:06:51.618Z
    {{ '2018-05-25T14:06:51.618Z' | dateFormat }}
    ``` 运行结果: ![](http://img.smyhvae.com/20180526_2319.png) 【荐】**举例2的改进**:(字符串的padStart方法使用) 上图中,我们可以看到,箭头处的时间有些问题,比如说,`6`要写成`06`更合适。为了实现这个功能,我们可以这样做: 使用ES6中的字符串新方法 `String.prototype.padStart(maxLength, fillString='')` 或 `String.prototype.padEnd(maxLength, fillString='')`来填充字符串。 `pad`在英文中指的是`补充`。 实现举例如下: ```html Document
    2018-05-25T14:06:51.618Z
    {{ '2018-05-25T14:06:51.618Z' | dateFormat }}
    ``` 运行效果如下: ![](http://img.smyhvae.com/20180526_2323.png) `pattern`参数的解释: 在做`if (pattern && pattern.toLowerCase() === 'yyyy-mm-dd')`这个判断时,逻辑是:**先保证pattern参数传进来了,然后继续后面的判断**。 我们不能写成:`if (pattern.toLowerCase() === 'yyyy-mm-dd')`。因为,万一在调用的时候,不传递参数pattern,那么 if语句就相当于`if (undefined.toLowerCase() === 'yyyy-mm-dd')`,就会报错。 当然,ES6中有个新特性叫“默认参数”,我们就可以这样写: ```html Document
    2018-05-25T14:06:51.618Z
    {{ '2018-05-25T14:06:51.618Z' | dateFormat }}
    ``` ## 自定义私有过滤器 **私有过滤器**:在某一个 vue 对象内部定义的过滤器称之为私有过滤器。这种过滤器只有在当前vue对象的el指定的监管区域有用。 **举例**:日期格式化 ```html Document
    {{ time }}
    {{ time | datefmt }}
    ``` 上面的代码中,我们在vue实例中,通过`filters`关键字,在里面定义了一个局部过滤器`datefmt`。 运行结果: ![](http://img.smyhvae.com/20180405_2038.png) 第一行代码显示的是默认的date。第二行代码显示的是格式化之后的date,说明过滤器是起到了作用的。 ### 总结 过滤器调用的时候,采用的是**就近原则**,如果私有过滤器和全局过滤器名称一致了,这时候 优先调用私有过滤器。 ## axios axios是在Vue中专门用来发送ajax请求的。 但是,axios并不依赖于Vue.js库,而是基于promise的。 ================================================ FILE: 12-Vue基础/07-自定义按键修饰符&自定义指令.md ================================================ --- title: 07-自定义按键修饰符&自定义指令 publish: true --- ## v-on的按键修饰符 ### Vue 内置的按键修饰符 通俗一点讲,指的是:监听键盘输入的事件。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。如下: Vue内置的按键修饰符: ``` .enter .tab .delete (捕获 “删除” 和 “退格” 键) .esc .space .up .down .left .right 1.0.8+版本:支持单字母的按键别名。 ``` 比如说,`keyup`指的是:键盘(任何键位)抬起时的监听事件。`.enter`指的是:按enter键的按键修饰符。我们把这两个结合起来看看。 **`@keyup.enter`举例**:按enter键后的监听事件 `@keyup.enter="addData"`表示:按住enter键后,执行addData()方法。**全称**是`v-on:key.enter="addData"`。 我们还是拿`01-04`这篇文章中的列表功能来举例。之前是点击“添加”按钮后,列表中会添加一个item。现在要求:在**输入框**中按enter键后,也能添加一个item。 核心代码如下: ```html ``` 注意,如果写成`@keyup="addData"`,效果却是:只要键盘的任何键位打了字(还没来得及按enter键),就会执行addData()方法,这种效果显然不是我们想要的。所以要加上修饰符`.enter`,表示只针对enter键。 ### 自定义的按键修饰符 如果我们直接在代码的``标签里写`@keyup.f2="addData"`,那么,按住「F2键」后,是没有效果的,因为「F2键」不是内置的按键修饰符(如果F2不能验证,你可以试一下F7)。 我们知道,每个按键都有一个键盘码。参考链接: - [js 里面的键盘事件对应的键码](http://www.cnblogs.com/wuhua1/p/6686237.html) 通过查阅,我们知道了「F2键」的键盘码为`113`,那代码可以这样写:(按住F2键后,执行 addData 方法) ```html ``` 虽然键盘码很全,但是不好记呀。于是,接下来,我们给键盘码定义别名。 **自定义全局按键修饰符**: ``` //自定义全局按键修饰符 Vue.config.keyCodes.f2 = 113; ``` 上方代码的书写位置,与自定义全局过滤器的位置,是并列的。 然后,我们就可以使用键盘码的别名了。 ## 自定义全局指令 ### 自定义全局指令的举例1 **举例1**:让指定文本框自动获取焦点 如果我们想实现这个例子,原生js的写法是: ```javascript //原生js写法:网页一打开,就让指定的输入框自动获取焦点 document.getElementById('search').focus() ``` 代码的位置: ![](http://img.smyhvae.com/20180527_2340.png) 但我们不建议这样做。我们可以通过Vue中的自定义指令来实现这个例子。步骤如下。 (1)使用`Vue.directive()`自定义全局指令: ```javascript //自定义全局指令 v-focus:让文本框自动获取焦点 //参数1:指令的名称。注意,在定义的时候,指令的名称前面,不需要加 v- 前缀;但是:在`调用`的时候,必须在指令名称前 加上 v- 前缀 //参数2:是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作 Vue.directive('focus', { //在每个函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象(DOM对象) bind: function (el) { // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,【只执行一次】 // 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用 // 因为,一个元素,只有插入DOM之后,才能获取焦点 // el.focus() }, inserted: function (el) { // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】 el.focus() // 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效 }, updated: function (el) { // 当VNode更新的时候,会执行 updated, 【可能会触发多次】 } }) ``` 上方的代码中,如果我们把`el.focus()`这行代码写在`bind`方法里,是没有效果的(但不会报错)。没有效果是因为,在执行到`bind`方法的时候,元素还没有插入到dom中去。 由此可以看看出:`bind`、`inserted`、`updated`这三个钩子函数的执行时机不同,且执行的次数有区别。 (2)在指定的文本框上加``: ```html ``` 完整版代码如下: ```html Document
    搜索框:
    ``` ### 自定义全局指令:使用钩子函数的第二个binding参数拿到传递的值 **举例2**:设置DOM元素的color样式 参考举例1中的写法,我们可能会这样给DOM元素设置样式: ```html Document
    搜索框:
    ``` 如上方代码所示,我们自定义了一个指令`v-color`,然后在`input`标签中用上了这个指令,就给元素设置了color属性。但是这个代码有个弊端是:color的属性值在定义指令的时候,被写死了。如何完善呢?我们可以在DOM元素中传参。一起来看看。 代码如下:【荐】 ```html Document
    搜索框1:
    ``` 上方代码中,bind方法里传递的第二个参数`binding`,可以拿到DOM元素中`v-color`里填的值。注意,`v-color="'green'"`,这里面写的是字符串常量;如果去掉单引号,就成了变量,不是我们想要的。 效果: ![](http://img.smyhvae.com/20180610_1323.png) **自定义全局指令的简写形式**: 在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如上面的代码中,我们可以写成简写形式: ```javascript Vue.directive('color', function (el, binding) { //注意,这个function等同于把代码写到了 bind 和 update 中去 el.style.color = binding.value }) ``` ## 自定义私有指令 **自定义私有指令**:在某一个 vue 对象内部自定义的指令称之为私有指令。这种指令只有在当前vue对象的el指定的监管区域有用。 代码举例:(设置文字的font-weight属性) ```html Document
    生命壹号
    ``` 效果: ![](http://img.smyhvae.com/20180610_1400.png) 注意, el.style.fontWeight设置属性值,至少要600,否则看不到加粗的效果。 **自定义私有指令的简写形式**: 在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如上面的代码中,我们可以写成简写形式: ``` //自定义私有指令(简写形式) directives: { 'fontweight': function (el, binding) { //注意,这个function等同于把代码写到了 bind 和 update 中去 el.style.fontWeight = binding.value; } } ``` ================================================ FILE: 12-Vue基础/08-Vue实例的生命周期函数.md ================================================ --- title: 08-Vue实例的生命周期函数 publish: true --- ## 介绍 - [vue实例的生命周期](https://cn.vuejs.org/v2/guide/instance.html#实例生命周期):从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。 - [生命周期钩子](https://cn.vuejs.org/v2/api/#选项-生命周期钩子):就是生命周期事件的别名而已。 生命周期钩子 = 生命周期函数 = 生命周期事件。 ## 生命周期函数的主要分类 ![](http://img.smyhvae.com/20180422_1650.png) 根据上面这张图,我们把生命周期函数主要分为三类。 ### 1、创建期间的生命周期函数 - beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性 - created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。我们可以在这里进行Ajax请求。 - beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中 - mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示**真实DOM渲染完了,可以操作DOM了**) **举例**: ```html Title
    ``` 打印结果: ![](http://img.smyhvae.com/20180610_1500.png) ### 运行期间的生命周期函数 - beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点 - updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。 PS:数据发生变化时,会触发这两个方法。不过,我们一般用watch来做。 **举例**: ```html Title

    {{ flag }}

    ``` 当我们点击按钮后,运行效果是: ![](http://img.smyhvae.com/20180610_1528.png) 可以看出: - 当执行 beforeUpdate 的时候,页面中的显示的数据还是旧的,但此时 data 数据是最新的 - updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的 ### 3、销毁期间的生命周期函数 - beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。 - destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 PS:可以在beforeDestroy里**清除定时器、或清除事件绑定**。 ## 生命周期函数图解 ![](http://img.smyhvae.com/20180611_2130.png) PS:图片来自网络。 ================================================ FILE: 12-Vue基础/09-Vue中的Ajax请求.md ================================================ --- title: 09-Vue中的Ajax请求 publish: true --- ## vue-resource的介绍 `vue-resource`是Vue高度集成的第三方包。 官网链接: - 文档(http相关): vue-resource 依赖于 Vue。所以,我们要按照先后顺序,导入vue.js和vue-resource.js文件。 **解释**: `vue.js`文件向Windows对象暴露了`Vue`这个关键词;`vue-resource.js`向Vue身上挂载了`this.$http`这个属性。于是,我们可以直接写`this.$http.get`或者`this.$http.post`或者`this.$http.jsonp`来调用。 ## vue-resource 发送Ajax请求 常见的数据请求类型包括:get、post、jsonp。下面我们分别讲一讲。 ### get 请求 **格式举例**: ```javascript this.$http.get(url) .then(function (result) { // 当发起get请求之后,通过 .then 来设置成功的回调函数 console.log(result.body); // response.body就是服务器返回的成功的数据 var result = result.body; }, function (err) { //err是异常数据 }); ``` 获取到的`response.body`就是要获取的数据,但直接打印出来是 object,所以要记得转成string。 **举例**:获取数据 现规定,获取品牌数据的 api 接口说明如下: ![](http://img.smyhvae.com/20180422_2140.png) ```html Document
    编号 名称 创建时间 操作
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` 上方代码中,我们用到了生命周期函数`created`,意思是:程序一加载,就马上在`created`这个函数里执行`getlist()`方法。 运行的结果如下: ![](http://img.smyhvae.com/20180422_2152.png) 如果我直接在浏览器中输入请求的url,获取的json数据如下:(可以看到,这种方式获取的是相同的数据) ![](http://img.smyhvae.com/20180422_2150.png) ### post请求 **格式举例**: ```javascript // 方法:$http.post(url, 传给服务器的请求体中的数据, {emulateJSON:true}) // 通过 post 方法的第三个参数{ emulateJSON: true } ,来设置 提交的内容类型 为 普通表单数据格式 this.$http.post(url, { name: '奔驰' }, { emulateJSON: true }) .then(function (response) { alert(response.body.message); }, function (error) { }); ``` 上方代码中,post()方法中有三个参数,其中第三个参数是固定值,照着写就可以了。 **代码举例**:(添加数据) 现规定,添加品牌数据的 api 接口说明如下: ![](http://img.smyhvae.com/20180422_1720.png) 代码如下:(在上一段代码的基础之上,添加代码) ```html Document
    编号 名称 创建时间 操作
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` **代码举例**:(删除数据) ```html Document
    编号 名称 创建时间 操作
    {{item.id}} {{item.name}} {{item.ctime}} 删除
    ``` ### jsonp ![](http://img.smyhvae.com/20180420_2250.png) **格式举例**: ```javascript // 利用vue-resource中的jsonp方法实现跨域请求数据,这里要注意的是: // url后面不需要跟callback=fn这个参数了,jsonp方法会自动加上 this.$http.jsonp('http://vuecms.ittun.com/api/getlunbo?id=1') .then(function (response) { console.log(JSON.stringify(response.body)); }, function (err) { //err是异常数据 }); ``` 请求结果: ![](http://img.smyhvae.com/20180420_2256.png) ## JSONP的实现原理 由于浏览器的安全性限制,默认不允许Ajax发起跨域(协议不同、域名不同、端口号不同)的请求。浏览器认为这种访问不安全。 **JSONP的实现原理**:通过动态创建script标签的形式,用script标签的src属性,代表api接口的url,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求)。 具体实现过程: - 先在客户端定义一个回调方法,预定义对数据的操作 - 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的api接口; - 服务器api接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行; - 客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了 ## axios 除了 vue-resource 之外,还可以使用 `axios` 的第三方包实现实现数据的请求。 ## 通过Vue全局配置api接口的url地址 api接口的url地址包括:绝对路径+相对路径。 我们在做Ajax请求的时候,所填写的url建议填**相对路径**,然后把**绝对路径**放在全局的位置。 Vue就提供了这个功能。举例如下: ```html ``` 如上方代码所示,第一步是在全局的位置写**绝对路径**: ```javascript Vue.http.options.root = 'http://smyhvae/'; ``` 第二步是在Ajax请求的url中写**相对路径**:(注意,前面不要带`/`) ```javascript this.$http.get('api/getprodlist') ``` ================================================ FILE: 12-Vue基础/10-Vue动画.md ================================================ --- title: 10-Vue动画 publish: true --- ## 前言 动画的作用:提高用户的体验,帮助用户更好的理解页面中的功能。 ## 使用过渡类名实现动画 ### 官方文档的截图 过渡类名如下: ![](http://img.smyhvae.com/20180616_1555.png) 动画进入: - v-enter:动画进入之前的**初始**状态 - v-enter-to:动画进入之后的**结束**状态 - v-enter-active:动画进入的时间段 PS:第一、第二个是时间点;第三个是时间段。 动画离开: - v-leave:动画离开之前的**初始**状态 - v-leave-to:动画离开之后的**结束**状态 - v-leave-active:动画离开的时间段 PS:第一、第二个是时间点;第三个是时间段。 ### 使用举例(通过Vue的过渡类名来实现) `v-enter-to`和`v-leave`的状态是一样的。而且一般来说,`v-enter`和`v-leave-to`的状态也是一致的。所以,我们可以把这四个状态写成两组。 现在我们来做个例子:点击按钮时,让div显示/隐藏。 **1、引入**: 如果我们不使用动画,应该是这样做: ```html Document

    这是一个H3

    ``` **2、使用动画**:(通过Vue的过渡类名来实现) 现在,我们加**淡入淡出**的动画,让div显示和隐藏。代码如下: ```html Document

    这是一个H3

    ``` 上方代码中,我们使用vue提供的``标签把需要被动画控制的元素,包裹起来;然后使用`.v-enter`、`.v-leave-to`等进行动画的定义。 运行效果如下: ![](http://img.smyhvae.com/20180615_2200.gif) **3、再加一个 transform 属性进行位移**: 我们在上方代码的基础之上,加一个 transform 属性,让动画有一个位移的效果。完整代码如下: ```html Document

    这是一个H3

    ``` 效果如下: ![](http://img.smyhvae.com/20180615_2205.gif) ### 修改过渡类名的前缀 在上一小段中,`.v-enter`、`.v-leave-to`这些过渡类名都是以`v-`开头的。这样做,会有一个局限性:假设有两个DOM元素都用``进行了包裹,那这两个DOM元素就都具备了`v-`中所定义的动画。 那**如果我们想把两个DOM元素的动画进行分开定义**,该怎么做呢?这里,我们可以通过修改过渡类名的前缀来做。比如: 第一步:(自定义别名) ```html
    这是一个H6
    ``` 上方代码中,我们加了`name="my"`。 第二步:(我们就可以使用 `my-enter`、`.my-leave-to`这些类名了) ```css .my-enter, .my-leave-to { opacity: 0; transform: translateY(70px); } ``` 完整代码举例如下; ```html Document

    这是一个H3


    这是一个H6
    ``` 运行效果如下: ![](http://img.smyhvae.com/20180616_1513.gif) ## 使用第三方animate.css类库实现动画 animate.css网址: - 官方网站: **代码举例**: 下面的代码中,我们使用animate.css提供的`bounceIn`、`bounceOut`这两个类来做入场、离场的动画。代码如下: ```html Document

    这是一个H3

    ``` 上面的代码中,注意: 注意1:`enter-active-class`和`leave-active-class`这两个类名是Vue动画里的关键词,不能写成自己**随意起**的类名。 注意2:`bounceIn`、`bounceOut`这两个类不能直接使用,要在前面加上`animated`这个类;否则动画是不会生效的。当然,上面的代码中,我们还可以把`class = animated`这个代码移到`

    `标签里,效果是一样的,如下: ```html

    这是一个H3

    ``` 运行效果如下: ![](http://img.smyhvae.com/20180616_1538.gif) **改进1**:(统一设置入场、出场动画的持续时间) 我们把上面的代码改进一下,如果我们想给入场、出场动画设置持续的时间,可以使用`:duration`来做。如下: ```html

    这是一个H3

    ``` **改进2**:(分别设置入场、出场动画的持续时间) ```html

    这是一个H3

    ``` ## 钩子函数实现半场动画 只有出场动画、没有离场动画,这种就是属于半场动画。比如你把一件商品加入收藏,会出现一个动画;当再次点击收藏按钮的时候却看不到动画效果,这就说明,只有前一半才有动画。 半场动画,可以使用钩子函数来实现。 ### 动画的钩子函数介绍 可以在属性中声明 JavaScript 钩子函数:(这八个钩子函数可以理解成是动画的生命周期) ```html ``` 我们可以这样理解:上面这八个钩子函数(四个入场、四个离场),对应了八个事件,我们要紧接着在methods中定义八个函数。 如果要定义半场动画,做法是:直接在methods中写入场动画的函数,不写离场动画的函数即可。 ### 举例:使用钩子函数模拟小球半场动画 现在要实现的例子是:点击按钮后,让小球进行移动。完整代码如下: ```html Document
    ``` 运行效果如下:(我们可以用这种动画效果,做类似于“加入购物车”的动画效果) ![](http://img.smyhvae.com/20180616_1618.gif) 上面的代码中,有两个地方要注意: **注意1**: `el.offsetWidth`这行代码不能少。虽然这行代码没有实际的意义,但是少了之后,动画效果出不来: ![](http://img.smyhvae.com/20180616_1620.gif) 当然,我们也可以把这行代码换成`el.offsetHeight`、`el.offsetLeft`、`el.offsetTop`之类的,只要包含了offset就行。 **注意2**: `enter()`函数里,函数的第二个参数要加上`done`,函数体的最后一行要写`done()`,表示**立即执行**后面的`afterEnter()`函数;如果没有这个`done`,则会**延迟执行**后面的`afterEnter()`函数: ![](http://img.smyhvae.com/20180616_2145.gif) Vue官方文档的解释是这样: > 当只用 JavaScript 过渡的时候,在`enter`和`leave`中必须使用`done`进行回调。否则,它们将被同步调用,过渡会立即完成。 ## 使用transition-group元素实现列表动画 现在的场景是:在一个`
      `列表中,如果我想给**指定的某个**`li`添加动画效果,该怎么做呢?(需要声明的是,这些`li`是用v-for循环进行遍历的) 如果我们用``把`li`包裹起来,就会让所有的`li`都具备了动画,这显然是不可取的。 那该怎么做呢?这里我们就可以用`transition-group`进行包裹。 **代码举例1**:点击添加按钮后,给新增的 item 加个动画 ```html Document
    • {{item.id}} --- {{item.name}}
    • ``` 运行效果如下: ![](http://img.smyhvae.com/20180616_2240.gif) **改进1**:添加删除item的功能 基于上面的代码,我们来添加**删除item**的功能,代码本应该是这样写: ```html Document
    • {{item.id}} --- {{item.name}}
    • ``` 运行效果如下: ![](http://img.smyhvae.com/20180617_1555.gif) **改进2:**: 上图中,我们发现,当我删除第2个item时,**第3、第4个item在往上移动的过程比会较突兀**。为了改进这个地方,我们可以给`.v-move`、`.v-leave-active`加一些动画属性。最终,完整版代码如下: ```html Document
    • {{item.id}} --- {{item.name}}
    • ``` 运行效果如下: ![](http://img.smyhvae.com/20180617_1556.gif) ### transition-group中appear和tag属性的作用 我们可以在上面的代码基础之上,给transition-group加上`appear`属性,这样的话,可以让transition-group包裹的所有DOM元素在刷新时,有**淡入效果**。 ```html Document
      • {{item.id}} --- {{item.name}}
      ``` ![](http://img.smyhvae.com/20180617_1600.gif) **改进**:`transition-group`的`tag`属性 上面的代码中,我们审查一下代码元素会发现,用`transition-group`包裹的元素,会被默认套上一层``: ![](http://img.smyhvae.com/20180617_1620.png) 这个``虽然没有太大副作用,但是不符合代码规范。为了解决这个问题,我们可以通过`tag`属性给`transition-group`包谷的元素套上一层`
        `,然后把现有的`
          `注释掉,就可以了。最终代码如下: ```html Document
        • {{item.id}} --- {{item.name}}
        • ``` 这样的话,审查元素的效果如下: ![](http://img.smyhvae.com/20180617_1621.png) ================================================ FILE: 12-Vue基础/11-Vue组件的定义和注册.md ================================================ --- title: 11-Vue组件的定义和注册 publish: true --- ## 前言 ### 什么是组件 **组件**: 组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可。 ### 模块化和组件化的区别 - 模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一 - 组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用 ## 全局组件的定义和注册 组件`Component`是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。 全局组件的定义和注册有三种方式,我们接下来讲一讲。 ### 写法一 写法一:使用Vue.extend方法定义组件,使用 Vue.component方法注册组件。 代码举例: ```html Document
          ``` 上方代码中,在注册组件时,第一个参数是标签名,第二个参数是组件的定义。 运行结果如下: ![](http://img.smyhvae.com/20180422_2230.png) 代码截图如下: ![](http://img.smyhvae.com/20180422_2223.png) 上图中,注意两点: **注意1**、红框部分,要保证二者的名字是一致的。如果在注册时,组件的名称是**驼峰命名**,比如: ```javascript Vue.component('myComponent', myAccount); //第一个参数是组件的名称(标签名),第二个参数是模板对象 ``` 那么,在标签中使用组件时,需要把大写的驼峰改为小写的字母,同时两个单词之间使用`-`进行连接: ```html ``` 所以,为了避免名字不一致的问题,我们注册组件时,组件的名称可以直接写成`my-component`。比如:(避免驼峰不一致的建议写法) ```javascript Vue.component('my-component', myAccount); ``` **注意2**、绿框部分,一定要用一个大的根元素(例如`
          `)包裹起来。如果我写成下面这样,就没有预期的效果: ``` template: '

          登录页面

          注册页面

          ' ``` 结果如下:(并非预期的效果) ![](http://img.smyhvae.com/20180422_2232.png) ### 写法二 写法二:Vue.component方法定义、注册组件(一步到位)。 代码如下: ```html Document
          ``` 代码截图如下: ![](http://img.smyhvae.com/20180422_2251.png) 上图中,同样注意两点: 1、红框部分,要保证二者的名字是一致的。 2、绿框部分,一定要用一个大的根元素(例如`
          `)包裹起来。如果我写成下面这样,就没有预期的效果: ``` template: '

          登录页面

          注册页面

          ' ``` 结果如下:(并非预期的效果) ![](http://img.smyhvae.com/20180422_2232.png) ### 写法三【荐】 > 上面的写法一、写法二并不是很智能,因为在定义模板的时候,没有智能提示和高亮,容易出错。我们不妨来看看写法三。 写法三:将组件内容定义到template标签中去。 代码如下: ```html Document
          ``` 代码截图如下: ![](http://img.smyhvae.com/20180422_2256.png) 写法三其实和方法二差不多,无非是把绿框部分的内容,单独放在了`