GitVP开源文摘
全部文章/前端开发

千古前端图文教程

超详细的前端入门到进阶知识库

作者千古壹号 仓库qianguyihao/Web ↗ 星标★ 28,679 字数304,508 许可仅用于学习交流 阅读8
摘要从零开始学前端的完整图文教程,作者的本意是「照顾每一位前端入门者的同理心」,不懂技术也能读得下去。
本文由 GitVP 自动汇编该仓库的前 62 个章节(全站共 253 个章节),完整版可点击页首「仓库」链接查看。

前言

千古前端图文教程,超详细的 Web 前端入门到进阶知识库。从零开始学前端,做一名精致优雅的前端工程师。持续更新中...

通俗易懂,不懂技术也能学。此前端教程不玩猫腻,不会设置任何套路,因为我相信启蒙的重要性。

项目介绍

项目地址

  • 官网地址:

如果官网打开异常,请先尝试强制刷新页面,或者清除浏览器缓存,或者提交 GitHub issues 反馈问题。

  • GitHub地址:

如果你发现本项目有内容上的错误,欢迎在 GitHub 提交 issues 或者 pull requests 进行指正,方便归档。

项目作用

  • 网上的大部分入门教程,都不太适合初学者,本项目争取照顾到每一位前端入门者的同理心。即使你完全不懂前端,甚至不懂编程,通过这个教程,也能让小白入门。
  • 帮助前端同学提供一个精品学习资源和路线,提高学习效率,少走很多弯路。
  • 可以当做前端字典,随时翻阅,查漏补缺。

相关链接

学习交流

在公众号「千古壹号」里回复“前端学习”,拉你进微信交流群:

  • 进群暗号:前端学习
  • 进群要求:少提问、少闲聊、多分享(不适合长期潜水)。

LICENSE

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。



title: 01-VS Code的使用


前言

文章标题:《第一次使用 VS Code 时你应该知道的一切配置》。本文的最新内容,更新于 2021-10-09。大家完全不用担心这篇文章会过时,因为随着 VS Code 的版本更新和插件更新,本文也会随之更新。
本文的最新内容,也会在GitHub上同步更新,欢迎 star。

VS Code 软件实在是太酷、太好用了,越来越多的新生代互联网民工正在使用它。

前端男神尤雨溪大大这样评价 VS Code:

有一点你可能会感到惊讶: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,列出一整年的规划图。VS Code 软件的官方文档也托管在了 GitHub 上。
  • 自带终端、图形化的调试工具、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 浏览器版本的最核心组件。#
  • 编程语言: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 进行开发的。Xterm.js 是一个使用 TS 开发的终端组件。另外,Xterm.js 并不是直接下来下来就能用的终端应用,它只是一个前端组件,可以与 bash这样的进程进行连接,然后让用户通过 Xterm.js 进行交互。

VS Code 的安装

  • VS Code 官网:

VS Code 的安装很简单,直接去官网下载安装包,然后双击安装即可。

上图中,直接点击 download,一键下载安装即可。

VS Code支持以下平台:

安装完成后的界面如下:

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 + PCtrl + Shift + P,F1显示命令面板
Cmd + BCtrl + B显示/隐藏侧边栏很实用
Cmd + \Ctrl + \拆分为多个编辑器【重要】抄代码利器
Cmd + 1、2Ctrl + 1、2聚焦到第 1、第 2 个编辑器同上重要
Cmd + +、Cmd + -ctrl + +、ctrl + -将工作区放大/缩小(包括代码字体、左侧导航栏)在投影仪场景经常用到
Cmd + JCtrl + J显示/隐藏控制台
Cmd + Shift + NCtrl + Shift + N重新开一个软件的窗口很常用
Cmd + Shift + WCtrl + Shift + W关闭软件的当前窗口
Cmd + NCtrl + N新建文件
Cmd + WCtrl + W关闭当前文件

2、跳转操作

Mac 快捷键Win 快捷键作用备注
Cmd + `没有在同一个软件的多个工作区之间切换使用很频繁
Cmd + Option + 左右方向键Ctrl + Pagedown/Pageup在已经打开的多个文件之间进行切换非常实用
Ctrl + TabCtrl + Tab在已经打开的多个文件之间进行跳转不如上面的快捷键快
Cmd + Shift + OCtrl + shift + O在当前文件的各种方法之间(符号:Symbol)进行跳转
Cmd + TCtrl + T在当前工作区的各种方法之间(符号:Symbol)进行跳转
Ctrl + GCtrl + 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 + CCtrl + C复制
Cmd + XCtrl + X剪切
Cmd + VCtrl + V粘贴
Cmd + EnterCtrl + Enter在当前行的下方新增一行,然后跳至该行即使光标不在行尾,也能快速向下插入一行
Cmd+Shift+EnterCtrl+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 + KCtrl + Shift + K删除整行「Cmd + X」的作用是剪切,但也可以删除整行
option + BackspaceCtrl + Backspace删除光标之前的一个单词英文有效,很常用
option + deleteCtrl + delete删除光标之后的一个单词
Cmd + Backspace删除光标之前的整行内容很常用
Cmd + delete删除光标之后的整行内容

备注:上面所讲到的移动光标、编辑操作、删除操作的快捷键,在其他编辑器里,大部分都适用。

6、多光标选择/多光标编辑

多光标选择在编程的提效方面可谓立下了汗马功劳。因为比较难记住,所以你要时不时回来复习这一段。

Mac 快捷键Win 快捷键作用备注
Option + 鼠标连续点击任意位置Alt + 鼠标连续点击任意位置在任意位置,同时出现多个光标很容易记住
Cmd + DCtrl + D将光标放在某个单词的位置(或者先选中某个单词),然后反复按下「 Cmd + D 」键, 即可将下一个相同的词逐一加入选择。较常用
Cmd + Shift + LCtrl + Shift + L将光标放在某个单词的位置(或者先选中某个单词),然后按下快捷键,则所有的相同内容处,都会出现光标。很常用。比如变量重命名的时候,就经常用到

7、多列选择/多列编辑

多列选择是更高效的多光标选择,所以单独列成一小段。

Mac 快捷键Win 快捷键作用备注
Cmd + Option + 上下键Ctrl + Alt + 上下键在连续的多列上,同时出现多个光标较常用
Option + Shift + 鼠标拖动Alt + Shift + 鼠标拖动按住快捷键,然后把鼠标从区域的左上角拖至右下角,即可在选中区域的每一行末尾,出现光标。很神奇的操作,较常用
Option + Shift + iAlt + Shift + I选中一堆文本后,按下快捷键,既可在每一行的末尾都出现一个光标。很常用

8、编程语言相关

Mac 快捷键Win 快捷键作用备注
Cmd + /Ctrl + /添加单行注释很常用
Option + Shift + FAlt + shift + F代码格式化很常用
F2F2以重构的方式进行重命名改代码备
Ctrl + J将多行代码合并为一行Win 用户可在命令面板搜索”合并行“
Cmd +
Cmd + UCtrl + U将光标的移动回退到上一个位置撤销光标的移动和选择

9、搜索相关

Mac 快捷键Win 快捷键作用备注
Cmd + Shift + FCtrl + Shift +F全局搜索代码很常用
Cmd + PCtrl + P在当前的项目工程里,全局搜索文件名
Cmd + FCtrl + F在当前文件中搜索代码,光标在搜索框里
Cmd + GF3在当前文件中搜索代码,光标仍停留在编辑器里很巧妙

10、自定义快捷键

按住快捷键「Cmd + Shift + P」,弹出命令面板,在命令面板中输入“快捷键”,可以进入快捷键的设置。

当然,你也可以选择菜单栏「偏好设置 --> 键盘快捷方式」,进入快捷键的设置:

此外,如果你输入这个快捷键后没起作用,那有可能是与其他软件(比如 PicGo 软件)的快捷键冲突了,请检查一下。

11、快捷键列表

你可以点击 VS Code 左下角的齿轮按钮,效果如下:

上图中,在展开的菜单中选择「键盘快捷方式」,就可以查看和修改所有的快捷键列表了:

快捷键参考表(官方)

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),可以打开快速命令面板。效果如下:

命令面板的作用是希望解放开发者的鼠标,让一些操作和配置可以直接通过键盘进行。如果让开发者记住所有的配置项在菜单的哪个位置是不现实的,而且有些命令并不在菜单中。

有了命令面板之后,如果你需要修改一些设置项,或者进行一些快捷操作,则可以通过「命令面板」来操作,效率会更高。接下来列举一些。

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、设置字体大小

在命令面板输入“字体”,可以进行字体的设置,效果如下:

当然,你也可以在菜单栏,选择「首选项-设置-常用设置」,在这个设置项里修改字体大小。

3、快捷键设置

在命令面板输入“快捷键”,就可以进入快捷键的设置。

4、大小写转换

选中文本后,在命令面板中输入transfrom,就可以修改文本的大小写了。

5、使用命令行启动 VS Code

(1)输入快捷键「Cmd + Shift + P 」,选择install code command:

(2)使用命令行:

  • code命令:启动 VS Code 软件
  • code pathName/fileName命令:通过 VS Code 软件打开指定目录/指定文件。

备注:这种方法快捷简单,但是在电脑重启之后就失效了。稍后在第五段,我会介绍更常见的方法。

6、修改特定编程语言的设置项

输入快捷键「Cmd + Shift + P 」打开命令面板,然后输入并执行 Configure Language Specific Settings即可。

四、私人订制:VS Code 的常见配置

0、设置项介绍

在修改 VS Code配置之前,我们需要知道,在哪里可以找到设置项的入口。

方式1:Mac用户选择菜单栏「Code--> 首选项-->设置」,即可打开配置项:

方式2:点击软件右下角的设置图标:

如上图所示,VS Code提供两种不同范围的设置:

  • 用户设置:全局生效。
  • 工作区设置:只针对当前项目生效。工作区设置会覆盖用户设置。适用于团队协作场景。工作区的设置文件是保存在当前项目根目录的.vscode/settings.json中,可以被提交到Git仓库,方便共享给项目组的其他成员。

操作技巧:

(1)我们可以在设置面板的顶部搜索框,输入关键词,就能迅速定位到你想要的设置项。

(2)上图中,点击右上角的icon,可以通过 json文件的形式修改设置项。

1、修改主题

1)修改颜色主题:

选择菜单栏「Code --> 首选项 --> 颜色主题」:

在弹出的对话框中,挑选你一个你喜欢的的颜色主题吧,或者安装其他颜色的主题:

20211013_1018

或者在设置项里搜索Workbench: Color Theme,进行修改。

2)修改文件图标的主题:

选择菜单栏「Code --> 首选项 --> 文件图标主题」:

20211013_1015

在弹出的对话框中,挑选你一个你喜欢的的主题吧,或者安装其他的主题:

20211013_1019

或者在设置项里搜索Workbench: Icon Theme,进行修改。

2、面包屑(Breadcrumb)导航

打开 VS Code 的设置项,选择「用户设置 -> 工作台 -> 导航路径」,如下图所示:

上图中,将红框部分打钩即可。

设置成功后,我们就可以查看到当前文件的「层级结构」,非常方便。如下图所示:

有了这个面包屑导航,我们可以点击它,在任意目录、任意文件之间随意跳转。使用频繁非常高。

3、是否显示代码的行号

VS Code 默认显示代码的行号。你可以在设置项里搜索 editor.lineNumbers修改设置,配置项如下:

我建议保留这个设置项,无需修改。

4、右侧是否显示代码的缩略图

如果某个文件的代码量很大,缩略图就很有用了,可以预览全局,并在当前文件中快速跳转。

VS Code 会在代码的右侧,默认显示缩略图。你可以在设置项里搜索 editor.minimap 进行设置,配置项如下:

上面这张图,你仔细琢磨下会发现,中文翻译十分精准。

5、将当前行代码高亮显示(更改光标所在行的背景色)

当我们把光标放在某一行时,这一行的背景色并没有发生变化。如果想高亮显示当前行的代码,需要设置两步:

(1)在设置项里搜索editor.renderLineHighlight,将选项值设置为all或者line。

(2)在设置项里增加如下内容:

"workbench.colorCustomizations": {
    "editor.lineHighlightBackground": "#00000090",
    "editor.lineHighlightBorder": "#ffffff30"
}

上方代码,第一行代码的意思是:修改光标所在行的背景色(背景色设置为全黑,不透明度 90%);第二行代码的意思是:修改光标所在行的边框色。

6、改完代码后立即自动保存

方式一:

改完代码后,默认不会自动保存。你可以在设置项里搜索files.autoSave,修改参数值为afterDelay ,即可自动保存。如下:

files.autoSave的参数值有以下几种:

  • off(默认值):不自动保存。
  • afterDelay(建议配置):文件修改超过一定时间(默认1秒)后,就自动保存。
  • onFocusChange:当前编辑器失去焦点时,则自动保存。如果我们将配置项修改为onFocusChange之后,那么,当光标离开该文件后,这个文件就会自动保存了。
  • onWindowChange:VS Code软件失去焦点时,则自动保存。

方式二:

当然,你也可以直接在菜单栏选择「文件-自动保存」。勾选后,当你写完代码后,文件会立即实时保存。

7、热退出

当VS Code退出后,它可以记住未保存的文件。如果你希望达到这种效果,那么,你需要先将设置项files.hotExit的值改为 onExitAndWindowClose。这个配置项要不要改,看你个人需要。比如我自己平时设置的值是onExit。

20211012_2014

8、保存代码后,是否立即格式化

保存代码后,默认不会立即进行代码的格式化。你可以在设置项里搜索editor.formatOnSave查看该配置项:

我觉得这个配置项保持默认就好,不用打钩。

9、自动格式化粘贴的内容

在设置项里搜索 editor.formatOnPaste,将设置项改为true:

20211012_1049

10、设置字体大小

在设置项里搜索fontSize,然后根据需要设置各种模块的字体大小:

20211012_1053

11、空格 or 制表符

VS Code 会根据你所打开的文件来决定该使用空格还是制表。也就是说,如果你的项目中使用的都是制表符,那么,当你在写新的代码时,按下 tab 键后,编辑器就会识别成制表符。

(1)建议的设置项如下:

  • editor.detectIndentation:自动检测(默认开启)。建议把这个配置项修改为 false,截图如下:

20211012_1139

这样做,是为了取消系统的自动缩进,建议自己手动格式化比较好。 参考链接:https://www.yisu.com/zixun/327399.html

  • editor.insertSpaces:按 Tab 键时插入空格(默认值为true)。截图如下:

  • editor.tabSize:一个制表符默认等于四个空格。截图如下:

(2)状态栏也会显示当前的缩进值。点击状态栏,可以直接修改 tabSize 缩进值:

(3)另外,我们还可以安装 prettier 插件,设置代码在格式化时默认缩进值。prettier 是做代码格式化的最常见工具。

(4)去掉每一行末尾的空格。在设置项里搜索空格或者"files.trimTrailingWhitespace",将值设置为 true:

20211012_1231

一般来说,每一行代码末尾的空格是多余的,所以建议去掉。

12、直观地显示代码里的空格和缩进 ✨

代码里如果有缩进或者空格,肉眼是看不出来的,但是我们可以修改配置项,把它揪出来。

在配置项里搜索editor.renderWhitespace,修改为all:

20211012_1150

修改之后,代码里的空格、缩进的展示效果如下:

20211012_1258

看到了没?哪里有空格、哪里是缩进,全都一目了然。

13、新建文件后的默认文件类型

当我们按下快捷键「Cmd + N」新建文件时,VS Code 默认无法识别这个文件到底是什么类型的,因此也就无法识别相应的语法高亮。

如果你想修改默认的文件类型,可以在设置项里搜索files.defaultLanguage,设置项如下:

上图中的红框部分,填入你期望的默认文件类型。我填的是html类型,你也可以填写成 javascript 或者 markdown,或者其他的语言类型。

14、删除文件时,是否弹出确认框

当我们在 VS Code 中删除文件时,默认会弹出确认框。如果你想修改设置,可以在设置项里搜索xplorer.confirmDelete。截图如下:

我建议这个设置项保持默认的打钩就好,不用修改。删除文件前的弹窗提示,也是为了安全考虑,万一手贱不小心删了呢?

15、在新窗口打开文件/文件夹

通过 window.openFoldersInNewWindow(默认值为off)和window.openFilesInNewWindow(默认值为default),可以配置在打开文件夹、打开文件时,是否开启一个新的窗口。我个人建议,把这两个配置项都设置为 on,避免旧的窗口被覆盖:

补充知识—— window.restoreWindows可以用来配置 如何恢复之前的会话窗口。涉及到的场景是:你把 VS Code 关闭了,然后又打开了,是否要展示之前打开过的文件、文件夹?参数值有以下几种:

  • one(默认配置):只会重新打开上一次回话中最后操作的那一个窗口。
  • none:打开一个空的窗口,不包含任何文件、文件夹。
  • all(建议配置):恢复上一次会话中的所有窗口。
  • folders:恢复上一次会话中包含文件夹的窗口。

20211012_1704

16、自动删除行尾的空格

打开设置项,搜索files.trimTrailingWhitespace,将选项勾选,即可在保存文件时自动删除行尾的空格。

17、突出显示成对的括号

我们可以用不同颜色显示代码中成对的括号,并用连线标注括号范围。简称彩虹括号。

最早是通过Bracket Pair Colorizer 2插件支持的,但是这个插件已经被废弃了,因为 VS Code 已经内置了该功能。我们可以通过 VS Code的如下配置项,达到效果:

{
  "editor.bracketPairColorization.enabled": true,
  "editor.guides.bracketPairs":"active"
}

18、自动换行

自动换行:意味着当文本到达屏幕或页面的右边缘时,自动换行会将文本移到下一行,以便继续输入或显示。

打开设置项,搜索Editor:Word Wrap,将选项值设置为 on。

此外,你还可以选择菜单栏查看-->自动换行,即可将当前文件设置为自动换行。

接下来,我们来讲一些更高级的操作。

五、纵享丝滑:常见操作和使用技巧

1、快速生成HTML骨架

先新建一个空的html文件,然后通过以下方式,可以快速生成html骨架。

方式1:输入!,然后按下enter键,即可生成html骨架。如下图:

方式2:输入html:5,然后按住 Tab键,即可生成html骨架。

生成的骨架,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    
    
    
    <title>Document</title>
</head>
<body>

</body>
</html>

有了上面的html骨架之后,我们就可以快乐地在里面插入CSS 代码和 JS 代码。

2、并排编辑:左右(上下)显示多个编辑器窗口(copy代码利器)

并排编辑是所有的编辑操作中最常用的一个技巧,十分有用。比如我们在开发一个项目时,可能需要同时打开 HTML 文件和 CSS 文件,很常见。

Mac 用户按住快捷键 Cmd + \, Windows 用户按住快捷键Ctrl + \,即可同时打开多个编辑器窗口,进行并排编辑。效果如下:

按快捷键「Cmd + 1 」切换到左边的窗口,按快捷键「Cmd + 2 」切换到右边的窗口,以此类推。随时随地,想切就切。

学会了这一招,以后 copy 代码的时候,leader 再也不用担心我抄得慢了,一天工资到手。


当然,使用快捷键Cmd + \只是其中一种方式,我们还有很多种方式打开并行编辑。我们来做一个汇总。

如果你已经打开了一个编辑器,那么可以通过以下几种方式在另一侧打开另一个编辑器:(按照使用频率,从高到低排序)

  • 使用快捷键Cmd + \将编辑器一分为二。
  • 使用快捷键Cmd + P调出文件列表,选择要打开的文件,然后按下 Cmd + Enter快捷键。【重要】
  • 按住 Option 键的同时,单击资源管理器的文件(Windows 用户是按 Alt 键)。
  • 点击编辑器右上角的 Split Editor按钮。
  • 选择菜单栏「查看--> 编辑器布局」,然后选择你具体想要的布局,如下图所示:

20211012_1451

  • 通过拖拽,把当前文件移动到任意一侧。

补充知识:通过配置项worbench.editor.OpenSideBySideDirection可以控制编辑器在并排打开时出现的默认位置(默认值为right,你也可以根据需要改为 down)。如下图所示:

20211012_1455

3、从终端 code 命令启动 VS Code(Mac电脑)

在终端输入code或者输入 code + 指定项目的目录,就可以启动 VS Code,十分便捷。即:

  • code 命令:启动 VS Code 软件。
  • code pathName/fileName 命令:通过 VS Code 软件打开指定目录/指定文件。

为了达到目的,我们需要先将 VS Code的软件安装路径添加到环境变量,一劳永逸。具体操作如下:

(1)打开 bash_profile文件:

cd ~
vim ./bash_profile

(2)在 bash_profile 中添加如下内容:

# 从终端启动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文件试试。

参考链接:

当然,还可以通过命令面板,一键设置环境变量。具体做法是:输入快捷键「Cmd + shift + P」打开命令面板,然后选择 shell 命令:从 PATH 中卸载 “code”命令:

完成后就可以在终端输入命令+文件路径来启动 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 值有问题。

参考链接:

4、在当前文件中搜索

在上面的快捷键列表中,我们已经知道如下快捷键:

  • Cmd + F(Win 用户是 Ctrl + F):在当前文件中搜索,光标在搜索框里
  • Cmd + G(Win 用户是 F3):在当前文件中搜索,光标仍停留在编辑器里

多个搜索结果出来之后,按下 Enter 键之后跳转到下一个搜索结果,按下 Shift + Enter 键之后跳转到上一个搜索结果。

另外,你可能会注意到,搜索框里有很多按钮,每个按钮都对应着不同的功能,如下图所示:

上图中,你可以通过「Tab」键和「Shift + Tab」键在输入框和替换框之间进行切换。

「在选定内容中查找」这个功能还是比较实用的。你也可以在设置项里搜索 editor.find.autoFindInSelection,勾选该设置项后,那么,当你选中指定内容后,然后按住「Cmd + F」,就可以自动只在这些内容里进行查找。该设置项如下图所示:

5、全局搜索

在上面的快捷键列表中,我们已经知道如下快捷键:

  • Cmd + Shift + F(Win 用户是 Ctrl + Shift +F):在全局的文件夹中进行搜索。效果如下:

20211012_1548

上图中,你可以点击红框部分,展开更多的配置项。然后点击红圈部分,进行过滤搜索。注意,第二个红圈那里会经常用到,它可以在搜索时过滤掉 .git、.node_modules等忽略文件。

上图中,我们还可以点击“在编辑器中打开”,在一个单独的文件中聚合展示搜索结果:

6、文件名/文件夹的搜索

前面的快捷键那一段我们讲过,通过 「Cmd + P」可以快速搜索并打开文件/文件夹。这种方式,一般用于快速打开最近编辑过的文件。

其实还有一种很巧妙的方式,可以在整个项目里,既能搜到文件,也能搜到文件夹。这种方式,常用于过滤项目的目录。操作方法很简单:

直接在文件资源管理器输入关键字就行。搜索结果会自动出现;使用方向键进行上下移动,可以在搜索的文件和文件夹之间进行跳转。 另外,右上角会看到一个过滤器,点击下图中的红圈部分,则只显示匹配的文件和文件夹。

20211012_1616

当然,这招也有一点不足:不能搜中文。

7、大纲视图

如下图所示,大纲视图可以展示当前代码的方法结构、文件的目录结构:

20211012_1628

20211012_1636

8、文件对比

VS Code 默认支持对比两个文件的内容。选中两个文件,然后右键选择「将已选项进行比较」即可,效果如下:

VS Code 自带的对比功能并不够强大,我们可以安装插件compareit,进行更丰富的对比。比如说,安装完插件compareit之后,我们可以将「当前文件」与「剪切板」里的内容进行对比:

如果你安装了 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 。 iTerm2 是Mac平台最好用的终端软件,没有之一。

右键行为:

在终端上,单击右键所产生的行为在不同的系统里是不同的。
  • Windows:如果有选定文本,则复制当前文本;如果没有选定文本,则粘贴。
  • macOS:选中光标所在位置的单词,并显示右键菜单。
  • Linux:显示右键菜单。

13、Git 版本管理

在 VS Code中使用Git之前,需要你先安装 Git 环境。

VS Code 自带了 Git 版本管理的功能,如下图所示:

上图中,我们可以在这里进行常见的 git 命令操作。如果你还不熟悉 Git 版本管理,可以先去补补课。

我自己用的最多的功能是diff 代码和合并冲突,自从用上了 VS Code 的这两个功能,简直离不开它。

我们先来看看 diff 代码的效果:

20211013_1411

上图中,点击右上角的...,然后点击内联视图,则可以换一种视图 diff 代码:

Git状态栏:

20211013_1421

在VS Code的左下角会显示Git状态栏。如果当前代码仓库配置了远程仓库,那么“同步更改”会显示以下信息:

  • 左边的数字:表示远程分支比本地分支多了XX个 Git commit。
  • 右边的数字:表示本地分支比远程分支多了XX个 Git commit。

点击“同步更改”按钮,会拉取(pull)远程分支到本地分支,并推送(push)本地的Git commit到远程分支。

如果当前代码仓库没有配置远程仓库,则会显示“发布更改”的按钮。点击“发布更改”按钮,会把当前分支push到远程仓库。


另外,我建议安装插件GitLens搭配使用,它是 VS Code 中我最推荐的一个插件,简直是 Git 神器,码农必备。

我还要补充一句:

有人说,高手都是直接用命令行操作Git。然而,根据我多年的经验来看,如果你的代码仓库需要管理的分支特别多,与团队的其他成员需要经常协作,那么,我建议你优先使用 GUI 图形化工具来操作Git,避免出错。

我推荐的GUI版的Git工具有:

14、将工作区放大/缩小

我们在上面的设置项里修改字体大小后,仅仅只是修改了代码的字体大小。

如果你想要缩放整个工作区(包括代码的字体、左侧导航栏的字体等),可以按下快捷键「cmd +/-」。windows 用户是按下「ctrl +/-」

当我们在投影仪上给别人演示代码的时候,这一招十分管用。

如果你想恢复默认的工作区大小,可以在命令面板输入重置缩放(英文是reset zoom)

f### 11、创建多层子文件夹

我们可以在新建文件夹的时候,如果直接输入aa/bb/cc,比如:

那么,就可以创建多层子文件夹,效果如下:

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 文件之后,你可以点击右上角的按钮进行预览,如下图所示:

我一般是安装「Markdown Preview Github Styling」插件,以 GitHub 风格预览 Markdown 样式。样式十分简洁美观。

你也可以在控制面板输入Markdown: 打开预览,直接全屏预览 markdown 文件。

18、Emmet in VS Code

Emmet可以极大的提高 html 和 css 的编写效率,它提供了一种非常简练的语法规则。

举个例子,我们在编辑器中输入缩写代码:ul>li*6 ,然后按下 Tab 键,即可得到如下代码片段:

<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

VS Code 默认支持 Emmet。更多 Emmet 语法规则,可以自行查阅。

19、修改字体,使用「Fira Code」字体

这款字体很漂亮,很适合用来写代码:

安装步骤如下:

(1)进入 网站,下载并安装「Fira Code」字体。

(2)打开 VS Code 的「设置」,搜索font,修改相关配置为如下内容:

"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种编程语言插件:

20211013_1120

智能提示的功能很强大, 包括函数介绍、代码自动补全等等。

22、调试与运行

VS Code 内置了对 Node.js 运行时的调试支持,可以直接调试 JavaScript 和 TypeScript。其他编程语言的调试,则需要先安装相应的插件才能支持。

在 VS Code插件市场中,下图是最受欢迎的几种调试插件:

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 的时候忽略的文件及文件夹,注意每一行后面有逗号,最后一行没有逗号

举例如下:(注意,其中的注释需要去掉)

{
  "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日,微软发布 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 有一个很强大的功能就是支持插件扩展,让你的编辑器仿佛拥有了三头六臂。

安装插件

上图中,点击红框部分,即可在顶部输入框里,查找你想要的插件名,然后进行安装。

插件安装完成后,记得重启软件(或者点击插件位置的“重新加载”),插件才会生效。

另外,我们还可以访问官网的插件市场来安装插件:

  • VS Code插件市场(官方):https://marketplace.visualstudio.com/vscode

插件的安装目录:

  • Windows::%USERPROFILE%\.vscode\extensions
  • macOS:~/.vscode/extensions
  • macOS:~/.vscode/extensions

插件的类型

20211013_1757_2

插件市场的首页有四个模块,可以作为重要的信息来源:

  • Featured:由 VS Code团队精心推荐的插件。
  • Trending:近期热门插件。
  • Most Popular:按总安装量排序的插件。
  • Recently Added:最新发布的插件。

20211013_1758

20211013_1955

插件市场至少有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

2)在搜索框输入字符@,会展示出不同类型的过滤器:

20211013_2015

常见的过滤器如下:

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 时是这样的:

安装了 GitLens 之后是这样的:

上图中,红框部分就是 GitLens 的功能,诸君可以自由发挥。

补充一个有意思的趣事:Python插件、Ruby插件、GitLens插件、Vetur插件,这四个插件的开发者先后加入了微软。

Git History

有些同学习惯使用编辑器中的 Git 管理工具,而不太喜欢要打开另外一个 Git UI 工具的同学,这一款插件满足你查询所有 Git 记录的需求。

Local History 【荐】

维护文件的本地历史记录。代码意外丢失时,有时可以救命。

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 选中后相同内容高亮插件推荐

vscode-icons

vscode-icons 会根据文件的后缀名来显示不同的图标,让你更直观地知道每种文件是什么类型的。

indent-rainbow:突出显示代码缩进

indent-rainbow插件:突出显示代码缩进。

安装完成后,效果如下图所示:

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,并在文件中添加如下内容:

{
  "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 分享时需要用到代码片段时,或者需要在网络上优雅地分享代码片段时,这一招很有用。

生成的效果如下:

其他同类插件: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 的渲染效果:

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 里写代码的时间。统计效果如下:

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 里的模块。

RemoteHub

不要惊讶,RemoteHub 和 GitLens 是同一个作者开发出来的。

RemoteHub插件的作用是:可以在本地查看 GitHub 网站上的代码,而不需要将代码下载到本地。

这个插件目前使用的人还不多,赶紧安装起来尝尝鲜吧。

Live Share:实时编码分享

Live Share这个神奇的插件是由微软官方出品,它的作用是:实时编码分享。也就是说,它可以实现你和你的同伴一起写代码。这绝对就是结对编程的神器啊。

安装方式:

打开插件管理,搜索“live share”,安装。安装后重启 VS Code,在左侧会多出一个按钮:

上图中,点击红框部分,登录后就可以分享你的工作空间了。

Import Cost

在项目开发过程中,我们会引入很多 npm 包,有时候可能只用到了某个包里的一个方法,却引入了整个包,导致代码体积增大很多。Import Cost插件可以在代码中友好的提示我们,当前引入的包会增加多少体积,这很有助于帮我们优化代码的体积。

10、主题插件

给你的 VS Code 换个皮肤吧,免费的那种。

  • Dracula Theme
  • Material Theme
  • Nebula Theme
  • One Monokai Theme
  • Monokai Pro
  • Ayu
  • SynthWave '84
  • GitHub Plus Theme:白色主题
  • Horizon Theme:红色主题

七、无缝切换:VS Code 配置云同步

我们可以将配置云同步,这样的话,当我们换个电脑时,即可将配置一键同步到本地,就不需要重新安装插件了,也不需要重新配置软件。

下面讲的两个同步方法,都可以,看你自己需要。方法1是 VS Code自带的同步功能,操作简单。方法2 需要安装插件,支持更多的自定义配置。

方法1:使用 VS Code 自带的同步功能

1、配置同步:

(1)在菜单栏选择「 Code --> 首选项 --> 打开设置同步」:

(2)选择需要同步的配置:

(3)通过Microsoft或者GitHub账号登录。 上图中,点击“登录并打开”,然后弹出如下界面:

上图中,使用 微软账号或者 GitHub账号登录:

(4)同步完成后,菜单栏会显示“首先项同步已打开”,最左侧也会多出一个同步图标,如下图所示:

2、管理同步:

(1)点击菜单栏「Code --> 首选项 --> 设置同步已打开」,会弹出如下界面,进行相应的同步管理即可:

(2)换另外一个电脑时,登录相同的账号,即可完成同步。

参考链接:

方法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)登录之后,插件的界面上,会自动出现之前的同步记录:

上图中,我们点击最新的那条记录,就可将云端的最新配置同步到本地:

如果你远程的配置没有成功同步到本地,那可能是网络的问题,此时,可以使用快捷键 「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

参考链接

2021年

  • 中文版 Awesome VS Code:https://github.com/formulahendry/awesome-vscode-cn

2020年


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



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'

上面这行命令,相当于:

$ git branch feature_item_recommend    // 创建新的分支

$ git checkout feature_item_recommend  //切换到新的分支

(2)在新的分支feature_item_recommend上,完成开发工作,并 commit 、push。

(3)将分支feature_item_recommend上的开发进度合并到master分支:

$ 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的时候,也是采用同样的步骤。

合并分支时,如果存在分叉

比如说上面这张图中,最早的时候,master分支是位于C2节点。我基于C2节点,new出一个新的分支iss53,我在iss53上提交了好几个commit。

现在,我准备把iss53上的几个commit合并到master上,此时发现,master分支已经前进到C4了。那该怎么合并呢?

合并的命令仍然是:

$ 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 的合并操作比其他系统都要简单不少。

解决合并时发生的冲突

如果 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童鞋的提示下,采取如下方式进行曲线救国。
# 设置当前仓库的用户名为空
git config  user.name ""

然后,当我们再输入git pull等命令行时,就会被要求重新输入新的账号密码。此时,密码就可以修改成功了。最后,我们还要输入如下命令,还原当前仓库的用户名:

git config user.name "smyhvae"

修改已经push的某次commit的作者信息

已经push的记录,如果要修改作者信息的话,只能 通过--force命令。我反正是查了很久,但最终还是不敢用公司的仓库尝试。

参考链接:

看最后一条答案。

将 branch1的某个commit1合并到branch2当中

切换到branch2中,然后执行如下命令:

git cherry-pick commit1

20190118-修改GitHub已提交的用户名和邮箱

参考链接:(亲测有效)

在执行./email.sh后,如果出现permission denied的错误,可以先执行chmod 777 email.sh,修改文件的权限。

20200520-将Git 项目迁移到另一个仓库

我们假设旧仓库的项目名称叫old-repository,新仓库的项目名称叫new-repository。操作如下:

(1)创建旧仓库的裸克隆:

git clone --bare https://github.com/exampleuser/old-repository.git

执行上述命令后,会在本地生成一个名叫 old-repository.git的文件夹。

(2)迁移到新仓库:

cd old-repository.git

git push --mirror https://github.com/exampleuser/new-repository.git

这样的话,项目就已经迁移到新仓库了。

注意,我们不需要手动新建一个空的新仓库,当我们执行上述命令之后,新仓库就已经自动创建好了。

参考链接:

2021-11-10-提交代码时,绕过 eslint 检查

需求:提交代码时,绕过 eslint 检查

解决办法:用命令行提交,末尾追加--no-verify。例如:

# 提交代码
git commit -m '千古壹号的commit备注' --no-verify

# 推送到远程时,也可以追加 --no-verify,以免远程仓库做了 eslint 限制。
git push origin --no-verify

2021-12-29-切换仓库的源地址

查看源地址:

git remote -v

切换源地址:

git remote set-url origin https://xxx.git

git客户端推荐

市面上的Git客户端我基本都用过了,我最推荐的一款Git客户端是:Tower 或者 Fork。

  • GitUp:

20180623时,网上看了下Git客户端的推荐排名:

SmartGit:

商业用途收费, 个人用户免费:

推荐书籍

  • 《pro.git中文版》

推荐连接

2018-06

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 03-网络抓包和代理工具:Whistle


Whistle 官网

  • Whistle 官网:
  • Whistle 的 GitHub:

Whistle 安装启动

1、Whistle 安装

(1)通过 npm 安装 Whistle

2、启动 whistle

w2 start

然后在浏览器输入http://127.0.0.1:8899/ 即可打开代理配置的页面。

3、配置代理

chrome浏览器配置代理:

可参考官方文档。

Firefox浏览器配置代理:

4、安装证书并添加信任:

证书下载后,双击安装,安装目录选择“登录”这个tab。安装完成后,记得执行 w2 restart重启 whistle。

手机设置代理

连接好指定的wifi后,点击那个wifi里的设置,将「代理」那一项,设置为手动,然后输入ip(电脑上的ip)、端口号(8899)。然后就可以通过电脑上的whistle工具,查看手机的网页请求。

注意,要保证手机和电脑在同一个网络下。

另外,还需要在手机的浏览器,地址栏输入rootca.pro,给手机安装证书。

捕获和拦截https请求

whistle安装证书后,可以拦截 https 请求。但是,我现在又不想拦截https请求了,该怎么卸载证书呢?

我发现,证书无法卸载,正确的操作是:

上图中,把红框部分,去掉勾选,就不捕获https了。谢谢azh童鞋。

参考链接:

移动端调试神器:eruda

手机连接代理时,如何看console.log的日志信息?
现在,代码里有console.log,如果是在电脑浏览器上看,可以直接在控制台查看console.log的内容。但是,如果手机连接代理,在手机上打开网页的话,要怎么查看console.log的内容呢?具体做法如下:

(1)在 whistle中,新建一个名叫Eruda H5的代理,代理中的内容是:

http://xxx.com htmlAppend://{eruda.html}

(2)新建一个values,里面的内容是:

然后就OK了。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



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 时,提示命名冲突。

错误的解决办法

git mv test Test

执行上面的命令时,会报错:fatal: renaming 'Test' failed: Invalid argument

正确的解决办法

# 将本地的 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 的默认配置项,不要随意自行修改,避免产生其他的麻烦。

参考链接

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:


常用插件

  • Emmet:快速生成HTML片段,比如输入ul>li*3可以快速生成:
<ul>
  <li></li>
  <li></li>
  <li></li>
</ul>

详细地址,Emmet教程

  • Snippets:快速生成 js 代码片段(用来处理代码片段的模板输出),详细地址
  • file icons:文件识别图标,使用这个插件会让你的编辑器显示对应的图标,详细地址
  • 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

参考链接:

相关设置

显示缩进线

settings -->Editor --> Show Indent Guide


前言

Emmet可以极大的提高 html 和 css 的编写效率,它提供了一种非常简练的语法规则。

举个例子,我们在编辑器中输入缩写代码:ul>li*6 ,然后按下 Tab 键,即可得到如下代码片段:

    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>

如何在某个语言中打开 Emmet 支持

默认情况下,你可以直接在 html、haml、jade、slim、jsx、xml、xsl、css、scss、sass、less、stylus、handlebars、php 和 javascriptreact 中使用 Emmet 。

但对于其他语言,你也可以通过如下设置将其打开:

"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

最终效果:

    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>

语法规则:

p5

最终效果:

padding: 5px;

新建文件时快速生成Html

安装如下插件:

  • FileHeader:自动创建文件开头模板,并且会根据最后的保存时间修改更新时间。官网链接。
  • CSS Format:css格式化。
  • Emmet:它能够让你在编辑器中书写CSS和HTML的缩写并且动态地拓展它,是一个能大幅度提高前端开发效率的一个工具。这个软件的安装过程比较久。官网教程。

开始使用:

新建文件,输入html:5,按[Ctrl + E] 或者 Tab 键,

参考链接:Sublime Text 新建文件快速生成Html【头部信息】和【代码补全】、【汉化】

常用插件

SublimeCodeIntel:JavaScript代码自动提示(不好用)

安装完成后,通过路径Perferences->Package Settings->SublimeCodeIntel->Setting - Defalut打开配置文件。

将

"codeintel_selected_catalogs": ["jQuery"]

改为:

"codeintel_selected_catalogs": ["JavaScript"]

保存后重启软件。

参考链接:#

javascript complet:JavaScript代码自动提示

在google上搜关键字"sublime javascript complete"找到的。另外还搜到一个SublimeAllAutocomp lete

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键,生成的代码如下;(符号>是包含的关系)

        <div>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
        </div>
    </div>

sublime text 快捷键

Win快捷键Mac快捷键作用备注
html:5+tabhtml结构代码
tab补全标签代码
tab补全标签代码比如,在html文件中输入div,然后 按住tab键后,会自动生成<div></div>。
Ctrl + Shift + DCmd + Shift + D复制当前行到下一行
Ctrl+Shift+K快速删除整行
Ctrl+鼠标左键单击集体输入
Ctrl+HOption+Cmd+F查找替换
Ctrl+/注释单行
Ctrl+Shift+/注释多行
Ctrl+L快速选中整行,继续操作则继续选择下一行,效果和 Shift + ↓ 效果一样
Ctrl+Shift+L先选中多行,再按下快捷键,会在每行行尾插入光标,即可同时编辑这些行经常与上一个快捷键结合起来使用
Ctrl + Shift +【↑/↓Ctrl + Cmd +↑/↓移动当前行
F11全屏

推荐阅读

我自己整理的。

参考链接


WebStorm的简单设置

1、主题修改:

可能大家会觉得软件的界面不太好看,我们可以换一下主题。选择菜单栏“File--settings--appearance--theme”,主题选择 Dracula:

2、导入第三方主题:

系统提供的两种主题可能都不太好看,我们可以进入网站来获取第三方主题,这里推荐两个主题,大家二选一即可:

上图中,在网站中将主题下载之后,是一个jar包。那怎么导入到WebStorm呢?

别着急,回到WebStorm,选择菜单栏“ File-Import Settings”,将下载好的jar包导入即可。

3、代码字体修改:

选择菜单栏“File--settings--Editor--Font”:

上图中,点击红框部分,然后弹出如下界面:

我们在上图中修改代码的字体。

修改完之后发现 WebStorm 的一些默认字体(比如侧边栏的工程目录的字体)并没有发生变化,如果想改的话,也可以改(我个人一般是不改的)。

4、关闭更新:

如下图所示:

5、快捷键习惯的修改:

7、配置代码的自动提示:

14、修改文件编码为UTF-8:

WebStorm 2017.3.3版本的默认编码方式是 GBK,我们还是统一设置为UTF-8吧,不要坑队友哦:

新建一个空的项目

配置完成后,可以开始新建一个项目文件夹(站点),项目通常包含如下内容:

  • 首页:index.html
  • 样式:css文件夹
- index.css
- 相同样式:全局样式、公共样式。起名为:base.css(基本样式)或者 global.css (全局样式)
  • 图片:images文件夹、文件
  • 特效:js文件夹、js文件

步骤如下:

(1)新建一个空的项目:

(2)然后新建一个html文件:

(3)新建一个空的文件夹,命名为css:

然后在这个css文件夹中,新建样式表:(比如index.css\base.css)

(4)最后新建一个images文件夹,用于存放土片。这样的话,一个基本的项目结构就搭建好了:

接下来,开始运用起你们的前端知识吧。

(5)如果要新建JS文件的话,操作如下:

使用技巧

多光标编辑

我们可以按住鼠标不松手,选中多个光标,然后同时编辑:

随时在浏览器中看代码效果

20180118_1658.png

如上图所示,我们可以点击右上角的浏览器图标,在各个浏览器中看效果。

实时查看颜色

写代码时如果想输入颜色,会自动提示颜色的预览。

点击最左侧的颜色预览,还能弹出调色板:

代码的自动补齐

(1)在html文档中,输入div*10,按tab键后,弹出的效果如下:

    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>

(2)在html文档中,输入如下部分:

.search-logo+.search-input+.search-car+.search-moreA

按tab键后,弹出的效果如下:

        <div class="search-logo"></div>
        <div class="search-input"></div>
        <div class="search-car"></div>
        <div class="search-moreA"></div>

你看,京东的搜索框就包含了这几个div:

20180122_1045.png

(3)方法的注释:

方法写完之后(注意,一定要先写完整),我们在方法的前面输入/**,然后回车,会发现,注释的格式会自动补齐。

比如:

/**
 * 功能:给定元素查找他的第一个元素子节点,并返回
 * @param ele
 * @returns {Element|*|Node}
 */
function getFirstNode(ele){
    var node = ele.firstElementChild || ele.firstChild;
    return node;
}

常用快捷键

标签环绕

输入一段字符后,按住Ctrl + Alt + T,可以用标签将这段字符环绕:

选中正行中的文本

比如下面这行:

    text-align: center;  /*让 li 里面的文本水平方向居中*/

如果直接按 【ctrl+C】的话,复制的是整行的内容,把前面的空格也包含进去了。如果不想复制空格,有另外一个办法:将光标放在行尾,然后按住【shift+home】,就能选中你想要的内容了。


控制台的使用

控制台查看源码

控制台的Sources标签可以查看源码。按住快捷键「cmd + P」,可以根据文件名查找源码文件。

其他

show user agent shadow DOM

把上图中的红框部分打钩。



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 相当于人的身体组织结构:

CSS 相当于人的衣服和打扮:

JS 相当于人的行为:


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。



title: 02-浏览器的介绍 publish: true


常见的浏览器

浏览器是网页运行的平台,常见的浏览器有谷歌(Chrome)、Safari、火狐(Firefox)、IE、Edge、Opera等。如下图所示:

我们重点需要学习的是 Chrome 浏览器。

浏览器的市场占有份额

浏览器的市场占有份额:

上面这张图的统计时间是2020年2月。

浏览器的组成

浏览器分成两部分:

  • 1、渲染引擎(即:浏览器内核)
  • 2、JS 引擎

1、渲染引擎(浏览器内核)

浏览器所采用的「渲染引擎」也称之为「浏览器内核」,用于解析 HTML和CSS、布局、渲染等工作。渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。

渲染引擎是浏览器兼容性问题出现的根本原因。

渲染引擎的英文叫做 Rendering Engine。通俗来说,它的作用就是:读取网页内容,计算网页的显示方式并显示在页面上。

常见浏览器的内核如下:

浏览器内核
chromeBlink
欧鹏Blink
360安全浏览器Blink
360极速浏览器Blink
SafariWebkit
Firefox 火狐Gecko
IETrident

备注: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-)
SafariJavaScriptCore,也称为Nitro,是 WebKit 引擎的一部分
IETrident
EdgeChakra。此外,ChakraCore是Chakra的开源版本,可以在不同的平台上使用。
OperaLinear 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的时候再回来看。

浏览器主要由下面这个七个部分组成:

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。

参考链接:(关于浏览器的工作管理,下面这篇文章,是精品中的精品,是必须要知道的)


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。



title: 03-初识HTML publish: true


编辑器相关

前端开发的编辑器软件,我首先推荐 VS Code,其次推荐Sublime Text。

有人说 WebStorm 也不错?但真实情况是,自从VS Code 问世之后,用 WebStorm 的人越来越少了。

PS:文件的后缀名不能决定文件格式,只能决定打开文件打开的方式。

VS Code 的使用

详情请移步至:第一次使用VS Code时你应该知道的一切配置

Sublime Text 的使用

详情请移步至:Sublime Text使用技巧

HTML的概述

HTML的概念

HTML 全称为 HyperText Markup Language,译为超文本标记语言。

HTML 不是一种编程语言,是一种描述性的标记语言。

作用:HTML是负责描述文档语义的语言。

概念:超文本

所谓的超文本,有两层含义:

(1)图片、音频、视频、动画、多媒体等内容,被称为超文本,因为它们超出了文本的限制。

(2)不仅如此,它还可以从一个文件跳转到另一个文件,与世界各地主机的文件进行连接。即:超级链接文本。

概念:标记语言

HTML 不是一种编程语言,是一种描述性的标记语言。这主要有两层含义:

(1)标记语言是一套标记标签。比如:标签<a>表示超链接、标签<img>表示图片、标签<h1>表示一级标题等等,它们都是属于 HTML 标签。

说的通俗一点就是:网页是由网页元素组成的,这些元素是由 HTML 标签描述出来,然后通过浏览器解析,就可以显示给用户看了。

(2)编程语言是有编译过程的,而标记语言没有编译过程,HTML标签是直接由浏览器解析执行。

HTML是负责描述文档语义的语言

HTML 格式的文件是一个纯本文文件(就是用txt文件改名而成),用一些标签来描述语义,这些标签在浏览器页面上是无法直观看到的,所以称之为“超文本标记语言”。

接下来,我们需要学习 HTML 中的很多“标签对儿”,这些“标签对儿”能够给文本不同的语义。

比如,面试的时候问你,<h1> 标签有什么作用?

  • 正确答案:给文本增加主标题的语义。
  • 错误答案:给文字加粗、加黑、变大。

关乎“语义”的更深刻理解,等接下来我们学习了各种标签,就明白了。

HTML的历史

html中标签发展趋势

其中,我们专门来对XHTML做一个介绍。

XHTML介绍: XHTML:Extensible Hypertext Markup Language,可扩展超文本标注语言。 XHTML的主要目的是为了取代HTML,也可以理解为HTML的升级版。 HTML的标记书写很不规范,会造成其它的设备(ipad、手机、电视等)无法正常显示。 XHTML与HTML4.0的标记基本上一样。 XHTML是严格的、纯净的HTML。

我们稍后将对XHTML的编写规范进行介绍。

HTML的专有名词

  • 网页 :由各种标记组成的一个页面就叫网页。
  • 主页(首页) : 一个网站的起始页面或者导航页面。
  • 标记: 比如<p>称为开始标记 ,</p>称为结束标记,也叫标签。每个标签都规定好了特殊的含义。
  • 元素:比如<p>内容</p>称为元素.
  • 属性:给每一个标签所做的辅助信息。
  • XHTML:符合XML语法标准的HTML。
  • DHTML:dynamic,动态的。javascript + css + html合起来的页面就是一个 DHTML。
  • HTTP:超文本传输协议。用来规定客户端浏览器和服务端交互时数据的一个格式。SMTP:邮件传输协议,FTP:文件传输协议。

书写第一个 HTML 页面

我们打开 VS Code 软件,新建一个文件,名叫test.html(注意,文件名是test,后缀名是html),保存到本地。

紧接着,在文件里,输入html:5,然后按一下键盘上的Tab键,就可以自动生成如下内容:

<!DOCTYPE html>
<html lang="en">
<head>
    
    
    <title>Document</title>
</head>
<body>

</body>
</html>

上面的内容,就是 html 页面的骨架。我们在此基础之上,新增几个标签,完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    
    
    <title>Document</title>
</head>
<body>
    <h3>我是三级标题</h3>
    <img src="" alt="">
    <a href="https://www.jd.com">我是超链接,可以点击一下</a>
</body>
</html>

标签写完之后,我们用 chrome 浏览器打开上面这个test.html文件,看看页面效果:

到此,第一个简单的 HTML 页面就写完了。是不是很有成就感?

HTML结构详解

HTML标签通常是成对出现的(双边标记),比如 <div> 和 </div>;也有少部分单标签(单边标记),如:<br />、<hr />和<img src="images/1.jpg" />等。

属性与标记之间、各属性之间需要以空格隔开。属性值以双引号括起来。

html骨架标签分类

标签名定义说明
<html></html>HTML标签页面中最大的标签,我们成为根标签
<head></head>文档的头部注意在head标签中我们必须要设置的标签是title
<title></title>文档的标题让页面拥有一个属于自己的网页标题
<body></body>文档的主体元素包含文档的所有内容,页面内容 基本都是放到body里面的

快速生成 html 的骨架

方式1:在 VS Code 中新建 html 文件,输入html:5,按 Tab键后,自动生成的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    
    
    
    <title>Document</title>
</head>
<body>

</body>
</html>

方式2:在Sublime Text中安装Emmet插件。新建html文件,输入html:5,按Tab键后,自动生成的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
	
	<title>Document</title>
</head>
<body>

</body>
</html>

方式3:在Sublime Text中安装Emmet插件。新建html文件,输入html:xt,按Tab键后,自动生成的代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	
	<title>Document</title>
</head>
<body>

</body>
</html>

上面的方式2和方式3中,我们会发现,第一行的内容有些不太一样,这就是我们接下来要讲的文档声明头。

1、文档声明头

任何一个标准的HTML页面,第一行一定是一个以<!DOCTYPE ……>开头的语句。这一行,就是文档声明头,即 DocType Declaration,简称DTD。

DTD可告知浏览器文档使用哪种 HTML 或 XHTML 规范。

HTML4.01有哪些规范呢?

HTML4.01这个版本是IE6开始兼容的。HTML5是IE9开始兼容的。如今,手机、移动端的网页,就可以使用HTML5了,因为其兼容性更高。

说个题外话,html1 至 html3 是美国军方以及高等研究所用的,并未对外公开。

HTML4.01里面有两大种规范,每大种规范里面又各有3种小规范。所以一共6种规范(见下图)。

HTML4.01里面规定了普通和XHTML两大种规范。HTML觉得自己有一些规定不严谨,比如,标签是否可以用大写字母呢?<H1></H1>所以,HTML就觉得,把一些规范严格的标准,又制定了一个XHTML1.0。在XHTML中的字母X,表示“严格的”。

总结一下,HTML4.01一共有6种DTD。说白了,HTML的第一行语句一共有6种情况:

下面对上图中的三种小规范进行解释:

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(文档声明头)如下:

<!DOCTYPE html>

2、页面语言 lang

下面这行标签,用于指定页面的语言类型:

<html lang="en">

最常见的语言类型有两种:

  • en:定义页面语言为英语。
  • zh-CN:定义页面语言为中文。

3、头标签 head

html5 的比较完整的骨架:

<!DOCTYPE html>
<html lang="en">
<head>
    
    
	
	
    
    
    <title>Document</title>
</head>
<body>

</body>
</html>

面试题:

  • 问:网页的head标签里面,表示的是页面的配置,有什么配置?
  • 答:字符集、关键词、页面描述、页面标题、IE适配、视口、iPhone小图标等等。

头标签内部的常见标签如下:

  • <title>:指定整个网页的标题,在浏览器最上方显示。
  • <base>:为页面上的所有链接规定默认地址或默认目标。
  • ``:提供有关页面的基本信息
  • <body>:用于定义HTML文档所要显示的内容,也称为主体标签。我们所写的代码必须放在此标签內。
  • ``
    • 3.2 采用 import 导入,必须写在`

洗白白

你懂得


![](http://img.smyhvae.com/2015-10-03-css-03.png)

### 3、CSS 和 HTML 结合方式三:引入外部样式表 css 文件

**引入样式表文件**的方式又分为两种:

-   (1)**采用``

-   (2)**采用 import**,必须写在`
    </head>
    <body>
        <p>千古壹号学完了安卓,继续学<span>前端</span>哟</p>
    </body>
</html>

【总结】需要注意的是:

(1)所有的标签,都可以是选择器。比如 ul、li、label、dt、dl、input。

(2)无论这个标签藏的多深,一定能够被选择上。

(3)选择的所有,而不是一个。

2、ID 选择器:规定用#来定义

针对某一个特定的标签来使用,只能使用一次。css 中的 ID 选择器以”#”来定义。

举例:

#mytitle{ border:3px dashed green; }

效果:

id 选择器的选择符是“#”。

任何的 HTML 标签都可以有 id 属性。表示这个标签的名字。这个标签的名字,可以任取,但是:

  • (1)只能有字母、数字、下划线。
  • (2)必须以字母开头。
  • (3)不能和标签同名。比如 id 不能叫做 body、img、a。

另外,特别强调的是:HTML 页面,不能出现相同的 id,哪怕他们不是一个类型。比如页面上有一个 id 为 pp 的 p,一个 id 为 pp 的 div,是非法的!

一个标签可以被多个 css 选择器选择:

比如,我们可以同时让标签选择器和 id 选择器作用于同一个标签。如下:

然后我们通过网页的审查元素看一下效果:

现在,假设选择器冲突了,比如 id 选择器说这个文字是红色的,标签选择器说这个文字是绿色的。那么听谁的? 实际上,css 有着非常严格的计算公式,能够处理冲突.

一个标签可以被多个 css 选择器选择,共同作用,这就是“层叠式”的第一层含义(第一层含义和第二层含义,放到 css 基础的第三篇文章里讲)。

3、类选择器:规定用圆点.来定义

、针对你想要的所有标签使用。优点:灵活。

css 中用.来表示类。举例如下:

.one{ width:800px; }

效果:

和 id 非常相似,任何的标签都可以携带 id 属性和 class 属性。class 属性的特点:

  • 特性 1:类选择器可以被多种标签使用。
  • 特性 2:同一个标签可以使用多个类选择器。用空格隔开。举例如下:(正确)
<h3 class="teshu  zhongyao">我是一个h3啊</h3>

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

<h3 class="teshu" class="zhongyao">我是一个h3啊</h3>

类选择器使用的举例:

类选择器的使用,能够决定一个人的 css 水平。

比如,我们现在要做下面这样一个页面:

正确的思路,就是用所谓“公共类”的思路,就是我们类就是提供“公共服务”,比如有绿、大、线,一旦携带这个类名,就有相应的样式变化。对应 css 里的代码如下:

然后让每个标签去选取自己想要用的类选择器:

<p class="lv da">段落1</p>
<p class="lv xian">段落2</p>
<p class="da xian">段落3</p>

也就是说:

(1)不要去试图用一个类名,把某个标签的所有样式写完。这个标签要多携带几个类,共同完成这个标签的样式。

(2)每一个类要尽可能小,有“公共”的概念,能够让更多的标签使用。

问题:到底用 id 还是用 class?

答案:尽可能的用 class,除非极特殊的情况可以用 id。

原因:id 是 js 用的。也就是说,js 要通过 id 属性得到标签,所以 css 层面尽量不用 id,要不然 js 就很别扭。另一层面,我们会认为一个有 id 的元素,有动态效果。

举例如下:

上图所示,css 和 js 都在用同一个 id,会出现不好沟通的情况。

我们记住这句话:类上样式,id 上行为。意思是说,class属性交给 css 使用,id属性交给 js 使用。

上面这三种选择器的区别:

  • 标签选择器针对的是页面上的一类标签。
  • ID 选择器是只针对特定的标签(一个),ID 是此标签在此页面上的唯一标识。
  • 类选择器可以被多种标签使用。

4、通配符*:匹配任何标签

通用选择器,将匹配任何标签。不建议使用,IE 有些版本不支持,大网站增加客户端负担。

效率不高,如果页面上的标签越多,效率越低,所以页面上不能出现这个选择器。

举例:

* {
    margin-left: 0px;
    margin-top: 0px;
}

效果:

CSS 的几种高级选择器

高级选择器:

  • 后代选择器:用空格隔开
  • 交集选择器:选择器之间紧密相连
  • 并集选择器(分组选择器):用逗号隔开
  • 伪类选择器

下面详细讲一下这几种高级(扩展)选择器。

1、后代选择器: 定义的时候用空格隔开

对于E F这种格式,表示所有属于 E 元素后代的 F 元素,有这个样式。空格就表示后代。

后代选择器,就是一种平衡:共性、特性的平衡。当要把某一个部分的所有的什么,进行样式改变,就要想到后代选择器。

后代选择器,描述的是祖先结构。

看定义可能有点难理解,我们来看例子吧。

举例 1:

空格就表示后代。.div1 p 表示.div1的后代所有的p。

这里强调一下:这两个标签不一定是连续紧挨着的,只要保持一个后代的关联即可。也就是说,选择的是后代,不一定是儿子。

举例:

上方代码的意思是说:定义了<h3>标签中的<b>标签中的<i>标签的样式。 同理:h3 和 b 和 i 标签不一定是连续紧挨着的,只要保持一个后代的关联即可。

效果:

或者还有下面这种写法:

上面的这种写法,<h3>标签和<i>标签并不是紧挨着的,但他们保持着一种后代关系。

还有下面这种写法:(含类选择器、id 选择器都是可以的)

我们在开头说了:后代选择器,描述的是一种祖先结构。我们举个例子来说明这句话:

<!DOCTYPE html>
<html lang="en">
    <head>
        
        <title>Document</title>
        
    </head>
    <body>
        <div>
            <div class="div2">
                <div class="div3">
                    <div class="div4">
                        <p>我是什么颜色?</p>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

上面 css 中的div div p,也能使文字的颜色变红。通过浏览器的审查元素,我们可以看到 p 元素的祖先列表:

讲到这里,我们再提一个 VS Code 的快捷键:

在 VS Code 中输入p#haha,按 tab 键后,会生成<p id="haha"></p>。

在 VS Code 中输入p.haha,按 tab 键后,会生成<p class="haha"></p>。

2、交集选择器:定义的时候紧密相连

定义交集选择器的时候,两个选择器之间紧密相连。一般是以标签名开头,比如div.haha,再比如p.special。

如果后一个选择器是类选择器,则写为div.special;如果后一个选择器 id 选择器,则写为div#special。

来看下面这张图就明白了:

h3.special {
    color: red;
}

选择的元素要求同时满足两个条件:必须是 h3 标签,然后必须是 special 标签。

举例:

<!DOCTYPE html>
<html lang="en">
    <head>
        
        <title>交集选择器测试</title>
        
    </head>
    <body>
        <h3 class="special zhongyao">标题1</h3>
        <h3 class="special">我也是标题</h3>
        <p>我是段落</p>
    </body>
</html>

效果如下:

注意,交集选择器没有空格。所以,没有空格的div.red(交集选择器)和有空格的div .red(后代选择器)不是一个意思。

交集选择器可以连续交:(一般不要这么写)

h3.special.zhongyao {
    color: red;
}

上面这种写法,是 IE7 开始兼容的,IE6 不兼容。

3、并集选择器:定义的时候用逗号隔开

三种基本选择器都可以放进来。

举例:

p,h1,.title1,#one {
    color: red;
}

效果:

一些 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 机器猫"关键字,然后在不同的浏览器中打开如下链接:

测试结果如下:

我们可以在百度统计里查看浏览器的市场占有率:

  • IE9 5.94%
  • IE8 21.19%
  • IE7 4.79%
  • IE6 4.11%

我们可以在中查看

我们要知道典型的 IE6 兼容问题(面试要问),但是做项目我们兼容到 IE8 即可。不解决 IE8 以下的兼容问题,目的在于:培养更高的兴趣和眼光,别天天的跟 IE6 较劲。

我们可以用「IETester」软件看看 css 在各个浏览器中的显示效果。

1.子代选择器,用符号>表示

IE7 开始兼容,IE6 不兼容。
div > p {
    color: red;
}

div 的儿子 p。和 div 的后代 p 的截然不同。

能够选择:

<div>
    <p>我是div的儿子</p>
</div>

不能选择:

<div>
    <ul>
        <li>
            <p>我是div的重孙子</p>
        </li>
    </ul>
</div>

2.序选择器

IE8 开始兼容;IE6、7 都不兼容

设置无序列表<ul>中的第一个<li>为红色:

设置无序列表<ul>中的最后一个<li>为红色:

ul li:last-child {
    color: blue;
}

序选择器还有更复杂的用法,以后再讲。

由于浏览器的更新需要过程,所以现在如果公司还要求兼容 IE6、7,那么就要自己写类名:

<ul>
    <li class="first">项目</li>
    <li>项目</li>
    <li>项目</li>
    <li>项目</li>
    <li>项目</li>
    <li>项目</li>
    <li>项目</li>
    <li>项目</li>
    <li>项目</li>
    <li class="last">项目</li>
</ul>

用类选择器来选择第一个或者最后一个:

ul li.first{ color:red; } ul li.last{ color:blue; }

3.下一个兄弟选择器

IE7 开始兼容,IE6 不兼容。

+表示选择下一个兄弟

上方的选择器意思是:选择的是 h3 元素后面紧挨着的第一个兄弟。

<h3>我是一个标题</h3>
<p>我是一个段落</p>
<p>我是一个段落</p>
<p>我是一个段落</p>
<h3>我是一个标题</h3>
<p>我是一个段落</p>
<p>我是一个段落</p>
<p>我是一个段落</p>
<h3>我是一个标题</h3>
<p>我是一个段落</p>
<p>我是一个段落</p>
<p>我是一个段落</p>
<h3>我是一个标题</h3>

效果如下:

这种选择器作用不大。

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 06-CSS盒模型详解 publish: true


盒子模型

前言

盒子模型,英文即box model。无论是div、span、还是a都是盒子。

但是,图片、表单元素一律看作是文本,它们并不是盒子。这个很好理解,比如说,一张图片里并不能放东西,它自己就是自己的内容。

盒子中的区域

一个盒子中主要的属性就5个:width、height、padding、border、margin。如下:

  • width和height:内容的宽度、高度(不是盒子的宽度、高度)。
  • padding:内边距。
  • border:边框。
  • margin:外边距。

盒子模型的示意图:

代码演示:

上面这个盒子,width:200px; height:200px; 但是真实占有的宽高是302*302。 这是因为还要加上padding、border。

注意:宽度和真实占有宽度,不是一个概念!来看下面这例子。

标准盒模型和IE盒模型

我们目前所学习的知识中,以标准盒子模型为准。

标准盒子模型:

IE盒子模型:

上图显示:

在 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并不是控件的一部分。

<body>标签也有margin

<body>标签有必要强调一下。很多人以为<body>标签占据的是整个页面的全部区域,其实是错误的,正确的理解是这样的:整个网页最大的盒子是<document>,即浏览器。而<body>是<document>的儿子。浏览器给<body>默认的margin大小是8个像素,此时<body>占据了整个页面的一大部分区域,而不是全部区域。来看一段代码。

<!doctype html>
<html lang="en">
 <head>
  
  
  
  
  
  <title>Document</title>

	

 </head>

 <body>

	<div>有生之年</div>
	<div>狭路相逢</div>

 </body>

</html>

上面的代码中,我们对div标签设置了边距等信息。打开google浏览器,按住F12,显示效果如下:

认识width、height

一定要知道,在前端开发工程师眼中,世界中的一切都是不同的。

比如说,丈量稿纸,前端开发工程师只会丈量内容宽度:

下面这两个盒子,真实占有宽高,都是302*302:

盒子1:

.box1{
	width: 100px;
	height: 100px;
	padding: 100px;
	border: 1px solid red;
}

盒子2:

.box2{
	width: 250px;
	height: 250px;
	padding: 25px;
	border: 1px solid red;
}

真实占有宽度 = 左border + 左padding + width + 右padding + 右border

上面这两个盒子的盒模型图如下:

如果想保持一个盒子的真实占有宽度不变,那么加width的时候就要减padding。加padding的时候就要减width。因为盒子变胖了是灾难性的,这会把别的盒子挤下去。

认识padding

padding区域也有颜色

padding就是内边距。padding的区域有背景颜色,css2.1前提下,并且背景颜色一定和内容区域的相同。也就是说,background-color将填充所有border以内的区域。

效果如下:

padding有四个方向

padding是4个方向的,所以我们能够分别描述4个方向的padding。

方法有两种,第一种写小属性;第二种写综合属性,用空格隔开。

小属性的写法:

	padding-top: 30px;
	padding-right: 20px;
	padding-bottom: 40px;
	padding-left: 100px;

综合属性的写法:(上、右、下、左)(顺时针方向,用空格隔开。margin的道理也是一样的)

padding:30px 20px 40px 100px;

如果写了四个值,则顺序为:上、右、下、左。

如果只写了三个值,则顺序为:上、右和左、下。

如果只写了两个值,则顺序为:上和下、左和右。

比如说:

padding: 30px 40px;

则顺序等价于:30px 40px 30px 40px;

要懂得,用小属性层叠大属性。比如:

padding: 20px;
padding-left: 30px;

上面的padding对应盒子模型为:

下面的写法:

padding-left: 30px;
padding: 20px;

第一行的小属性无效,因为被第二行的大属性层叠掉了。

下面的题,会做了,说明你明白了。

一些题目

题目1:说出下面盒子真实占有宽高,并画出盒模型图。

	div{
		width: 200px;
		height: 200px;
		padding: 10px 20px 30px;
		padding-right: 40px;
		border: 1px solid #000;
	}

答案:

题目2:说出下面盒子真实占有宽高,并画出盒模型图。

	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大属性,层叠掉了他们。

盒子模型如下:

题目3:现在给你一个盒子模型图,请写出代码,试着用最最简单的方法写。

答案:

	width:123px;
	height:123px;
	padding:20px 40px;
	border:1px solid red;

题目4:现在给你一个盒子模型图,请写出代码,试着用最最简单的方法写。

答案:

	width:123px;
	height:123px;
	padding:20px;
	padding-right:40px;
	border:1px solid red;

一些元素,默认带有padding

一些元素,默认带有padding,比如ul标签。如下:

上图显示,不加任何样式的ul,也是有40px的padding-left。

所以,我们做站的时候,为了便于控制,总是喜欢清除这个默认的padding。

可以使用*进行清除:

		*{
			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就是边框。边框有三个要素:像素(粗细)、线型、颜色。

比如:


.div1{
	width: 10px;
	height: 10px;
	border: 2px solid red;
}

颜色如果不写,默认是黑色。另外两个属性如果不写,则无法显示边框。

border-style

border的所有的线型如下:(我们可以通过查看CSS参考手册得到)

比如border:10px ridge red;这个属性,在chrome和firefox、IE中有细微差别:(因为可以显示出效果,因此并不是兼容性问题,只是有细微差别而已)

如果公司里面的设计师是处女座的,追求极高的页面还原度,那么不能使用css来制作边框。就要用到图片,就要切图了。

所以,比较稳定的border-style就几个:solid、dashed、dotted。

border拆分

border是一个大综合属性。比如说:

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;

效果如下:

(1)按三要素拆:

border-width:10px;    //边框宽度
border-style:solid;   //线型
border-color:red;     //颜色。

等价于:

border:10px solid red;

(2)按方向来拆:

border-top:10px solid red;
border-right:10px solid red;
border-bottom:10px solid red;
border-left:10px solid red;

等价于:

border:10px solid red;

(3)按三要素和方向来拆:(就是把每个方向的,每个要素拆开。3*4 = 12)

	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;

等价于:

border:10px solid red;

工作中到底用什么?很简答:什么简单用什么。但要懂得,用小属性层叠大属性。举例如下:

为了实现上方效果,写法如下:

border:10px solid red;
border-right-color:blue;

为了实现上方效果,写法如下:

border:10px solid red;
border-style:solid dashed;

border可以没有:

border:none;

可以某一条边没有:

border-left: none;

也可以调整左边边框的宽度为0:

border-left-width: 0;

border-image 属性

比如:

border-image: url(.img.png) 30 round;

这个属性在实际开发中用得不多,暂时忽略。

举例1:利用 border 属性画一个三角形(小技巧)

完整代码如下:

div{
	width: 0;
	height: 0;
	border: 50px solid transparent;
	border-top-color: red;
	border-bottom: none;
}

步骤如下:

(1)当我们设置盒子的width和height为0时,此时效果如下:

(2)然后将border的底部取消:

(3)最后设置border的左边和右边为白色或者透明:

这样,一个三角形就画好了。

举例2:利用 border 属性画一个三角形(更推荐的技巧)

上面的例子1中,画出来的是直角三角形,可如果我想画等边三角形,要怎么做呢?

完整代码如下:(用 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;
}

效果如下:

另外,我们在上方代码的基础之上,再加一个 border-radus: 20px; 就能画出一个扇形。

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 07-浮动 publish: true


文本主要内容

  • 标准文档流
- 标准文档流的特性
- 行内元素和块级元素
- 行内元素和块级元素的相互转换
  • 浮动的性质
  • 浮动的清除
  • 浏览器的兼容性问题
  • 浮动中margin相关
  • 关于margin的IE6兼容问题

标准文档流

宏观地讲,我们的web页面和photoshop等设计软件有本质的区别:web页面的制作,是个“流”,必须从上而下,像“织毛衣”。而设计软件,想往哪里画个东西,都能画。

标准文档流的特性

(1)空白折叠现象:

无论多少个空格、换行、tab,都会折叠为一个空格。

比如,如果我们想让img标签之间没有空隙,必须紧密连接:

<img src="images/0.jpg" /><img src="images/1.jpg" /><img src="images/2.jpg" />

(2)高矮不齐,底边对齐:

举例如下:

(3)自动换行,一行写不满,换行写。

行内元素和块级元素

学习的初期,我们就要知道,标准文档流等级森严。标签分为两种等级:

  • 行内元素
  • 块级元素

我们可以举一个例子,看看块级元素和行内元素的区别:

上图中可以看到,h1标签是块级元素,占据了整行,span标签是行内元素,只占据内容这一部分。

现在我们尝试给两个标签设置宽高。效果如下:

上图中,我们尝试给两个标签设置宽高,但发现,宽高属性只对块级元素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标签。

我们把上面的分类画一个图,即可一目了然:

行内元素和块级元素的相互转换

我们可以通过display属性将块级元素和行内元素进行相互转换。display即“显示模式”。

块级元素可以转换为行内元素:

一旦,给一个块级元素(比如div)设置:

display: inline;

那么,这个标签将立即变为行内元素,此时它和一个span无异。inline就是“行内”。也就是说:

  • 此时这个div不能设置宽度、高度;
  • 此时这个div可以和别人并排了。

举例如下:

行内元素转换为块级元素:

同样的道理,一旦给一个行内元素(比如span)设置:

display: block;

那么,这个标签将立即变为块级元素,此时它和一个div无异。block”是“块”的意思。也就是说:

  • 此时这个span能够设置宽度、高度
  • 此时这个span必须霸占一行了,别人无法和他并排
  • 如果不设置宽度,将撑满父亲

举例如下:

标准流里面的限制非常多,导致很多页面效果无法实现。如果我们现在就要并排、并且就要设置宽高,那该怎么办呢?办法是:移民!脱离标准流!

css中一共有三种手段,使一个元素脱离标准文档流:

  • (1)浮动
  • (2)绝对定位
  • (3)固定定位

这便引出我们今天要讲的内容:浮动。

浮动的性质

浮动是css里面布局用的最多的属性。

现在有两个div,分别设置宽高。我们知道,它们的效果如下:

此时,如果给这两个div增加一个浮动属性,比如float: left;,效果如下:

这就达到了浮动的效果。此时,两个元素并排了,并且两个元素都能够设置宽度、高度了(这在上一段的标准流中,不能实现)。

浮动想学好,一定要知道三个性质。接下来讲一讲。

性质1:浮动的元素脱标

脱标即脱离标准流。我们来看几个例子。

证明1:

上图中,在默认情况下,两个div标签是上下进行排列的。现在由于float属性让上图中的第一个<div>标签出现了浮动,于是这个标签在另外一个层面上进行排列。而第二个<div>还在自己的层面上遵从标准流进行排列。

证明2:

上图中,span标签在标准流中,是不能设置宽高的(因为是行内元素)。但是,一旦设置为浮动之后,即使不转成块级元素,也能够设置宽高了。

所以能够证明一件事:一旦一个元素浮动了,那么,将能够并排了,并且能够设置宽高了。无论它原来是个div还是个span。所有标签,浮动之后,已经不区分行内、块级了。

性质2:浮动的元素互相贴靠

我们来看一个例子就明白了。

我们给三个div均设置了float: left;属性之后,然后设置宽高。当改变浏览器窗口大小时,可以看到div的贴靠效果:

上图显示,3号如果有足够空间,那么就会靠着2号。如果没有足够的空间,那么会靠着1号大哥。 如果没有足够的空间靠着1号大哥,3号自己去贴左墙。

不过3号自己去贴墙的时候,注意:

上图显示,3号贴左墙的时候,并不会往1号里面挤。

同样,float还有一个属性值是right,这个和属性值left是对称的。

性质3:浮动的元素有“字围”效果

来看一张图就明白了。我们让div浮动,p不浮动。

上图中,我们发现:div挡住了p,但不会挡住p中的文字,形成“字围”效果。

总结:标准流中的文字不会被浮动的盒子遮挡住。(文字就像水一样)

关于浮动我们要强调一点,浮动这个东西,为避免混乱,我们在初期一定要遵循一个原则:永远不是一个东西单独浮动,浮动都是一起浮动,要浮动,大家都浮动。

性质4:收缩

收缩:一个浮动的元素,如果没有设置width,那么将自动收缩为内容的宽度(这点非常像行内元素)。

举例如下:

上图中,div本身是块级元素,如果不设置width,它会单独霸占整行;但是,设置div浮动后,它会收缩

浮动的补充(做网站时注意)

上图所示,将para1和para2设置为浮动,它们是div的儿子。此时para1+para2的宽度小于div的宽度。效果如上图所示。可如果设置para1+para2的宽度大于div的宽度,我们会发现,para2掉下来了:

布置一个作业

布置一个作业,要求实现下面的效果:

为实现上方效果,代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	
	<title>Document</title>
	
</head>
<body>
	<!-- 头部 -->
	<div class="header">
		<div class="logo">logo</div>
		<div class="language">语言选择</div>
		<div class="nav">导航条</div>
	</div>

	<!-- 主要内容 -->
	<div class="content">
		<div class="banner">大广告</div>
		<div class="rightPart">
			<div class="main">
				<div class="news">
					<div class="news1"></div>
					<div class="news2"></div>
					<div class="news3"></div>
				</div>
				<div class="hotpic"></div>
			</div>
			<div class="links"></div>
		</div>
	</div>

	<!-- 页尾 -->
	<div class="footer"></div>
</body>
</html>

其实,这个页面的布局是下面这个网站:

浮动的清除

这里所说的清除浮动,指的是清除浮动与浮动之间的影响。

前言

通过上面这个例子,我们发现,此例中的网页就是通过浮动实现并排的。

比如说一个网页有header、content、footer这三部分。就拿content部分来举例,如果设置content的儿子为浮动,但是,这个儿子又是一个全新的标准流,于是儿子的儿子仍然在标准流里。

从学习浮动的第一天起,我们就要明白,浮动有开始,就要有清除。我们先来做个实验。

下面这个例子,有两个块级元素div,div没有任何属性,每个div里有li,效果如下:

上面这个例子很简单。可如果我们给里面的<li>标签加浮动。效果却成了下面这个样子:

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
	
	<title>Document</title>
	
</head>
<body>
	<div class="box1">
		<ul>
			<li>生命壹号1</li>
			<li>生命壹号2</li>
			<li>生命壹号3</li>
			<li>生命壹号4</li>
		</ul>
	</div>
	<div class="box2">
		<ul>
			<li>许嵩1</li>
			<li>许嵩2</li>
			<li>许嵩3</li>
			<li>许嵩4</li>
		</ul>
	</div>
</body>
</html>

效果如下:

上图中,我们发现:第二组中的第1个li,去贴靠第一组中的最后一个li了(我们本以为这些li会分成两排)。

这便引出我们要讲的:清除浮动的第一种方式。 那该怎么解决呢?

方法1:给浮动元素的祖先元素加高度

造成前言中这个现象的根本原因是:li的父亲div没有设置高度,导致这两个div的高度均为0px(我们可以通过网页的审查元素进行查看)。div的高度为零,导致不能给自己浮动的孩子,撑起一个容器。

撑不起一个容器,导致自己的孩子没办法在自己的内部进行正确的浮动。

好,现在就算给这个div设置高度,可如果div自己的高度小于孩子的高度,也会出现不正常的现象:

给div设置一个正确的合适的高度(至少保证高度大于儿子的高度),就可以看到正确的现象:

总结:

如果一个元素要浮动,那么它的祖先元素一定要有高度。

有高度的盒子,才能关住浮动。(记住这句过来人的经验之语)

只要浮动在一个有高度的盒子中,那么这个浮动就不会影响后面的浮动元素。所以就是清除浮动带来的影响了。

方法2:clear:both;

网页制作中,高度height其实很少出现。为什么?因为能被内容撑高!也就是说,刚刚我们讲解的方法1,工作中用得很少。

那么,能不能不写height,也把浮动清除了呢?也让浮动之间,互不影响呢?

这个时候,我们可以使用clear:both;这个属性。如下:

clear:both;

clear就是清除,both指的是左浮动、右浮动都要清除。clear:both的意思就是:不允许左侧和右侧有浮动对象。

这种方法有一个非常大的、致命的问题,它所在的标签,margin属性失效了。读者可以试试看。

margin失效的本质原因是:上图中的box1和box2,高度为零。

方法3:隔墙法

上面这个例子中,为了防止第二个div贴靠到第二个div,我们可以在这两个div中间用一个新的div隔开,然后给这个新的div设置clear: both;属性;同时,既然这个新的div无法设置margin属性,我们可以给它设置height,以达到margin的效果(曲线救国)。这便是隔墙法。

我们看看例子效果就知道了:

上图这个例子就是隔墙法。

内墙法:

近些年,有演化出了“内墙法”:

上面这个图非常重要,当作内墙法的公式,先记下来。

为了讲内墙法,我们先记住一句重要的话:一个父亲是不能被浮动的儿子撑出高度的。举例如下:

(1)我们在一个div里放一个有宽高的p,效果如下:(很简单)

(2)可如果在此基础之上,给p设置浮动,却发现父亲div没有高度了:

(3)此时,我么可以在div的里面放一个div(作为内墙),就可以让父亲div恢复高度:

于是,我们采用内墙法解决前言中的问题:

与外墙法相比,内墙法的优势(本质区别)在于:内墙法可以给它所在的家撑出宽度(让box1有高)。即:box1的高度可以自适应内容。

而外墙法,虽然一道墙可以把两个div隔开,但是这两个div没有高,也就是说,无法wrap_content。

清除浮动方法4:overflow:hidden;

我们可以使用如下属性:

overflow:hidden;

overflow即“溢出”, hidden即“隐藏”。这个属性的意思是“溢出隐藏”。顾名思义:所有溢出边框的内容,都要隐藏掉。如下:

上图显示,overflow:hidden;的本意是清除溢出到盒子外面的文字。但是,前端开发工程师发现了,它能做偏方。如下:

一个父亲不能被自己浮动的儿子,撑出高度。但是,只要给父亲加上overflow:hidden; 那么,父亲就能被儿子撑出高了。这是一个偏方。

举个例子:

那么对于前言中的例子,我们同样可以使用这一属性:

这一招,实际上生成了BFC。关于BFC的解释,详见本项目的另外一篇文章《前端面试/CSS盒模型及BFC.md》。

浮动清除的总结

我们在上一段讲了四种清除浮动的方法,本段来进行一个总结。

浮动的元素,只能被有高度的盒子关住。 也就是说,如果盒子内部有浮动,这个盒子有高,那么妥妥的,浮动不会互相影响。

1、加高法

工作上,我们绝对不会给所有的盒子加高度,这是因为麻烦,并且不能适应页面的快速变化。

<div>     //设置height
	<p></p>
	<p></p>
	<p></p>
</div>

<div>    //设置height
	<p></p>
	<p></p>
	<p></p>
</div>

2、clear:both;法

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

<div>
	<p></p>
	<p></p>
	<p></p>
</div>

<div>   //clear:both;
	<p></p>
	<p></p>
	<p></p>
</div>

浮动确实被清除了,不会互相影响了。但是有一个问题,就是margin失效。两个div之间,没有任何的间隙了。

3、隔墙法

在两部分浮动元素中间,建一个墙。隔开两部分浮动,让后面的浮动元素,不去追前面的浮动元素。 墙用自己的身体当做了间隙。

<div>
	<p></p>
	<p></p>
	<p></p>
</div>

<div class="cl h10"></div>

<div>
	<p></p>
	<p></p>
	<p></p>
</div>

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

内墙法:

<div>
	<p></p>
	<p></p>
	<p></p>
	<div class="cl h10"></div>
</div>

<div>
	<p></p>
	<p></p>
	<p></p>
</div>

内墙法的优点就是,不仅仅能够让后部分的p不去追前部分的p了,并且能把第一个div撑出高度。这样,这个div的背景、边框就能够根据p的高度来撑开了。

4、overflow:hidden;

这个属性的本意,就是将所有溢出盒子的内容,隐藏掉。但是,我们发现这个东西能够用于浮动的清除。 我们知道,一个父亲,不能被自己浮动的儿子撑出高度,但是,如果这个父亲加上了overflow:hidden;那么这个父亲就能够被浮动的儿子撑出高度了。这个现象,不能解释,就是浏览器的偏方。 并且,overflow:hidden;能够让margin生效。

清除浮动的例子:

我们现在举个例子,要求实现下图中无序列表部分的效果:

对比一下我们讲的四种清除浮动的方法。如果用外墙法,ul中不能插入div标签,因为ul中只能插入li,如果插入li的墙,会浪费语义。如果用内墙法,不美观。综合对比,还是用第四种方法来实现吧,这会让标签显得极其干净整洁:

上方代码中,如果没有加overflow:hidden;,那么第二行的li会紧跟着第一行li的后面。

浏览器的兼容性问题

讲一下上述知识点涉及到的浏览器兼容问题。

兼容性1(微型盒子)

兼容性的第一条:IE6不支持小于12px的盒子,任何小于12px的盒子,在IE6中看都大。即:IE 6不支持微型盒子。

举个例子。我们设置一个height为 5px 、宽度为 200px的盒子,看下在IE 8和 IE 6中的显示效果:

解决办法很简单,就是将盒子的字号大小,设置为小于盒子的高,比如,如果盒子的高为5px,那就把font-size设置为0px(0px < 5px)。如下:

height: 5px;
_font-size: 0px;

我们现在介绍一下浏览器hack。hack就是“黑客”,就是使用浏览器提供的后门,针对某一种浏览器做兼容。

IE6留了一个后门:只要给css属性之前,加上下划线,这个属性就是IE6的专有属性。

比如说,我们给背景颜色这个属性加上下划线,就变成了_background-color: green;。效果如下:

于是乎,为了解决微型盒子(即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是可以叠加的,即水平方向没有塌陷现象)。如下图所示:

如果不在标准流,比如盒子都浮动了,那么两个盒子之间是没有塌陷现象的。

盒子居中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)

	<div>
		<p></p>
	</div>

上面的结构中,我们尝试通过给儿子p一个margin-top:50px;的属性,让其与父亲保持50px的上边距。结果却看到了下面的奇怪的现象:

此时我们给父亲div加一个border属性,就正常了:

如果父亲没有border,那么儿子的margin实际上踹的是“流”,踹的是这“行”。所以,父亲整体也掉下来了。

margin这个属性,本质上描述的是兄弟和兄弟之间的距离; 最好不要用这个marign表达父子之间的距离。

所以,如果要表达父子之间的距离,我们一定要善于使用父亲的padding,而不是儿子的margin。

关于margin的IE6兼容问题

IE6的双倍margin的bug:

当出现连续浮动的元素,携带与浮动方向相同的margin时,队首的元素,会双倍marign。

	<ul>
		<li></li>
		<li></li>
		<li></li>
	</ul>

解决方案:

(1)使浮动的方向和margin的方向,相反。

所以,你就会发现,我们特别喜欢,浮动的方向和margin的方向相反。并且,前端开发工程师,把这个当做习惯了。

	float: left;
	margin-right: 40px;

(2)使用hack:(没必要,别惯着这个IE6)

单独给队首的元素,写一个一半的margin:

<li class="no1"></li>
ul li.no1{
	_margin-left:20px;
}

PS:双倍margin的问题,面试经常问哦。

IE6的3px bug

解决办法:不用管,因为根本就不允许用儿子踹父亲(即描述父子之间的距离,请用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)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 08-CSS属性:定位属性 publish: true


CSS的定位属性有三种,分别是绝对定位、相对定位、固定定位。

	position: absolute;  <!-- 绝对定位 -->

	position: relative;  <!-- 相对定位 -->

	position: fixed;     <!-- 固定定位 -->

下面逐一介绍。

相对定位

相对定位:让元素相对于自己原来的位置,进行位置调整(可用于盒子的位置微调)。

我们之前学习的背景属性中,是通过如下格式:

	background-position:向右偏移量 向下偏移量;

但这回的定位属性,是通过如下格式:

	position: relative;
	left: 50px;
	top: 50px;

相对定位的举例:

<!doctype html>
<html lang="en">
 <head>
  
  
  
  
  
  <title>Document</title>

	
 </head>

 <body>

	<div class="div1">有生之年</div>
	<div class="div2">狭路相逢</div>

 </body>

</html>

效果:

相对定位不脱标

相对定位:不脱标,老家留坑,别人不会把它的位置挤走。

也就是说,相对定位的真实位置还在老家,只不过影子出去了,可以到处飘。

相对定位的用途

如果想做“压盖”效果(把一个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;

如果要描述上面这张图的方向,我们可以首先可以这样描述:

	position: relative;
	left: 200px;
	top: 100px;

因为left: 200px等价于right: -200px,所以这张图其实有四种写法。

绝对定位

绝对定位:定义横纵坐标。原点在父容器的左上角或左下角。横坐标用left表示,纵坐标用top或者bottom表示。

格式举例如下:

	position: absolute;  /*绝对定位*/
	left: 10px;  /*横坐标*/
	top/bottom: 20px;  /*纵坐标*/

绝对定位脱标

绝对定位的盒子脱离了标准文档流。

所以,所有的标准文档流的性质,绝对定位之后都不遵守了。

绝对定位之后,标签就不区分所谓的行内元素、块级元素了,不需要display:block就可以设置宽、高了。

绝对定位的参考点(重要)

(1)如果用top描述,那么参考点就是页面的左上角,而不是浏览器的左上角:

(2)如果用bottom描述,那么参考点就是浏览器首屏窗口尺寸(好好理解“首屏”二字),对应的页面的左下角:

为了理解“首屏”二字的含义,我们来看一下动态图:

问题:

答案:

用bottom的定位的时候,参考的是浏览器首屏大小对应的页面左下角。

以盒子为参考点

一个绝对定位的元素,如果父辈元素中也出现了已定位(无论是绝对定位、相对定位,还是固定定位)的元素,那么将以父辈这个元素,为参考点。

如下:(子绝父相)

以下几点需要注意。

(1) 要听最近的已经定位的祖先元素的,不一定是父亲,可能是爷爷:

		<div class="box1">        相对定位
			<div class="box2">    没有定位
				<p></p>           绝对定位,将以box1为参考,因为box2没有定位,box1就是最近的父辈元素
			</div>
		</div>

再比如:

		<div class="box1">        相对定位
			<div class="box2">    相对定位
				<p></p>           绝对定位,将以box2为参考,因为box2是自己最近的父辈元素
			</div>
		</div>

(2)不一定是相对定位,任何定位,都可以作为儿子的参考点:

子绝父绝、子绝父相、子绝父固,都是可以给儿子定位的。但是在工程上,如果子绝、父绝,没有一个盒子在标准流里面了,所以页面就不稳固,没有任何实战用途。

工程应用:

“子绝父相”有意义:这样可以保证父亲没有脱标,儿子脱标在父亲的范围里面移动。于是,工程上经常这样做:

父亲浮动,设置相对定位(零偏移),然后让儿子绝对定位一定的距离。

(3)绝对定位的儿子,无视参考的那个盒子的padding:

下图中,绿色部分是父亲div的padding,蓝色部分p是div的内容区域。此时,如果div相对定位,p绝对定位,那么, p将无视父亲的padding,在border内侧为参考点,进行定位:

工程应用:

绝对定位非常适合用来做“压盖”效果。我们来举个lagou.com上的例子。

现在有如下两张图片素材:

要求作出如下效果:

代码实现如下:

<!DOCTYPE html>
<html lang="en">
<head>
	
	<title>Document</title>
	
</head>
<body>
	<div class="box">
		<span class="dtc"></span>
		<div class="image">
			<img src="http://img.smyhvae.com/20180116_1116.jpg" alt="">
		</div>
		<h4>广东深圳宝安区建安一路海雅缤纷城4楼</h4>
	</div>
</body>
</html>

代码解释如下:

  • 为了显示“多套餐”那个小图,我们需要用到精灵图。
  • “多套餐”下方黑色背景的文字都是通过“子绝父相”的方式的盖在大海报image的上方的。

代码的效果如下:

让绝对定位中的盒子在父亲里居中

我们知道,如果想让一个标准流中的盒子在父亲里居中(水平方向看),可以将其设置margin: 0 auto属性。

可如果盒子是绝对定位的,此时已经脱标了,如果还想让其居中(位于父亲的正中间),可以这样做:

	div {
		width: 600px;
		height: 60px;
		position: absolute;  绝对定位的盒子
		left: 50%;           首先,让左边线居中
		top: 0;
		margin-left: -300px;  然后,向左移动宽度(600px)的一半
	}

如上方代码所示,我们先让这个宽度为600px的盒子,左边线居中,然后向左移动宽度(600px)的一半,就达到效果了。

我们可以总结成一个公式:

left:50%; margin-left:负的宽度的一半

固定定位

固定定位:就是相对浏览器窗口进行定位。无论页面如何滚动,这个盒子显示的位置不变。

备注:IE6不兼容。

用途1:网页右下角的“返回到顶部”

比如我们经常看到的网页右下角显示的“返回到顶部”,就可以固定定位。

	

用途2:顶部导航条

我们经常能看到固定在网页顶端的导航条,可以用固定定位来做。

需要注意的是,假设顶部导航条的高度是60px,那么,为了防止其他的内容被导航条覆盖,我们要给body标签设置60px的padding-top。

顶部导航条的实现如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	
	<title>Document</title>
	
</head>
<body>
	<div class="nav">
		<div class="inner_c">
			<ul>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
				<li><a href="#">网页栏目</a></li>
			</ul>
		</div>
	</div>
</body>
</html>

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在下层)

现在加一个z-index属性,要求效果如下:

第五条分析:

z-index属性的应用还是很广泛的。当好几个已定位的标签出现覆盖的现象时,我们可以用这个z-index属性决定,谁处于最上方。也就是层级的应用。

层级:

(1)必须有定位(除去static)

(2)用z-index来控制层级数。

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 09-CSS案例讲解:博雅互动 publish: true


前言

CSS已经学了一些基础内容了,我们来讲解一个小案例吧。以博雅互动的官网首页举例。

版心

首页的版心如下:

这里我们要普及一个概念,叫“版心”。版心是页面中主要内容所在的区域。

比如说,网站左上角的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不兼容)。我们暂时使用切图来实现。

我们最好把「加入我们」这个超链接<a>放到div里,然后设置div的margin和padding,而不是直接设置<a>的边距。

我们起个名字叫class=jrwm是没有问题的,这在工作当中很常见,如果写成class=join_us反倒很别扭。

暂时我们的做法是:

  • (1)给class=jrwm_box这个div里放一个class=jrwm的div。class=jrwm用来放绿色的背景图片。
  • (2)在class=jrwm里放一个超链接,并将超链接转为块级元素。

最终,导航栏的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>Document</title>
    
</head>
<body>
    <div class="header">
        <div class="inner_c">
            <div class="logo">
                <img src="images/logo.png " alt="">
            </div>
            <div class="nav">
                <ul>
                    <li><a href="#" class="current">首页</a></li>
                    <li><a href="#">博雅游戏</a></li>
                    <li><a href="#">博雅新闻</a></li>
                    <li><a href="#">关于我们</a></li>
                    <li><a href="#">客服中心</a></li>
                    <li class="last"><a href="#">投资者关系</a></li>
                </ul>
            </div>
            <div class="jrwm_box">
                <div class="jrwm">
                    <a href="https://www.google.com/" target="_blank">加入我们</a>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

导航栏的效果如下:

banenr图

因为涉及到 js 的内容,这里先不讲内容区域轮播图的效果。

我们首先在导航条和banner图之间加一道墙,即class=cl,然后采用隔墙法对其设置clear: both;的属性。

然后设置banner的背景图片属性,添加banner图。

内容区域的制作

导航栏+banner+内容区域的完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>Document</title>
    
</head>
<body>
    <div class="header">
        <div class="inner_c">
            <div class="logo">
                <img src="images/logo.png " alt="">
            </div>
            <div class="nav">
                <ul>
                    <li><a href="#" class="current">首页</a></li>
                    <li><a href="#">博雅游戏</a></li>
                    <li><a href="#">博雅新闻</a></li>
                    <li><a href="#">关于我们</a></li>
                    <li><a href="#">客服中心</a></li>
                    <li class="last"><a href="#">投资者关系</a></li>
                </ul>
            </div>
            <div class="jrwm_box">
                <div class="jrwm">
                    <a href="https://www.google.com/" target="_blank">加入我们</a>
                </div>
            </div>
        </div>
    </div>

    <!-- 在导航条和banner之间隔一堵墙 -->
    <div class="cl"></div>

    <div class="banner"></div>

    <!-- 内容区域 -->
    <div class="content inner_c">
        <div class="product">
            <ul>
                <li>
                    <p><img src="images/pro1.jpg" alt="" /></p>
                    <h3>BPT宣传片</h3>
                    <p class="djbf">
                        <a href="#">点击播放</a>
                    </p>
                </li>
                <li>
                    <p><img src="images/pro2.jpg" alt="" /></p>
                    <h3>BPT宣传片</h3>
                    <p class="djbf">
                        <a href="#">点击播放</a>
                    </p>
                </li>
                <li>
                    <p><img src="images/pro3.jpg" alt="" /></p>
                    <h3>BPT宣传片</h3>
                    <p class="djbf">
                        <a href="#">点击播放</a>
                    </p>
                </li>
                <li class="last">
                    <p><img src="images/pro4.jpg" alt="" /></p>
                    <h3>BPT宣传片</h3>
                    <p class="djbf">
                        <a href="#">点击播放</a>
                    </p>
                </li>
            </ul>
        </div>
    </div>
</body>
</html>

代码解释:

(1)导航栏,左侧的logo:

错误的写法:

可能会有人直接将img标签作为logo的布局:

    <div class="logo">
        <img src="images/logo.png " alt="">
    </div>

然后将img的样式设置为:

    .header .logo{
        float: left;
        margin-right: 40px;
    }

这样写虽然视觉效果上达到了,但是搜索引擎是搜不到图片的,不利于SEO。

正确的写法:

正确的写法是将超链接作为logo的布局,里面放入文字(文字可以被SEO):

			<h1 class="logo">
				<a href="#">
					博雅互动-世界上最好的游戏公司
				</a>
			</h1>

然后将logo设置为背景图:

		.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)内容区域,“点击播放”右侧的小三角形:

我们在“点击播放”的右侧放了一个三角形。这个很有技巧。

代码截取如下:

    .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+内容区域的效果如下:

工程文件:2018-03-20-boya.rar

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 10-CSS3选择器详解 publish: true


CSS3介绍

CSS3在CSS2基础上,增强或新增了许多特性, 弥补了CSS2的众多不足之处,使得Web开发变得更为高效和便捷。

CSS3的现状

  • 浏览器支持程度不够好,有些需要添加私有前缀
  • 移动端支持优于PC端
  • 不断改进中
  • 应用相对广泛

应对的策略:渐进增强

  • (1)坚持渐进增强的原则:让低版本浏览器能正常访问页面,高版本的浏览器用户体验更好。【重要】

比如说,同样是一个头像,可能在低版本的浏览器中,头像方的;在高版本的浏览器中,头像是圆的。

  • (2)考虑用户群体。
  • (3)遵照产品的方案。

参考链接:

浏览器的版本问题

由于CSS3普遍存在兼容性问题,为了避免因兼容性带来的干扰,浏览器的建议版本为:

  • Chrome浏览器 version 46+
  • Firefox浏览器 firefox 42+

如何使用手册

CSS参考手册的网址:

CSS参考手册的下载链接:

在查看CSS参考手册时,需要注意以下符号:

比如说,{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属性来举例。代码举例:

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>选择器 - 属性</title>
    
</head>
<body>
<div class="wrapper">
    <header>CSS3-属性选择器</header>
    <section>
        <header>简介</header>
        <div class="wrap-box">
            <form action="">

                <ul>
                    <li>
                        姓名: <input type="text">
                    </li>
                    <li>
                        密码: <input type="password">
                    </li>

                    <li>
                        性别: <input type="radio">男
                        <input type="radio"> 女
                    </li>
                    <li>
                        兴趣: <input type="checkbox" name="" id="">写代码
                    </li>
                    <li>
                        <input type="submit" value="提交">
                    </li>
                </ul>
            </form>
        </div>
    </section>
    <section class="attr1">
        <header>E[attr]</header>
        <div class="wrap-box">
            <a href="./a.rmvb" class="download download-movie">下载</a>
            <a href="./b.rmvb" class="download download-movie">下载</a>
            <a href="./a.mp3" class="download download-music">下载</a>
        </div>
    </section>
    <section class="attr2">
        <header>E[attr~=attr]</header>
        <div class="wrap-box">
            <a href="./a.rmvb" class="download download-movie">下载</a>
            <a href="./b.rmvb" class="download download-movie">下载</a>
            <a href="./a.mp3" class="download download-music">下载</a>
        </div>
    </section>
    <section class="attr3">
        <header>E[attr|=attr]</header>
        <div class="wrap-box">
            <a href="./a.rmvb" class="download">下载</a>
            <a href="./b.rmvb" class="download-movie">下载</a>
            <a href="./a.mp3" class="download-music">下载</a>
        </div>
    </section>
    <section class="attr4">
        <header>E[attr*=val]</header>
        <div class="wrap-box">
            <a href="./a.rmvb" class="download">下载</a>
            <a href="./b.rmvb" class="moviedownload">下载</a>
            <a href="./a.mp3" class="downloadmusic">下载</a>
        </div>
    </section>
    <section class="attr5">
        <header>E[attr^=val]</header>
        <div class="wrap-box">
            <a href="./a.rmvb" class="download">下载</a>
            <a href="./b.rmvb" class="moviedownload">下载</a>
            <a href="./a.mp3" class="downloadmusic">下载</a>
        </div>
    </section>
    <section class="attr6">
        <header>E[attr$=val]</header>
        <div class="wrap-box">
            <a href="./a.rmvb" class="download">下载</a>
            <a href="./b.rmvb" class="moviedownload">下载</a>
            <a href="./a.mp3" class="downloadmusic">下载</a>
        </div>
    </section>
</div>
</body>
</html>

最后来张表格:

结构伪类选择器

伪类选择器的标志性符号是 :。

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)这个属性也很有意思。比如,针对下面这样一组标签:

    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>

上方代码中:

  • 如果选择器写成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元素。要配合锚点使用。

举例:

我们可以把多个伪类选择器结合起来使用,比如:

如果想把上图中,第一行的前三个 span 标红,我们可以这样使用结构伪类选择器:

	dt:first-child span:nth-of-type(-n+3) {
		color: red;
	}

最后来张表格:

伪元素选择器

伪元素选择器的标志性符号是 ::。

1、格式:(第一部分)

  • E::before 设置在 元素E 前面(依据对象树的逻辑结构)的内容,配合content属性一起使用。
  • E::after 设置在 元素E 后面(依据对象树的逻辑结构)的内容,配合content属性一起使用。

E:after、E:before 在旧版本里是伪类,在 CSS3 这个新版本里是伪元素。新版本里,E:after、E:before会被自动识别为E::after、E::before,按伪元素来对待,这样做的目的是用来做兼容处理。

举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>

<span>生命壹号</span>
</body>
</html>

效果如下:

上图可以看出:

  • 通过伪元素选择器,就可以添加出类似于span标签的效果(记得要结合 content 属性使用)。
  • 通过这两个属性添加的伪元素,是行内元素,需要转换成块元素才能设置宽高。

2、格式:(第二部分)

  • E::first-letter 设置元素 E 里面的第一个字符的样式。
  • E::first-line 设置元素 E 里面的第一行的样式。
  • E::selection 设置元素 E 里面被鼠标选中的区域的样式(一般设置颜色和背景色)。

E::first-letter 的举例:

E::first-line的举例:

最后来张表格:

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 11-CSS3属性详解(一) publish: true


前言

我们在上一篇文章中学习了CSS3的选择器,本文来学一下CSS3的一些属性。

本文主要内容:

  • 文本
  • 盒模型中的 box-sizing 属性
  • 处理兼容性问题:私有前缀
  • 边框
  • 背景属性
  • 渐变

文本

text-shadow:设置文本的阴影

格式举例:

	text-shadow: 20px 27px 22px pink;

参数解释:水平位移 垂直位移 模糊程度 阴影颜色。

效果举例:

举例:凹凸文字效果

text-shadow 可以设置多个阴影,每个阴影之间使用逗号隔开。我们来看个例子。

代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="ao">生命壹号</div>
<div class="tu">生命壹号</div>
</body>
</html>

效果如下:

上图中,实现凹凸文字效果的方式比较简单,给左上角放黑色的阴影,右下角放白色的阴影,就达到了凹下去的效果。

盒模型中的 box-sizing 属性

我们在之前的文章中专门讲过盒子模型。

CSS3 对盒模型做出了新的定义,即允许开发人员指定盒子宽度和高度的计算方式。

这就需要用到 box-sizing属性。它的属性值可以是:content-box、border-box。解释如下。

外加模式:(css的默认方式)

	box-sizing: content-box;

解释:此时设置的 width 和 height 是内容区域的宽高。盒子的实际宽度 = 设置的 width + padding + border。此时改变 padding 和 border 的大小,也不会改变内容的宽高,而是盒子的总宽高发生变化。

内减模式:【需要注意】

	box-sizing: border-box;

解释:此时设置的 width 和 height 是盒子的总宽高。盒子的实际宽度 = 设置的 width。此时改变 padding 和 border 的大小,会改变内容的宽高,盒子的总宽高不变。

处理兼容性问题:私有前缀

通过网址 可以查询CSS3各特性的支持程度。

处理兼容性问题的常见方法:为属性添加私有前缀。

如此方法不能解决,应尽量避免使用,无需刻意去处理CSS3的兼容性问题。

私有前缀的举例:

比如说,我想给指定的div设置下面这样一个属性:

	background: linear-gradient(left, green, yellow);

上面这个属性的作用是:添加从左到右的线性渐变,颜色从绿色变为黄色。

如果直接这样写属性,是看不到效果的:

此时,我们可以为浏览器添加不同的私有前缀,属性就可以生效了。

格式如下:

    -webkit-: 谷歌 苹果
    -moz-:火狐
    -ms-:IE
    -o-:欧朋

格式举例如下:

    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);

效果如下:

边框

边框的属性很多,其中边框圆角和边框阴影这两个属性,应用十分广泛,兼容性也相对较好,且符合渐进增强的原则,需要重点熟悉。

边框圆角:border-radius 属性

边框的每个圆角,本质上是一个圆,圆有水平半径和垂直半径:如果二者相等,就是圆;如果二者不等, 就是椭圆。

单个属性的写法:

	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;

举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>

<div class="parent">
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="box">5</div>
    <div class="box">6</div>
</div>
</body>
</html>

效果如下:

边框阴影:box-shadow 属性

格式举例:

	box-shadow: 水平偏移 垂直偏移 模糊程度 阴影大小 阴影颜色

	box-shadow: 15px 21px 48px -2px #666;

参数解释:

  • 水平偏移:正值向右 负值向左。
  • 垂直偏移:正值向下 负值向上。
  • 模糊程度:不能为负值。

效果如下:

另外,后面还可以再加一个inset属性,表示内阴影。如果不写,则默认表示外阴影。例如:

	box-shadow:3px 3px 3px 3px #666 inset;

效果如下:

注意:设置边框阴影不会改变盒子的大小,即不会影响其兄弟元素的布局。

我们还可以设置多重边框阴影,实现更好的效果,增强立体感。

边框图片

边框图片有以下属性:

	/* 边框图片的路径*/
	border-image-source: url("images/border.png");

	/* 图片边框的裁剪*/
	border-image-slice: 27;

	/*图片边框的宽度*/
	border-image-width: 27px;

	/*边框图片的平铺*/
	/* repeat :正常平铺 但是可能会显示不完整*/
	/*round: 平铺 但是保证 图片完整*/
	/*stretch: 拉伸显示*/
	border-image-repeat: stretch;

我们也可以写成一个综合属性:

	 border-image: url("images/border.png") 27/20px round;

这个属性要好好理解,我们假设拿下面这张图来作为边框图片:

这张图片将会被“切割”成九宫格形式,然后进行平铺。四个角位置、形状保持不变,中心位置和水平垂直向两个方向平铺:

再具体一点:

常见的边框图片汇总

CSS3的更多属性,且看下文继续。

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 12-CSS3属性详解:动画详解 publish: true


前言

本文主要内容:

  • 过渡:transition
  • 2D 转换 transform
  • 3D 转换 transform
  • 动画:animation

过渡:transition

transition的中文含义是过渡。过渡是CSS3中具有颠覆性的一个特征,可以实现元素不同状态间的平滑过渡(补间动画),经常用来制作动画效果。

  • 补间动画:自动完成从起始状态到终止状态的的过渡。不用管中间的状态。
  • 帧动画:通过一帧一帧的画面按照固定顺序和速度播放。如电影胶片。

参考链接:补间动画基础

transition 包括以下属性:

  • transition-property: all; 如果希望所有的属性都发生过渡,就使用all。
  • transition-duration: 1s; 过渡的持续时间。
  • transition-timing-function: linear; 运动曲线。属性值可以是:
- `linear` 线性
- `ease`  减速
- `ease-in` 加速
- `ease-out` 减速
- `ease-in-out`  先加速后减速
  • transition-delay: 1s; 过渡延迟。多长时间后再执行这个过渡动画。

上面的四个属性也可以写成综合属性:

	transition: 让哪些属性进行过度 过渡的持续时间 运动曲线 延迟时间;

	transition: all 3s linear 0s;

其中,transition-property这个属性是尤其需要注意的,不同的属性值有不同的现象。我们来示范一下。

如果设置 transition-property: width,意思是只让盒子的宽度在变化时进行过渡。效果如下:

如果设置 transition-property: all,意思是让盒子的所有属性(包括宽度、背景色等)在变化时都进行过渡。效果如下:

案例:小米商品详情

代码:

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>CSS 过渡</title>
    
</head>
<body>
<div class="content">
    <div class="item">
        <img src="./images/1.png" alt="">
    </div>

    <div class="item">
        <img src="./images/2.png" alt="">
        <span class="desc"></span>
    </div>
    <div class="item">
        <img src="./images/3.jpg" alt="">
        <span class="desc"></span>
    </div>
</div>
</body>
</html>

效果如下:

动画效果录制的比较差,但真实体验还是可以的。

工程文件:2018-02-08-小米商品详情过渡

2D 转换

转换是 CSS3 中具有颠覆性的一个特征,可以实现元素的位移、旋转、变形、缩放,甚至支持矩阵方式。

转换再配合过渡和动画,可以取代大量早期只能靠 Flash 才可以实现的效果。

在 CSS3 当中,通过 transform 转换来实现 2D 转换或者 3D 转换。

  • 2D转换包括:缩放、移动、旋转。

我们依次来讲解。

1、缩放:scale

格式:

	transform: scale(x, y);

	transform: scale(2, 0.5);

参数解释: x:表示水平方向的缩放倍数。y:表示垂直方向的缩放倍数。如果只写一个值就是等比例缩放。

取值:大于1表示放大,小于1表示缩小。不能为百分比。

格式举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">
    <div class="box1">1</div>
    <div class="box2">2</div>
    <div class="box3">3</div>
</div>

</body>
</html>

效果:

上图可以看到,给 box1 设置 2D 转换,并不会把兄弟元素挤走。

2、位移:translate

格式:

	transform: translate(水平位移, 垂直位移);

	transform: translate(-50%, -50%);

参数解释:

  • 参数为百分比,相对于自身移动。
  • 正值:向右和向下。 负值:向左和向上。如果只写一个值,则表示水平移动。

格式举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    

</head>
<body>
<div class="box">
    <div class="box1">1</div>
    <div class="box2">2</div>
    <div class="box3">3</div>
</div>

</body>
</html>

效果:

上图中,因为我在操作的时候,鼠标悬停后,立即进行了略微的移动,所以产生了两次动画。正确的效果应该是下面这样的:

应用:让绝对定位中的盒子在父亲里居中

我们知道,如果想让一个标准流中的盒子在父亲里居中(水平方向看),可以将其设置margin: 0 auto属性。

可如果盒子是绝对定位的,此时已经脱标了,如果还想让其居中(位于父亲的正中间),可以这样做:

	div {
		width: 600px;
		height: 60px;
		position: absolute;  绝对定位的盒子
		left: 50%;           首先,让左边线居中
		top: 0;
		margin-left: -300px;  然后,向左移动宽度(600px)的一半
	}

如上方代码所示,我们先让这个宽度为600px的盒子,左边线居中,然后向左移动宽度(600px)的一半,就达到效果了。

现在,我们还可以利用偏移 translate 来做,这也是比较推荐的写法:

	div {
	    width: 600px;
	    height: 60px;
	    background-color: red;
	    position: absolute;       绝对定位的盒子
	    left: 50%;               首先,让左边线居中
	    top: 0;
	    transform: translate(-50%);    然后,利用translate,往左走自己宽度的一半【推荐写法】
	}

3、旋转:rotate

格式:

	transform: rotate(角度);

	transform: rotate(45deg);

参数解释:正值 顺时针;负值:逆时针。

举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">1</div>

</div>
</body>
</html>

效果:

注意,上方代码中,我们给盒子设置了 transform 中的 rotate 旋转,但同时还要给盒子设置 transition 过渡。如果没有这行过渡的代码,旋转会直接一步到位,效果如下:(不是我们期望的效果)

案例1:小火箭

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
    <img  class="rocket" src="images/rocket.png" alt=""/>
</body>
</html>

上方代码中,我们将 transform 的两个小属性合并起来写了。

小火箭图片的url:

案例2:扑克牌

rotate 旋转时,默认是以盒子的正中心为坐标原点的。如果想改变旋转的坐标原点,可以用transform-origin属性。格式如下:

	transform-origin: 水平坐标 垂直坐标;

	transform-origin: 50px 50px;

	transform-origin: center bottom;   //旋转时,以盒子底部的中心为坐标原点

我们来看一下 rotate 结合 transform-origin 的用法举例。

代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">
    <img src="images/pk1.png"/>
    <img src="images/pk2.png"/>
    <img src="images/pk1.png"/>
    <img src="images/pk2.png"/>
    <img src="images/pk1.png"/>
    <img src="images/pk2.png"/>
    <img src="images/pk1.png"/>
    <img src="images/pk2.png"/>
    <img src="images/pk1.png"/>
    <img src="images/pk2.png"/>
    <img src="images/pk1.png"/>
    <img src="images/pk2.png"/>
    <img src="images/pk1.png"/>
</div>
</body>
</html>

效果如下:

4、倾斜

暂略。

3D 转换

1、旋转:rotateX、rotateY、rotateZ

3D坐标系(左手坐标系)

如上图所示,伸出左手,让拇指和食指成“L”形,大拇指向右,食指向上,中指指向前方。拇指、食指和中指分别代表X、Y、Z轴的正方向,这样我们就建立了一个左手坐标系。

浏览器的这个平面,是X轴、Y轴;垂直于浏览器的平面,是Z轴。

旋转的方向:(左手法则)

左手握住旋转轴,竖起拇指指向旋转轴的正方向,正向就是其余手指卷曲的方向。

从上面这句话,我们也能看出:所有的3d旋转,对着正方向去看,都是顺时针旋转。

格式:

	transform: rotateX(360deg);    //绕 X 轴旋转360度

	transform: rotateY(360deg);    //绕 Y 轴旋转360度

	transform: rotateZ(360deg);    //绕 Z 轴旋转360度

格式举例:

(1)rotateX 举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="rotateX">
    <img src="images/x.jpg" alt=""/>
</div>
</body>
</html>

效果:

上方代码中,我们最好加个透视的属性,方能看到3D的效果;没有这个属性的话,图片旋转的时候,像是压瘪了一样。

而且,透视的是要加给图片的父元素 div,方能生效。我们在后面会讲解透视属性。

(2)rotateY 举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="rotateY">
    <img src="images/y.jpg" alt=""/>
</div>
</body>
</html>

效果:

(3)rotateZ 举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="rotateZ">
    <img src="images/z.jpg" alt=""/>
</div>
</body>
</html>

效果:

案例:百度钱包的水平翻转效果

现在有下面这张图片素材:

要求做成下面这种效果:

上面这张图片素材其实用的是精灵图。实现的代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">
    <div class="box1"></div>
    <div class="box2"></div>
</div>
</body>
</html>

2、移动:translateX、translateY、translateZ

格式:

	transform: translateX(100px);    //沿着 X 轴移动

	transform: translateY(360px);    //沿着 Y 轴移动

	transform: translateZ(360px);    //沿着 Z 轴移动

格式举例:

(1)translateX 举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">

</div>
</body>
</html>

效果:

(2)translateY 举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">

</div>
</body>
</html>

效果:

(3)translateZ 举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">

</div>
</body>
</html>

效果:

上方代码中,如果不加透视属性,是看不到translateZ的效果的。

3、透视:perspective

电脑显示屏是一个 2D 平面,图像之所以具有立体感(3D效果),其实只是一种视觉呈现,通过透视可以实现此目的。

透视可以将一个2D平面,在转换的过程当中,呈现3D效果。但仅仅只是视觉呈现出 3d 效果,并不是正真的3d。

格式有两种写法:

  • 作为一个属性,设置给父元素,作用于所有3D转换的子元素
  • 作为 transform 属性的一个值,做用于元素自身。

格式举例:

perspective: 500px;

4、3D呈现(transform-style)

3D元素构建是指某个图形是由多个元素构成的,可以给这些元素的父元素设置transform-style: preserve-3d来使其变成一个真正的3D图形。属性值可以如下:

	transform-style: preserve-3d;     /* 让 子盒子 位于三维空间里 */

	transform-style: flat;            /* 让子盒子位于此元素所在的平面内(子盒子被扁平化) */

案例:立方体

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">
    <div class="up">上</div>
    <div class="down">下</div>
    <div class="left">左</div>
    <div class="right">右</div>
    <div class="forward">前</div>
    <div class="back">后</div>
</div>
</body>
</html>

动画

动画是CSS3中具有颠覆性的特征,可通过设置多个节点 来精确控制一个或一组动画,常用来实现复杂的动画效果。

1、定义动画的步骤

(1)通过@keyframes定义动画;

(2)将这段动画通过百分比,分割成多个节点;然后各节点中分别定义各属性;

(3)在指定元素里,通过 animation 属性调用动画。

之前,我们在 js 中定义一个函数的时候,是先定义,再调用:

    js 定义函数:
        function fun(){ 函数体 }

     调用:
     	fun();

同样,我们在 CSS3 中定义动画的时候,也是先定义,再调用:

    定义动画:
        @keyframes 动画名{
            from{ 初始状态 }
            to{ 结束状态 }
        }

     调用:
      animation: 动画名称 持续时间;

其中,animation属性的格式如下:

            animation: 定义的动画名称 持续时间  执行次数  是否反向  运动曲线 延迟执行。(infinite 表示无限次)

            animation: move1 1s  alternate linear 3;

            animation: move2 4s;

定义动画的格式举例:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="box">

</div>
</body>
</html>

注意好好看代码中的注释。

效果如下:

2、动画属性

我们刚刚在调用动画时,animation属性的格式如下:

animation属性的格式如下:

            animation: 定义的动画名称  持续时间  执行次数  是否反向  运动曲线 延迟执行。(infinite 表示无限次)

            animation: move1 1s  alternate linear 3;

            animation: move2 4s;

可以看出,这里的 animation 是综合属性,接下来,我们把这个综合属性拆分看看。

(1)动画名称:

	animation-name: move;

(2)执行一次动画的持续时间:

	animation-duration: 4s;

备注:上面两个属性,是必选项,且顺序固定。

(3)动画的执行次数:

	animation-iteration-count: 1;       //iteration的含义表示迭代

属性值infinite表示无数次。

(3)动画的方向:

	animation-direction: alternate;

属性值:normal 正常,alternate 反向。

(4)动画延迟执行:

	animation-delay: 1s;

(5)设置动画结束时,盒子的状态:

	animation-fill-mode: forwards;

属性值: forwards:保持动画结束后的状态(默认), backwards:动画结束后回到最初的状态。

(6)运动曲线:

	animation-timing-function: ease-in;

属性值可以是:linear ease-in-out steps()等。

注意,如果把属性值写成 steps(),则表示动画不是连续执行,而是间断地分成几步执行。我们接下来专门讲一下属性值 steps()。

steps()的效果

我们还是拿上面的例子来举例,如果在调用动画时,我们写成:

	animation: move2 4s steps(2);

效果如下:

有了属性值 steps(),我们就可以作出很多不连续地动画效果。比如时钟;再比如,通过多张静态的鱼,作出一张游动的鱼。

step()举例:时钟的简易模型

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div></div>
</body>
</html>

上方代码,我们通过一个黑色的长条div,旋转360度,耗时60s,分成60步完成。即可实现。

效果如下:

动画举例:摆动的鱼

现在,我们要做下面这种效果:

PS:图片的url是,图片较大,如无法观看,可在浏览器中单独打开。

为了作出上面这种效果,要分成两步。

(1)第一步:让鱼在原地摆动

鱼在原地摆动并不是一张 gif动图,她其实是由很多张静态图间隔地播放,一秒钟播放完毕,就可以了:

上面这张大图的尺寸是:宽 509 px、高 2160 px。

我们可以理解成,每一帧的尺寸是:宽 509 px、高 270 px。270 * 8 = 2160。让上面这张大图,在一秒内从 0px 的位置往上移动2160px,分成8步来移动。就可以实现了。

代码是:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="sharkBox">
    <div class="shark"></div>
</div>

</div>
</body>
</html>

效果如下:

我们不妨把上面的动画的持续时间从1s改成 8s,就可以看到动画的慢镜头:

这下,你应该恍然大悟了。

(2)第二步:让鱼所在的盒子向前移动。

实现的原理也很简单,我们在上一步中已经让shark这个盒子实现了原地摇摆,现在,让 shark 所在的父盒子 sharkBox向前移动,即可。完整版代码是:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="sharkBox">
    <div class="shark"></div>
</div>

</div>
</body>
</html>

大功告成。

工程文件:2018-02-09-fishes.rar

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



title: 13-CSS3属性:Flex布局图文详解 publish: true


前言

CSS3中的 flex 属性,在布局方面做了非常大的改进,使得我们对多个元素之间的布局排列变得十分灵活,适应性非常强。其强大的伸缩性和自适应性,在网页开中可以发挥极大的作用。

flex 初体验

我们先来看看下面这个最简单的布局:

上面这张图中的布局是我们都熟悉的:默认文档流中,在一个父容器里放置多个块级的子元素,那么,这些子元素会默认从上往下排列。

在此基础之上,如果我给父容器仅仅加一个 display: flex属性,此时,这些子元素的布局会摇身一变:

没错,子元素们会在水平方向上,从左至右排列,就是这么神奇。到此为止,你已经掌握了关于 flex 的一半的知识。

flex 布局的优势

1、flex 布局的子元素不会脱离文档流,很好地遵从了“流的特性”。

但你如果用 float 来做布局,float 属性的元素会脱离文档流,而且会涉及到各种 BFC、清除浮动的问题。浮动相关的问题,比较麻烦,所以也成了面试必问的经典题目。但有了 flex 布局之后,这些问题都不存在的。

2、flex 是一种现代的布局方式,是 W3C 第一次提供真正用于布局的 CSS 规范。 flex 提供了非常丰富的属性,非常灵活,让布局的实现更佳多样化,且方便易用。

flex 唯一的缺点就在于,它不支持低版本的 IE 浏览器。

flex 的兼容性问题

上图中可以看到, flex 布局不支持 IE9 及以下的版本;IE10及以上也只是部分支持。如果你的页面不需要处理 IE浏览器的兼容性问题,则可以放心大胆地使用 flex 布局。

但是,比如网易新闻、淘宝这样的大型网站,面对的是海量用户,即便使用低版本浏览器的用户比例很少,但绝对基数仍然是很庞大的。因此,这些网站为了兼容低版本的 IE 浏览器,暂时还不敢尝试使用 flex 布局。

概念:弹性盒子、子元素

在讲 flex 的知识点之前,我们事先约定两个概念:

  • 弹性盒子:指的是使用 display:flex 或 display:inline-flex 声明的父容器。
  • 子元素/弹性元素:指的是父容器里面的子元素们(父容器被声明为 flex 盒子的情况下)。

概念:主轴和侧轴

在上面的“初体验”例子中,我们发现,弹性盒子里面的子元素们,默认是从左至右排列的,这个方向,代表的就是主轴的方向。

在此,我们引入主轴和侧轴的概念。

如上图所示:

  • 主轴: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这个属性,那么,子元素默认就是从左到右排列的。

代码演示:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
    <section>
        <h3>传统布局</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>伸缩布局 display:flex</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴方向 flex-direction:row</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴方向 flex-direction:row-reverse</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴方向 flex-direction:column</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴方向 flex-direction:column-reverse</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>
</body>
</html>

flex-wrap 属性

flex-wrap:控制子元素溢出时的换行处理。

justify-content 属性

justify-content:控制子元素在主轴上的排列方式。

弹性元素

justify-content 属性

  • justify-content: flex-start; 设置子元素在主轴上的对齐方式。属性值可以是:
- `flex-start` 从主轴的起点对齐(默认值)
- `flex-end` 从主轴的终点对齐
- `center` 居中对齐
- `space-around` 在父盒子里平分
- `space-between` 两端对齐 平分

代码演示:(在浏览器中打开看效果)

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
    <section>
        <h3>主轴的对齐方式:justify-content:flex-start</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴的对齐方式:justify-content:flex-end</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴的对齐方式:justify-content:center</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴的对齐方式:justify-content:space-round</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
    </section>

    <section>
        <h3>主轴的对齐方式:justify-content:space-bettwen</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
    </section>
</body>
</html>

align-items 属性

align-items:设置子元素在侧轴上的对齐方式。属性值可以是:

- `flex-start` 从侧轴开始的方向对齐
- `flex-end` 从侧轴结束的方向对齐
- `baseline` 基线 默认同flex-start
- `center` 中间对齐
- `stretch` 拉伸

代码演示:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<section>
    <h3>侧轴的对齐方式:align-items :flex-start</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>

<section>
    <h3>侧轴的对齐方式:align-items:flex-end</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>

<section>
    <h3>侧轴的对齐方式:align-items:center</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>

<section>
    <h3>侧轴的对齐方式:align-itmes:baseline</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>

<section>
    <h3>侧轴的对齐方式:align-itmes: stretch</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>
</body>
</html>

flex属性:设置子盒子的权重

代码演示:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<section>
    <h3>伸缩比例:flex</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>

<section>
    <h3>伸缩比例:flex</h3>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</section>


</body>
</html>

相关链接

CSS Flexbox 可视化手册

可视化的截图如下:(请点开链接,查看大图)

相关文章:

  • 【英文原版】 CSS Flexbox Fundamentals Visual Guide:
  • 【中文翻译】CSS Flexbox 可视化手册:

flex 相关的推荐文章

  • flex 效果在线演示:
  • A Complete Guide to Flexbox | 英文原版:
  • CSS3 Flexbox 布局完全指南 | 中文翻译:

flex 相关的教程

技巧:使用 margin 自动撑满剩余空间



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://download.csdn.net/download/smyhvae/10253561

解压后如下:

上图中, 我们把箭头处的html文件打开,里面告诉了我们 webfont 的使用步骤:

(1)第一步:使用font-face声明字体

@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的样式

.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)第三步:为文字加上对应的样式

<i class="web-font">这一分钟,你和我在一起,因为你,我会记得那一分钟。从现在开始,我们就是一分钟的朋友。这是事实,你改变不了,因为已经完成了。</i>

举例:

我们按照上图中的步骤来,引入这个字体。完整版代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
    <!-- 第三步:引用 webfont 字体 -->
    <p class="webfont">生命壹号,永不止步</p>
</body>
</html>

代码解释:

(1)my-web-font这个名字是随便起的,只要保证第一步和第二步中的名字一样就行。

(2)因为我把字体文件单独放在了font文件夹中,所以在src中引用字体资源时,写的路径是 font/...

工程文件:2018-02-20-WebFont举例.zip

字体图标(阿里的 iconfont 网站举例)

我们其实可以把图片制作成字体。常见的做法是:把网页中一些小的图标,借助工具生成一个字体包,然后就可以像使用文字一样使用图标了。这样做的优点是:

  • 将所有图标打包成字体库,减少请求;
  • 具有矢量性,可保证清晰度;
  • 使用灵活,便于维护。

也就是说,我们可以把这些图标当作字体来看待,凡是字体拥有的属性(字体大小、颜色等),均适用于图标。

使用步骤如下:(和上一段的使用步骤是一样的)

打开网站,找到想要的图标,加入购物车。然后下载下来:

压缩包下载之后,解压,打开里面的demo.html,里面告诉了我们怎样引用这些图标。

举例1:(图标字体引用)

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
    <!-- 【重要】编码代表图标 -->
    <p><span class="iconfont">&#xe628;</span>扫码付款</p>
</body>
</html>

效果如下:

举例2:(伪元素的方式使用图标字体)

如果想要在文字的前面加图标字体,我们更习惯采用伪元素的方式进行添加。

代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
    <p class="icon">扫码付款</p>
    <span class="icon" >我是span</span>
    <div class="icon">divvvvvvvvvvv</div>
</body>
</html>

效果如下:

~工程文件~:

  • 2018-02-20-图标字体demo.zip
  • 下载链接暂无。

其他相相关网站介绍

  • Font Awesome 使用介绍:

定制自已的字体图标库:

SVG素材:

360浏览器网站案例

暂略。

这里涉及到:jQuery fullPage 全屏滚动插件。

  • 中文网址:http://www.dowebok.com
  • 相关说明:http://www.dowebok.com/77.html

使用 Bootstrap 网站的图标字体

打开如下网站:。

如上图所示,下载字体后,进行解压:

使用步骤如下:

(1)如图只是想要字体的话,可以把css和font这两个文件夹拷贝到项目里。

(2)在html文档中的 标签里,引入 font-awesome.min.css 文件:

    

(3)想在哪个标签里用这个图标,直接在这个标签里加className就行(className都在网站上列出来了)。

完整版代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>Title</title>
    
    
</head>
<body>
    <span class="icon-play">播放</span>
</body>
</html>

我的公众号

想学习更多技能?不妨关注我的微信公众号:千古壹号(id:qianguyihao)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:



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环境)

下载地址:

貌似网络很慢,不一定能下载成功~

安装时,记得勾选“环境变量”:

安装完ruby之后,在命令行中输入ruby -v,查看ruby的的版本:

关于Mac下的Ruby

刚刚说了,Mac下自带Ruby,但是版本肯定很老:

有的时候,我们可能需要使用特定版本的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

(3)安装sass:

紧接着,输入如下命令安装Sass:

	gem install sass        // 如果mac下输入这个命令时没有权限,则需要在前面加上 sudo

系统会自动安装上最新版本的Sass。

查看sass版本的命令为:

	sass -v

升级sass版本的命令为:

	gem update sass

你也可以运行帮助命令行来查看你需要的命令:

	sass -h

参考链接:

Compass 简介和安装

安装完sass之后,我们在main.scss中写一些代码,然后输入如下命令,就可以将scss文件转化为css文件:

	sass main.scss main.css

然而,真正的项目开发中,我们不一定是直接使用 sass 命令,而是使用 Compass。

Compass 简介

官网是:。

Compass 是开源的CSS书写框架。

Compass 安装

输入如下命令安装 Compass:

	gem isntall compass

输入如下命令查看版本:

	compass -v

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:

	sass-convert main.scss main.sass

打开生成的main.sass,内容如下:

*
  margin: 0
  padding: 0

变量语法

Sass 是通过$符号来声明变量。

(1)我们新建一个文件_variables.scss,这个文件专门用来存放变量,然后在其他的文件中引入_variables.scss即可。

因为这个文件只需要存储变量,并不需要它编译出对应的 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

/* 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文件中。



title: 16-浏览器的兼容性问题 publish: false


我们在div里放一个img,发现:

在html和html5中,div的长宽是不同的,后者的高度要超过几个像素。

比如说,下面这个是html的。

<!DOCTYPE html>
<html lang="en">
<head>
	
	<title>Document</title>
	
</head>
<body>
	<div>
	<img src="/Users/smyhvae/Dropbox/img/20170813_1143.jpg" alt="">

	</div>
</body>
</html>


title: 17-CSS3的常见边框汇总 publish: true


CSS3 常见边框汇总

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>CSS3 边框</title>
    
</head>
<body>
<div class="wrapper">
    <header>
        <h3>CSS3 边框圆角</h3>
    </header>
    <div class="main">
        <div class="item" data-brief="整圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="拱形">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="半圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="左上角">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="四分之一圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="横着的椭圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="竖着的椭圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="半个横着的椭圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="半个竖着的椭圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="四分之一竖着的椭圆">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="饼环">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="圆饼">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="左上角圆饼">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="对角圆饼">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="四边不同色">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="右透明色">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="圆右透明色">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="圆右红透明色">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="阴阳图前世">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="阴阳图前世2">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="阴阳图今生">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="阴阳图今生2">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="消息框">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="奇怪的图形">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="奇怪的图形2">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="QQ对话">
            <div class="border-radius"></div>
        </div>
        <div class="item" data-brief="圆角百分比">
            <div class="border-radius"></div>
        </div>
    </div>
</div>
</body>
</html>

效果如下:

爱心

<!DOCTYPE html>
<html>
<head lang="en">
    
    <title></title>
    
</head>
<body>
<div class="heart">

</div>
</body>
</html>

效果如下:

它其实是下面这两个盒子叠起来的:

改变 .heart::after 的 left值,即可叠起来。



publish: false


<!DOCTYPE html>
<html lang="en">

<head>
    
    
    
    <title>Document</title>
</head>


<body>
    <label>
        <input class="mui-switch" type="checkbox"> 默认未选中</label>
    <label>
        <input class="mui-switch" type="checkbox" checked> 默认选中</label>
    <label>
        <input class="mui-switch mui-switch-animbg" type="checkbox"> 默认未选中,简单的背景过渡效果,加mui-switch-animbg类即可</label>
    <label>
        <input class="mui-switch mui-switch-animbg" type="checkbox" checked> 默认选中</label>
    <label>
        <input class="mui-switch mui-switch-anim" type="checkbox"> 默认未选中,过渡效果,加 mui-switch-anim 类即可
    </label>
    <label>
        <input class="mui-switch mui-switch-anim" type="checkbox" checked> 默认选中</label>
</body>

</html>


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 abcd>、div[adcd]{}

其他问题

  • html 元素的嵌套关系是怎么确定的?哪些嵌套不可以发生?
  • 比如说,为什么 div 可以放在 a 标签里面?
  • css 选择器的权重是如何计算的?写代码时要注意什么?
  • 浮动布局是怎么回事?有什么优缺点?国内用的多吗?
  • css 可否做逐帧动画吗?性能如何?
  • Bootstrap 怎么做响应式布局?
  • 如何解决 css 模块化过程中的选择器互相干扰的问题?

总结

单独看 css 属性并不难,难的是需要把这些思路和思想,想到它的应用场景。



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


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 布局:为布局而生,非常灵活,是最为推荐的布局写法。

唯一的缺点是兼容性问题:

上图中可以看到, flex 布局不支持 IE9 及以下的版本。如果你的页面不需要处理 IE浏览器的兼容性问题,则可以放心大胆地使用 flex 布局。

flex 是一种现代的布局方式,是 W3C 第一次提供真正用于布局的 CSS 规范。

5、响应式布局。

float 布局

是 CSS 中一种比较麻烦的属性,涉及到 BFC 和清除浮动(面试的重点)。

float 属性的特点

  • 元素浮动
  • 脱离文档流,但不脱离文本流

代码举例:

下面这两个并列的div1和div2,默认是在标准流中的:

在此基础之上,如果给div1增加float: left属性后,效果如下:

上图中,可以看到,div1设置为浮动后,会脱离文档流,不会对div2的布局造成影响;但是div1不会脱离文本流,它会影响div2中文字的排列。

其实,这正是 float 属性的作用。float 本身是用来做图文混排、文字环绕的效果。

float 所带来的影响

1、对自身的影响:

  • 形成“块”(BFC)
  • 位置尽量靠上
  • 位置尽量靠左/右

下面这两个并列的div1和div2,设置为浮动之后的效果:(都是尽量靠左显示的)

在上方代码的基础之上,增加 div2的宽度之后,会发现,div2掉下来了:

2、对兄弟元素的影响:

  • 不影响其他块级元素的位置
  • 影响其他块级元素的内部文本

3、对父级元素的影响:

  • 从父级的布局中“消失”
  • 造成父级元素的高度塌陷:父级元素撑开 div1 之后(父级元素里没有其他元素的情况下),如果设置 div1 为 float 之后,,会让父级元素的高度变为0。

inline-block 布局

对外的表现是行内元素(不会独占一行),对内的表现是块级元素(可以设置宽高)。

思路:像文本一样去排列 block 元素,没有清除浮动等问题。

存在的问题:需要处理间隙。代码举例如下:

<!DOCTYPE html>
<html lang="en">

<head>
	
	
	<title>Document</title>
</head>


<body>
	<div class="container">
		<div class="div1">div1的inline-block 属性</div>
		<div class="div2">div2的inline-block 属性</div>
		<div class="div3">
			琴棋书画不会,洗衣做饭嫌累。
		</div>
	</div>
</body>

</html>

上面的代码,存在两个问题。

问题一:如果设置div2的宽度为 200px 之后,div2 掉下来。

问题二:div1和div2设置为 inline-block之后,这两个盒子之间存在了间隙。这是因为,此时的 div1和div2 已经被当成文本了。文本和文本之间,本身就会存在间隙。

为了去掉这个间隙,可以有几种解决办法:

办法1:设置父元素container的字体大小为0,即font-size: 0,然后设置子元素 div1、div2的字体font-size: 12px。

办法2:在写法上,去掉div1和div2之间的换行。改为:

<div class="div1">div1的inline-block 属性</div><div class="div2">div2的inline-block 属性</div>

响应式布局

移动端用得较多,本文暂时先不讲。



title: 认识Web和Web标准 publish: false


让flex盒子中的子元素们,居中

flex布局常用的三行代码:

    display: flex;
    justify-content: center; // 子元素在横轴的对齐方式 (左右居中)
    align-items: center;  // 子元素在竖轴的对齐方式(上下居中)

让文字只显示一行,超出显示省略号

	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;

让文字最多只显示两行,超出后显示省略号

	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
	text-overflow: ellipsis;

上面的代码中,我们把 -webkit-line-clamp的值设为1,也能达到“让文字只显示一行,超出显示省略号”的效果。

参考链接:

让文字最多只显示三行,超过后显示省略号

如果当前元素处于flex布局等复杂的场景中,那么,样式可能比较难调,用上面两种写法未必能达到预期效果。此时可以试试下面这种写法(多加了一行 white-space: normal)。
    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的逗号一般写在()里。不同属性值之间,用的是空格,不是逗号。比如:

	transform: translate(-50%, -50%);  /* 这种写逗号 */
	transform: translate(-50%, -50%) scale(0.5);   /* 不同属性值之间,用的是空格 */

	background-size: 100% 100%;  /* 这里是空格,不是逗号 */

2019-11-01

人民币价格中的羊角符号,有半角和全角之分:

  • ¥半角
  • ¥全角

可以看出,半角的宽度更小。考虑到容器的空间一般比较紧张,所以推荐使用半角。

2019-11-19-鼠标悬停时,弹出提示文字

参考链接:css实现鼠标悬浮后的提示效果

2019-11-27-图片的宽度固定,高度自适应

这个场景下,别用background。直接放img元素就好了,将图片的高度设置为auto。

2020-03-26-通过CSS扩大点击热区

.button {
	position: relative;
	/* [其余样式] */
}

.button::before {
	content: '';
	position: absolute;
	top: -10px;
	right: -10px;
	bottom: -10px;
	left: -10px;
}

注意,button 里面不要写 overflow: hidden 属性,否则扩大的热区无效。

参考链接:

2020-10-09-上下滚动,不显示滚动条

.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。

参考链接:

2021-05-06-设置页面的宽高,撑满屏幕

一般来说,我们在开发一个页面的时候,默认是希望这个页面的宽高能够撑满屏幕的。代码可以这样写:

.app {
	width: 100vw;
	min-height: 100vh;
}

2021-09-13-JS中插入CSS

代码举例:

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


title: 认识Web和Web标准 publish: false


2020-03-16

Bootstrap 中文文档:

2020-03-13



title: 认识Web和Web标准 publish: false


常见问题

你是如何理解 HTML 语义化的?

语义化:指对文本内容的结构化(内容语义化),选择合乎语义的标签(代码语义化)。

举例:段落用 p,边栏用 aside,主要内容用 main 标签。

好处:

  • 便于开发者阅读和维护
  • 有利于SEO:让浏览器的爬虫和辅助技术更好的解析,

语义化标签介绍:

在HTML5出来之前,我们习惯于用div来表示页面的章节或者不同模块,但是div本身是没有语义的。但是现在,HTML5中加入了一些语义化标签,来更清晰的表达文档结构。

20180322_1120.jpg

参考链接:

meta viewport 是做什么用的,怎么写?

 	

控制页面在移动端不要缩小显示。

canvas 元素是干什么的?

看 MDN 的 canvas 入门手册。

说一下CSS盒模型

可以参考本人的另外一篇文章:《02-CSS基础/06-CSS盒模型详解》。

css reset 和 Normalize.css 有什么区别

此题考英文。

二者都是用来统一浏览器的默认样式:

  • reset:重置。相对「暴力」,不管你有没有用,统统重置成一样的效果,且影响的范围很大,讲求跨浏览器的一致性。(一刀切)
  • Normalize.css :标准化。相对「平和」,注重通用的方案,重置掉该重置的样式,保留有用的 user agent 样式,同时进行一些 bug 的修复,这点是 reset 所缺乏的。(去伪存真)

参考链接:

选择器的优先级如何确定

  • 选择器越具体,优先级越高。 #xxx 大于 .yyy
  • 同样优先级,写在后面的覆盖前面的。
  • color: red !important; 优先级最高。

BFC 是什么

overflow:hidden :取消父子 margin 合并。 (另一种推荐做法:padding-top: 0.1px;)

如何清除浮动

(1)overflow: hidden

(2).clearfix 清除浮动写在爸爸身上

    .clearfix::after {
        content: '';
        display: block;
        clear: both;
    }

    /* 兼容 IE */
    .clearfix {
        zoom: 1;
    }

伪类和伪元素的区别是什么?

概念上的区别:

  • 伪类表示一种状态
  • 伪元素是真的有元素。比如 ::after 是真的有元素,可以在页面上显示内容。

使用上的区别:

  • 伪类:使用单冒号
  • 伪元素:使用双冒号

参考链接



title: 01-编程语言和JavaScript简介


计算机语言

概念

计算机语言:人与计算机之间通信的语言。它是人与计算机之间传递信息的媒介,它通过特定的语法规则和语义约定,将人类可理解的指令转化为计算机可以执行的机器指令。

计算机程序:就是计算机所执行的一系列的指令集合,程序全部都是用我们所掌握的语言来编写的,如果我们要控制计算机,就需要通过计算机语言向计算机发出指令。

计算机语言的分类

计算机语言的种类非常多,总的来说可以分成三大类:机器语言、汇编语言和高级语言。他们之间的转换过程如下:

计算机最终所执行的都是机器语言,它是由“0”和“1”组成的二进制数,二进制是计算机语言的基础。

计算机语言的范围比编程语言的范围更广,后者是前者的子集。比如:

  • HTML 是标记语言,CSS 是样式语言,这两个是属于计算机语言;但不属于编程语言,因为它们编写出来的不是程序,只是简单的标记和样式。
  • JavaScript 是属于编程语言,当然也属于计算机语言。

计算机语言的分类如下:

image-20231001002948987

从机器语言到汇编语言,再到高级语言,这些语言的发展越来越高级,编写方式越来越接近人的思维。我们来具体看看这三种计算机语言的发展历史。

机器语言

计算机的存储单元只有0和1两种状态。

将数字0和1按照一定的规律组成的编码称为机器码,也称为二进制编码或者机器指令或者计算机指令。用这些机器指令所编写的程序称为机器语言。

机器语言的优点是可以直接被计算机识别、直接操作硬件、不需要经过编译解析、程序的执行效率很高;缺点是可读性差、可维护性差、出错概率高。

现如今,几乎没有人用这种方式编程。

汇编语言

对于人类来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性和维护性的问题,就诞生了汇编语言。

汇编语言:是二进制指令的文本形式,与指令是一一对应的关系,比如,加法指令00000011写成汇编语言就是 ADD;汇编语言使用助记符(Mnemonics)来代替和表示特定低级机器语言的操作。

汇编语言经过汇编器,可翻译成机器语言,进而被 CPU 直接执行,所以汇编语言是最底层的低级语言。

不同的硬件系统有不同的汇编语言语法。换而言之,每一种特定的汇编语言和其特定的机器语言指令集是一一对应的。汇编语言的常见的应用场景有:操作系统内核、驱动程序、单片机程序等。

举例:用汇编语言写一个简单的加法程序“3+5=8”,加数和结果分别存在内存单元NUM1、NUM2、NUM3中。

代码实现:

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来举例:

#include <stdio.h>
int main(int argc, char const *argv[])
{
  printf("Hello world!");
  return 0;
}

对于以上 C 语言代码,main 是程序入口,实现的功能是打印字符串hello world到屏幕上,编译和执行过程如下:

  1. C 语言代码经过预处理(比如依赖处理、宏替换)。以上方代码示例,#include<stdio.h>会在预处理阶段被替换。
  2. 编译:编译器会把 C 语言翻译成汇编程序。一条 C 语言通常被编译为多条汇编代码,同时编译器会对程序进行优化,生成目标汇编程序。旁征博
  3. 汇编语言通过汇编器再汇编成目标程序hello.o。
  4. 链接:程序中往往包含一些共享目标文件,如示例代码中的printf()函数位于静态库,需要经过链接器进行链接。

20211030-0031-2

(上方图片来源:JavaScript 基础-基本概念 )

20211030-0026-2

(上方图片来源:编译型语言 )

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。

发明这个语言的背景是这样的: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,但这门语言当时更像是一个大杂烩:

  • 基本语法:借鉴 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 中需要这样定义变量:

int a;
float a;
double a;
String a;
boolean a;

而 JS 中,只需要用一种方式来定义:

// 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、单线程。

参考链接

pl_history

img

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 02-开始写JavaScript:hello world


开始写第一行 JavaScript:hello world

JS 代码的书写位置在哪里呢?这个问题,也可以理解成:引入 JS 代码,有哪几种方式?有三种方式:(和 CSS 的引入方式类似)

  1. 行内式:写在HTML标签内部。
  1. 内嵌式(内联式):写在 script 标签中。
  1. 外链式:引入外部 JS 文件。

方式 1:行内式

代码举例:

<input type="button" value="点我点我" />

完整的可执行代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    
    
    <title>Document</title>
  </head>
  <body>
    <input type="button" value="点我点我" />
  </body>
</html>

解释:

  • 可以将单行或少量 JS 代码写在 HTML 标签的事件属性中(以 on 开头的属性),比如放在上面的 onclick点击事件中。
  • 这种书写方式,不推荐使用,原因是:可读性差,尤其是需要编写大量 JS 代码时,很难维护;引号多层嵌套时,也容易出错。
  • 关于代码中的「引号」,在 HTML 标签中,我们推荐使用双引号,JS 中推荐使用单引号。

方式 2、内嵌式(内联式)

我们可以在 HTML 页面的 <body> 标签里放入`标签对,并在


解释:

- tyue属性中的text 表示纯文本,因为 JavaScript 代码本身就是纯文本。当然,type属性可以省略,因为 JavaScript 是所有现代浏览器以及 HTML5 中的默认脚本语言。

- 可以将多行 JS 代码写到 `
  </body>
</html>

解释:

  • 上面这段代码,依然是放到 body 标签里,可以和内嵌的 JS 代码并列。
  • 上方代码的 script 标签已经引入了外部 JS 文件,所以这个标签里面,不可以再写 JS 代码。
  • 方式2和方式3不能混用。

总结:

我们在实战开发中,基本都是采用方式 3,因为将 html 文件和 js 文件分开的方式,有利于代码的结构化和复用,符合高内聚、低耦合的思想。很少会有人把一大堆 JS 代码塞到 单个 html 文件里。

拓展知识

window.onload:先加载,最后执行

上面的三种方式,有个共同的地方是:JS 代码都是写在 body 标签中的,准确来说,是在页面标签元素(比如 title 标签)的后面,在 </body>结束标签的前面。

为什么一般是按这样的顺序来写呢?这是因为:浏览器默认遵循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 只有`/* */`这种注释,没有`//`这种注释。而且注释要写在`
        
    </head>

    <body></body>
</html>

页面效果:

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



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 关键字创建出来的对象实例,都是属于对象类型。

自定义对象

为什么需要自定义对象

保存一个值时,可以使用变量,保存多个值(一组值)时,可以使用数组。

比如,如果要保存一个人的信息,通过数组的方式可以这样保存:

const arr = ['王二', 35, '男', '180'];

上面这种表达方式比较乱。而如果用 JS 中的自定义对象来表达,结构会更清晰。如下:

const person = {
    name: 'qianguyihao',
    age: 30,
    sex: '男',
    favor: ['阅读', '羽毛球'],
    sayHi: function () {
        console.log('qianguyihao');
    },
};

由此可见,自定义对象里面的属性均是键值对(key: value),表示属性和值的映射关系:

  • 键/属性:属性名。
  • 值:属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。

自定义对象的语法

语法如下:

const obj = {
    key: value,
    key: value,
    key: value,
};

key 和 value 之间用冒号分隔,每组 key:vaue 之间用逗号分隔,最后一对 key:value 的末尾可以写逗号,也可以不写逗号。

问:对象的属性名是否需要加引号?

答:如果属性名不符合 JS 标识符的命名规范,则需要用引号包裹。比如:

const person = {
    'my-name': 'qianguyihao',
};

补充:其实,JS 的内置对象、宿主对象,底层也是通过自定义对象的形式(也就是键值对的形式)进行封装的。

对象的属性值补充

什么叫对象的方法【重要】

对象的属性值可以是任意的数据类型,也可以是个函数(也称之为方法)。换而言之,如果对象的属性值是函数,则这个函数可被称之为对象的“方法”。

const obj = new Object();
obj.sayName = function () {
    console.log('qianguyihao');
};

// 没加括号,就是获取方法
console.log(obj.sayName);
console.log('-----------');
// 加了括号,就是调用方法。即:执行函数内容,并执行函数体的内容
console.log(obj.sayName());

打印结果:

对象中的属性值,也可以是一个对象

举例:

//创建对象 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、对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。变量保存的是对象的内存地址(对象的引用)。换而言之,对象的值是保存在堆内存中的,而对象的引用(即变量)是保存在栈内存中的。

如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。这句话很重要,我们来看看下面的例子。

传值

代码举例:

let a = 1;

let b = a; // 将 a 赋值给 b

b = 2; // 修改 b 的值

上方代码中,当我修改 b 的值之后,a 的值并不会发生改变。这个大家都知道。我们继续往下看。

传址(一个经典的例子)

代码举例:

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() 来复制对象。效果如下:

var obj1 = { name: '孙悟空' };

// 复制对象:把 obj1 赋值给 obj3。两者之间互不影响
var obj3 = Object.assign({}, obj1);

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 14-基本包装类型


基本数据类型不能绑定属性和方法

属性和方法只能添加给对象,不能添加给基本数据类型。我们拿字符串来举例。

1、基本数据类型:

基本数据类型string是无法绑定属性和方法的。

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是可以绑定属性和方法的。如下:

var strObj = new String('smyhvae');
strObj.aaa = 123;
console.log(strObj);
console.log(typeof strObj); //打印结果:Object
console.log(strObj.aaa);

打印结果:

内置对象 Number 也有一些自带的方法,比如:

  • Number.MAX_VALUE;
  • Number.MIN_VALUE;

内置对象 Boolean 也有一些自带的方法,但是用的不多。

基本包装类型

介绍

我们都知道,JS 中的数据类型包括以下几种。

  • 基本数据类型:String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。
  • 引用数据类型:Object 对象。

JS 为我们提供了三个基本包装类:

  • String():将基本数据类型字符串,转换为 String 对象。
  • Number():将基本数据类型的数字,转换为 Number 对象。
  • Boolean():将基本数据类型的布尔值,转换为 Boolean 对象。

通过上面这这三个包装类,我们可以将基本数据类型的数据转换为对象。

代码举例:

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

需要注意的是:我们在实际应用中一般不会使用基本数据类型的对象。如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果。

比如说:

var boo1 = new Boolean(true);
var boo2 = new Boolean(true);

console.log(boo1 === boo2); // 打印结果竟然是:false

再比如说:

var boo3 = new Boolean(false);

if (boo3) {
    console.log('qianguyihao'); // 这行代码竟然执行了
}

基本包装类型的作用

当我们对一些基本数据类型的值去调用属性和方法时,JS引擎会临时使用包装类将基本数据类型转换为引用数据类型(即“隐式类型转换”),这样的话,基本数据类型就有了属性和方法,然后再调用对象的属性和方法;调用完以后,再将其转换为基本数据类型。

举例:

var str = 'qianguyihao';
console.log(str.length); // 打印结果:11

比如,上面的代码,执行顺序是这样的:

// 步骤(1):把简单数据类型 string 转换为 引用数据类型  String,保存到临时变量中
var temp = new String('qianguyihao');

// 步骤(2):把临时变量的值 赋值给 str
str = temp;

//  步骤(3):销毁临时变量
temp = null;

在底层,字符串以字符数组的形式保存

在底层,字符串是以字符数组的形式保存的。代码举例:

var str = 'smyhvae';
console.log(str.length); // 获取字符串的长度
console.log(str[2]); // 获取字符串中的第3个字符(下标为2的字符)

上方代码中,smyhvae这个字符串在底层是以["s", "m", "y", "h", "v", "a", "e"]的形式保存的。因此,我们既可以获取字符串的长度,也可以获取指定索引 index 位置的单个字符。这很像数组中的操作。

再比如,String 对象的很多内置方法,也可以直接给字符串用。此时,也是临时将字符串转换为 String 对象,然后再调用内置方法。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



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:

索引值 = str.indexOf(想要查询的字符串);

备注:indexOf() 是从前向后查找字符串的位置。同理,lastIndexOf()是从后向前寻找。

解释:可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回其第一次出现的索引;如果没有找到指定的内容,则返回 -1。

因此可以得出一个重要技巧:

  • 如果获取的索引值为 0,说明字符串是以查询的参数为开头的。
  • 如果获取的索引值为-1,说明这个字符串中没有指定的内容。

举例 1:(查找单个字符)

const str = 'abcdea';

//给字符查索引(索引值为0,说明字符串以查询的参数为开头)
console.log(str.indexOf('c'));
console.log(str.lastIndexOf('c'));

console.log(str.indexOf('a'));
console.log(str.lastIndexOf('a'));

打印结果:

举例 2:(查找字符串)

const name = 'qianguyihao';

console.log(name.indexOf('yi')); // 打印结果:6

语法 2:

这个方法还可以指定第二个参数,用来指定查找的起始位置。语法如下:

索引值 = str.indexOf(想要查询的字符串, [起始位置]);

举例 3:(两个参数时,需要特别注意)

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,从而继续查找。

代码实现:

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():获取字符串中指定内容的索引(参数里一般是正则)

语法:

索引值 = str.search(想要查找的字符串);
索引值 = str.search(正则表达式);

备注:search() 方法里的参数,既可以传字符串,也可以传正则表达式。

解释:可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回其第一次出现的索引;如果没有找到指定的内容,则返回 -1。

举例:

const name = 'qianguyihao';

console.log(name.search('yi')); // 打印结果:6
console.log(name.search(/yi/i)); // 打印结果:6

备注:上方的/yi/i采用的是正则表达式的写法,意思是,让 name去匹配字符yi,忽略大小写。我们在后面会专门介绍正则表达式。

3、includes():字符串中是否包含指定的内容

语法:

布尔值 = str.includes(想要查找的字符串, [position]);

解释:判断一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回 true;否则返回 false。

参数中的 position:如果不指定,则默认为0;如果指定,则规定了检索的起始位置。

const name = 'qianguyihao';

console.log(name.includes('yi')); // 打印结果:true
console.log(name.includes('haha')); // 打印结果:false

console.log(name.includes('yi',7)); // 打印结果:false

4、startsWith():字符串是否以指定的内容开头

语法:

布尔值 = str.startsWith(想要查找的内容, [position]);

解释:判断一个字符串是否以指定的子字符串开头。如果是,则返回 true;否则返回 false。

参数中的position:

  • 如果不指定,则默认为0。
  • 如果指定,则规定了检索的起始位置。检索的范围包括:这个指定位置开始,直到字符串的末尾。即:[position, str.length)

举例:

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():字符串是否以指定的内容结尾

语法:

布尔值 = str.endsWith(想要查找的内容, [position]);

解释:判断一个字符串是否以指定的子字符串结尾。如果是,则返回 true;否则返回 false。

参数中的position:

  • 如果不指定,则默认为 str.length。
  • 如果指定,则规定了检索的结束位置。检索的范围包括:从第一个字符串开始,直到这个指定的位置。即:[0, position)
  • 或者你可以这样简单理解:endsWith() 方法里的position,表示检索的长度。

注意:startsWith() 和 endsWith()这两个方法,他们的 position 的含义是不同的,请仔细区分。

举例:

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()介绍

获取指定位置的字符

1、charAt(index)

语法:

字符 = str.charAt(index);

解释:返回字符串指定位置的字符。这里的 str.charAt(index)和str[index]的效果是一样的。

注意:字符串中第一个字符的下标是 0。如果参数 index 不在 [0, string.length) 之间,该方法将返回一个空字符串。

代码举例:

var str = new String('smyhvae');

for (var i = 0; i < str.length; i++) {
    console.log(str.charAt(i));
}

打印结果:

上面这个例子一般不用。一般打印数组和 json 的时候用索引,打印 String 不建议用索引。

2、str[index]

str.charAt(index)和str[index]的效果是一样的,不再赘述。区别在于:str[index]是 H5 标准里新增的特性。

3、charCodeAt(index)

语法:

字符 = str.charCodeAt(index);

解释:返回字符串指定位置的字符的 Unicode 编码。不会修改原字符串。

在实际应用中,通过这个方法,我们可以判断用户按下了哪个按键。

代码举例:打印字符串的占位长度。

提示:一个英文占一个位置,一个中文占两个位置。

思路:判断该字符是否在 0-127 之间(在的话是英文,不在是非英文)。

代码实现:

打印结果:

    30
    24

从打印结果可以看出:字符串的长度是 24,但是却占了 30 个字符位(一个中文占两个字符位)。

另外,sort()方法其实底层也是用到了 charCodeAt(),因为用到了 Unicode 编码。

字符串截取

1、slice()

slice() 方法用的最多。

语法:

新字符串 = str.slice(开始索引, 结束索引); //两个参数都是索引值。包左不包右。

解释:从字符串中截取指定的内容。不会修改原字符串,而是将截取到的内容返回。

注意:上面的参数,包左不包右。参数举例如下:

  • (2, 5) 截取时,包左不包右。
  • (2) 表示从指定的索引位置开始,截取到最后。
  • (-3) 表示从倒数第三个开始,截取到最后。
  • (1, -1) 表示从第一个截取到倒数第一个。
  • (5, 2) 表示前面的大,后面的小,返回值为空。

2、substring()

语法:

新字符串 = str.substring(开始索引, 结束索引); //两个参数都是索引值。包左不包右。

解释:从字符串中截取指定的内容。和slice()类似。

substring()和slice()是类似的。但不同之处在于:

  • substring()不能接受负值作为参数。如果传递了一个负值,则默认使用 0。
  • substring()还会自动调整参数的位置,如果第二个参数小于第一个,则自动交换。比如说, substring(1, 0)相当于截取的是第一个字符。

3、substr()

语法:

字符串 = str.substr(开始索引, 截取的长度);

解释:从字符串中截取指定的内容。不会修改原字符串,而是将截取到的内容返回。

注意,这个方法的第二个参数截取的长度,不是结束索引。

参数举例:

  • (2,4) 从索引值为 2 的字符开始,截取 4 个字符。
  • (1) 从指定位置开始,截取到最后。
  • (-3) 从倒数第几个开始,截取到最后。

备注:ECMAscript 没有对 substr() 方法进行标准化,因此不建议使用它。

String.fromCharCode()

String.fromCharCode():根据字符的 Unicode 编码获取字符。

代码举例:

var result1 = String.fromCharCode(72);
var result2 = String.fromCharCode(20013);

console.log(result1); // 打印结果:H
console.log(result2); // 打印结果:中

concat()

语法:

    新字符串 = str1.concat(str2); //连接两个字符串

解释:字符串的连接。

这种方法基本不用,直接把两个字符串相加就好。

是的,你会发现,数组中也有concat()方法,用于数组的连接。这个方法在数组中用得挺多的。

代码举例:

var str1 = 'qiangu';
var str2 = 'yihao';

var result = str1.concat(str2);
console.log(result); // 打印结果:qianguyihao

split():字符串转换为数组 【重要】

语法:

新的数组 = str.split(分隔符);

解释:通过指定的分隔符,将一个字符串拆分成一个数组。不会改变原字符串。

备注:split()这个方法在实际开发中用得非常多。一般来说,从接口拿到的 json 数据中,经常会收到类似于"q, i, a, n"这样的字符串,前端需要将这个字符串拆分成['q', 'i', 'a', 'n']数组,这个时候split()方法就派上用场了。

代码举例 1:

var str = 'qian, gu, yi, hao'; // 用逗号隔开的字符串
var array = str.split(','); // 将字符串 str 拆分成数组,通过逗号来拆分

console.log(array); // 打印结果是数组:["qian", " gu", " yi", " hao"]

代码举例 2:

//split()方法:字符串变数组
var str3 = '千古壹号|qianguyihao|许嵩';

console.log('结果1:' +str3.split()); // 无参数,表示:把整个字符串作为一个元素添加到数组中。

console.log(str3.split('')); // 参数为空字符串,则表示:分隔字符串中每一个字符,分别添加到数组中

console.log(str3.split('|')); // 参数为指定字符,表示:用 '|' 分隔字符串。此分隔符将不会出现在数组的任意一个元素中

console.log(str3.split('许')); // 同上

打印结果:(都是数组)

replace()

语法:

新的字符串 = str.replace(被替换的子串,新的子串);

解释:将字符串中的指定内容,替换为新的内容并返回。不会修改原字符串。

注意:这个方法,默认只会替换第一个被匹配到的字符。如果要全局替换,需要使用正则。

代码举例:

//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():重复字符串

语法:

newStr = str.repeat(重复的次数);

解释:将字符串重复指定的次数。会返回新的值,不会修改原字符串。

举例1:

const name = 'qianguyihao';

console.log(name.repeat(2)); // 打印内容:qianguyihaoqianguyihao

举例2:(模糊字符串的后四位)

const telephone = '13088889999';
const mix_telephone = telephone.slice(0, -4) + '*'.repeat(4); // 模糊电话号码的后四位

console.log(telephone); // 打印结果:13088889999
console.log(mix_telephone); // 打印结果:1308888****

trim()

trim():去除字符串前后的空白。

代码举例:

//去除字符串前后的空格,trim();
let str = '   a   b   c   ';
console.log(str);
console.log(str.length);

console.log(str.trim());
console.log(str.trim().length);

打印结果:

大小写转换

举例:

var str = 'abcdEFG';

//转换成小写
console.log(str.toLowerCase());

//转换成大写
console.log(str.toUpperCase());

html 方法

  • anchor() 创建 a 链接
  • big()
  • sub()
  • sup()
  • link()
  • bold()

注意,str.link() 返回值是字符串。

举例:

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());

字符串练习

练习 1:"smyhvaevaesmyh"查找字符串中所有 m 出现的位置。

代码实现:

var str2 = 'smyhvaevaesmyh';
for (var i = 0; i < str2.length; i++) {
    //如果指定位置的符号=== "o"
    //str2[i]
    if (str2.charAt(i) === 'm') {
        console.log(i);
    }
}

练习 2:判断一个字符串中出现次数最多的字符,统计这个次数

打印结果:

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 16-内置对象:Number和Math


内置对象 Number 的常见方法

Number.isInteger() 判断是否为整数

语法:

布尔值 = Number.isInteger(数字);

toFixed() 小数点后面保留多少位

语法:

字符串 = myNum.toFixed(num);

解释:将数字 myNum 的小数点后面保留 num 位小数(四舍五入),并返回。不会改变原数字。注意,返回结果是字符串。

参数 num:指定了小数点后面的位数。

举例:

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()开方:对一个数进行开方运算

举例:

    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() 方法。

代码举例:

    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) 之间的随机数

    Math.round(Math.random()*x)

生成 [x, y) 之间的随机数

    Math.round(Math.random()*(y-x)+x)

【重要】生成 [x, y]之间的随机整数

也就是说:生成两个整数之间的随机整数,并且要包含这两个整数。

这个功能很常用,我们可以将其封装成一个方法,代码实现如下:

    /*
    * 生成两个整数之间的随机整数,并且要包含这两个整数
    */
    function getRandom(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    console.log(getRandom(1, 10));

举例:随机点名

根据上面的例子,我们还可以再延伸一下,来看看随机点名的例子。

    /*
    * 生成两个整数之间的随机整数,并且要包含这两个整数
    */
    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:

代码实现:

	var a = Math.pow(3, Math.pow(2, 2));
	console.log(a);

举例2:

代码实现:

	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编码替换所有无效的字符,从而让浏览器能够接受和理解。

    encodeURIComponent();   //把字符串作为 URI 组件进行编码
    decodeURIComponent();   //把字符串作为 URI 组件进行解码

举例:

    var url = "http://www.cnblogs.com/smyhvae/";

    var str = encodeURIComponent(url);
    console.log(str);                           //打印url的编码
    console.log(decodeURIComponent(str));       //对url进行编码后,再解码,还原为url

打印结果:

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 17-内置对象:Date


内置对象:Date

Date 对象在实际开发中,使用得很频繁,且容易在细节地方出错,需要引起重视。

内置对象 Date 用来处理日期和时间。

需要注意的是:与 Math 对象不同,Date 对象是一个构造函数 ,需要先实例化后才能使用。

创建Date对象

创建Date对象有两种写法:

  • 写法一:如果Date()不写参数,就返回当前时间对象
  • 写法二:如果Date()里面写参数,就返回括号里输入的时间对象

针对这两种写法,我们来具体讲一讲。

写法一:不传递参数时,则获取系统的当前时间对象

代码举例:

var date1 = new Date();
console.log(date1);
console.log(typeof date1);

代码解释:不传递参数时,表示的是获取系统的当前时间对象。也可以理解成是:获取当前代码执行的时间。

打印结果:

Mon Feb 17 2020 21:57:22 GMT+0800 (中国标准时间)
object

写法二:传递参数

传递参数时,表示获取指定时间的时间对象。参数中既可以传递字符串,也可以传递数字,也可以传递时间戳。

通过传参的这种写法,我们可以把时间字符串/时间数字/时间戳,按照指定的格式,转换为时间对象。

举例1:(参数是字符串)

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:(参数是多个数字)

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:(参数是时间戳)

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-110代表一月
getDate()获取日:1-31获取的是几号
getDay()获取星期:0-60代表周日,1代表周一
getHours()获取小时:0-23
getMinutes()获取分钟:0-59
getSeconds()获取秒:0-59
getMilliseconds()获取毫秒1s = 1000ms

代码举例:

	// 我在执行这行代码时,当前时间为 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

获取了日期和时间的指定部分之后,我们把它们用字符串拼接起来,就可以按照自己想要的格式,来展示日期。

举例:年月日的格式化

代码举例:

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毫秒)。

计算机底层在保存时间时,使用的都是时间戳。时间戳的存在,就是为了统一时间的单位。

我们经常会利用时间戳来计算时间,因为它更精确。而且,在实战开发中,接口返回给前端的日期数据,都是以时间戳的形式。

我们再来看下面这样的代码:

	var myDate = new Date("1970/01/01 0:0:0");

	console.log(myDate.getTime()); // 获取时间戳

打印结果(可能会让你感到惊讶)

	-28800000

为啥打印结果是-28800000,而不是0呢?这是因为,我们的当前代码,是在中文环境下运行的,与英文时间会存在8个小时的时差(中文时间比英文时间早了八个小时)。如果代码是在英文环境下运行,打印结果就是0。

getTime():获取时间戳

getTime() 获取日期对象的时间戳(单位:毫秒)。这个方法在实战开发中,用得比较多。但还有比它更常用的写法,我们往下看。

获取 Date 对象的时间戳

代码演示:

// 方式一:获取 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秒 到现在所花费的总毫秒数。

获取当前时间的时间戳

如果我们要获取当前时间的时间戳,除了上面的几种方式之外,还有另一种方式。代码如下:

// 方式六:获取当前时间的时间戳(很常用的写法)
console.log(Date.now()); // 打印结果举例:1589448165370

上面这种方式六,用得也很多。只不过,Date.now()是H5标准中新增的特性,如果你的项目需要兼容低版本的IE浏览器,就不要用了。这年头,谁还用IE呢?

利用时间戳检测代码的执行时间

我们可以在业务代码的前面定义 时间戳1,在业务代码的后面定义 时间戳2。把这两个时间戳相减,就能得出业务代码的执行时间。

format()

将时间对象转换为指定格式。

参考链接:

练习

举例1:模拟日历

要求每天打开这个页面,都能定时显示当前的日期。

代码实现:

<!DOCTYPE html>
<html>
    <head lang="en">
        
        <title></title>
        
    </head>
    <body>
        <div></div>

        
    </body>
</html>

实现效果:

举例2:发布会倒计时

实现思路:

  • 设置一个定时器,每间隔1毫秒就自动刷新一次div的内容。
  • 核心算法:输入的时间戳减去当前的时间戳,就是剩余时间(即倒计时),然后转换成时分秒。

代码实现:

<!DOCTYPE html>
<html>
    <head lang="en">
        
        <title></title>
        
    </head>
    <body>
        <div></div>

        
    </body>
</html>

实现效果:

Moment.js

Moment.js 是一个轻量级的JavaScript时间库,我们可以利用它很方便地进行时间操作,提升开发效率。

  • 中文官网:

使用举例:

<!DOCTYPE html>
<html lang="en">
    <head>
        
        
        <title>Document</title>
    </head>
    <body>
        
        
    </body>
</html>

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 18-数组简介


之前学习的数据类型,只能存储一个值(字符串也为一个值)。如果我们想存储多个值,就可以使用数组。

数组简介

数组(Array)是属于内置对象,数组和普通对象的功能类似,都可以用来存储一些值。不同的是:

  • 普通对象是使用字符串作为属性名,而数组是使用数字作为索引来操作元素。索引:从 0 开始的整数就是索引。

数组的存储性能比普通对象要好。在实际开发中我们经常使用数组存储一些数据(尤其是列表数据),使用频率非常高。

比如说,上面这个页面的列表数据,它的数据结构就是一个数组。

数组中的元素可以是任意的数据类型,可以是对象,可以是函数,也可以是数组。数组的元素中,如果存放的是数组,我们就称这种数组为二维数组。

接下来,我们讲一讲数组的基本操作。

创建数组对象

方式一:使用字面量创建数组

举例:

let arr1 = []; // 创建一个空的数组

let arr2 = [1, 2, 3]; // 创建带初始值的数组

方式一最简单,也用得最多。

方式二:使用构造函数创建数组

语法:

let arr = new Array(参数);

let arr = Array(参数);

如果参数为空,表示创建一个空数组;如果参数是一个数值,表示数组的长度;如果有多个参数,表示数组中的元素内容。

举个例子:

// 方式一
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));

打印结果:

object;

arr1 = [11, 12, 13];
arr2 = [];
arr3 = [null, null, null, null];
arr4 = [15, 16, 17];

从上方打印结果的第一行可以看出,数组的类型是属于对象。

数组中的元素的类型

数组中可以存放任意类型的数据,例如字符串、数字、布尔值、对象等。

比如:

const arr = ['qianguyihao', 28, true, { name: 'qianguyihao' }];

我们甚至可以在数组里存放数组。比如:

const arr2 = [
    [11, 12, 13],
    [21, 22, 23],
];

数组的基本操作

数组的索引

索引 (下标) :用来访问数组元素的序号,代表的是数组中的元素在数组中的位置(下标从 0 开始算起)。

数组可以通过索引来访问、修改对应的数组元素。我们继续看看。

向数组中添加元素

语法:

数组[索引] = 值;

代码举例:

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]

获取数组中的元素

语法:

数组[索引];

如果读取不存在的索引(比如元素没那么多),系统不会报错,而是返回 undefined。

代码举例:

const arr = [21, 22, 23];

console.log(arr[0]); // 打印结果:21
console.log(arr[5]); // 打印结果:undefined

获取数组的长度

可以使用length属性来获取数组的长度(即“元素的个数”)。

数组的长度是元素个数,不要跟索引号混淆。

语法:

数组的长度 = 数组名.length;

代码举例:

const arr = [21, 22, 23];

console.log(arr.length); // 打印结果:3

补充:

对于连续的数组,使用 length 可以获取到数组的长度(元素的个数);对于非连续的数组(即“稀疏数组”,本文稍后会讲),length 的值会大于元素的个数。因此,尽量不要创建非连续的数组。

修改数组的长度

可以通过修改length属性修改数组的长度。

  • 如果修改的 length 大于原长度,则多出部分会空出来,置为 null。
  • 如果修改的 length 小于原长度,则多出的元素会被删除,数组将从后面删除元素。
  • (特例:伪数组 arguments 的长度可以修改,但是不能修改里面的元素,以后单独讲。)

代码举例:

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));

打印结果:

[11]
[21, 22, 23, null, null]

遍历数组

遍历: 就是把数组中的每个元素从头到尾都访问一次。

最简单的做法是通过 for 循环,遍历数组中的每一项。举例:

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中,如果想把数组中的元素赋值给其他变量,是这样做的:

const arr = [1, 2, [3,4]];
let a = arr[0]; // 1
let b = arr[1]; // 2
let c = arr[2]; // [3, 4]

上面这种写法比较啰嗦。通过ES6中的结构复制,我们可以像下面这样做。

1、数组解构赋值,代码举例:

let [a, b, c] = [1, 2, [3, 4]];
console.log(a); // 1
console.log(b); // 2
console.log(c); // [3, 4]

注意点:

(1)等号左边的个数和格式,必须和右边的一模一样,才能完全解构。

(2)当然,左边的个数和右边的个数,可以不一样。

2、默认值。在赋值之前,我们可以给左边的变量指定默认值:

let [a, b = 3, c = 4] = [1, 2];
console.log(a); // 1
console.log(b); // 2。默认值被覆盖。
console.log(c); // 4。继续保持默认值。

3、我们可以使用ES6中新增的扩展运算符打包剩余的数据。如果使用了扩展运算符, 那么扩展运算符只能写在最后。代码举例:

let [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

稀疏数组与密集数组

这个知识点,简单了解即可。
  • 稀疏数组:索引不连续、数组长度大于元素个数的数组,可以简单理解为有 empty(有空隙)的数组。
  • 密集数组:索引连续、数组长度等于元素个数的数组。

参考链接:

  • [译]JavaScript中的稀疏数组与密集数组:https://www.cnblogs.com/ziyunfei/archive/2012/09/16/2687165.html

案例

例 1:翻转数组

代码实现:

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:冒泡排序

代码实现:

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]

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



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():判断是否为数组

语法:

布尔值 = Array.isArray(被检测的数组);

以前,我们会通过 A instanceof B来判断 A 是否属于 B 类型。但是在数组里,这种 instanceof 方法已经用的不多了,因为有 isArray()方法。

数组转换为字符串

数组转为字符串,有三种方式。

方式1、toString()

// 语法
字符串 = 数组.toString();

// 举例
const result = [1, 3, 5].toString(); // 转换结果 result 为字符串 '1, 3, 5'

解释:把数组转换成字符串,每一项用英文逗号,分割。

备注:大多数的数据类型都可以使用.toString()方法,将其转换为字符串。

方式 2

// 语法
字符串 = String(数组);

// 举例
const result = String([1, 3, 5]); // 转换结果 result 为字符串 '1, 3, 5'

方式 3:join()方法

字符串 = 数组.join(','); // 将数组转为字符串,每一项用 英文逗号 分隔

关于 join()方法的详细介绍,详见下一段。

join()

join():将数组转换为字符串,返回结果为转换后的字符串(不会改变原来的数组)。

补充:join()方法可以指定一个字符串作为参数,这个参数是元素之间的连接符;如果不指定连接符,则默认使用英文逗号, 作为连接符,此时和 toString()的效果是一致的。

语法:

新的字符串 = 原数组.join(参数); // 参数选填

代码举例:

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);

上方代码中,最后三行的打印结果是:

arr =["a","b","c"]
result1:a,b,c
result2:a-b-c

split()

注意,split()是字符串的方法,不是数组的方法。

语法:

新的数组 = str.split(分隔符);

解释:通过指定的分隔符,将一个字符串拆分成一个数组。不会改变原字符串。

备注:split()这个方法在实际开发中用得非常多。一般来说,从接口拿到的 json 数据中,经常会收到类似于"q, i, a, n"这样的字符串,前端需要将这个字符串拆分成['q', 'i', 'a', 'n']数组,这个时候split()方法就派上用场了。

Array.from():将伪数组转换为真数组

语法:

array = Array.from(arrayLike);

作用:将伪数组或可遍历对象转换为真数组。

代码举例:

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() 等方法。

伪数组举例

<body>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>

    
</body>

上面的布局中,有三个 button 标签,我们通过getElementsByTagName获取到的btnArray实际上是伪数组,并不是真实的数组:

既然btnArray是伪数组,它就不能使用数组的一般方法,否则会报错:

解决办法:采用Array.from方法将btnArray这个伪数组转换为真数组即可:

Array.from(btnArray);

然后就可以使用数组的一般方法了:

Array.of():创建数组

语法:

Array.of(value1, value2, value3);

作用:根据参数里的内容,创建数组。

举例:

const arr = Array.of(1, 'abc', true);
console.log(arr); // 打印结果是数组:[1, "abc", true]

补充:new Array()和 Array.of()的区别在于:当参数只有一个时,前者表示数组的长度,后者表示数组中的内容。

数组元素的添加和删除

push()

push():向数组的最后面插入一个或多个元素,返回结果为新数组的长度。会改变原数组,因为原数组变成了新数组。

语法:

新数组的长度 = 数组.push(元素);
新数组的长度 = 数组.push(元素1,元素2 ...);

代码举例:

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():删除数组中的最后一个元素,返回结果为被删除的元素。

语法:

被删除的元素 = 数组.pop();

代码举例:

var arr = ['王一', '王二', '王三'];
var result1 = arr.pop();

console.log(JSON.stringify(arr)); // 打印结果:["王一","王二"]
console.log(result1); // 打印结果:王三

unshift()

unshift():在数组最前面插入一个或多个元素,返回结果为新数组的长度。会改变原数组,将原数组变成了新数组。插入元素后,其他元素的索引会依次调整。

语法:

新数组的长度 = 数组.unshift(元素);
新数组的长度 = 数组.unshift(元素1,元素2...);

代码举例:

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():删除数组中的第一个元素,返回结果为被删除的元素。

语法:

被删除的元素 = 数组.shift();

代码举例:

var arr = ['王一', '王二', '王三'];

var result1 = arr.shift();

console.log(JSON.stringify(arr)); // 打印结果:["王二","王三"]
console.log(result1); // 打印结果:王一

splice()

splice():从数组中删除指定的一个或多个元素,返回结果为被删除元素组成的新数组(会改变原来的数组)。

备注:该方法会改变原数组,会将指定元素从原数组中删除;被删除的元素会封装到一个新的数组中返回。

语法:

新数组 = 原数组.splice(起始索引index);

新数组 = 原数组.splice(起始索引index, 需要删除的个数);

新数组 = 原数组.splice(起始索引index, 需要删除的个数, 新的元素1, 新的元素2...);

上方语法中,第三个及之后的参数,表示:删除元素之后,向原数组中添加新的元素,这些元素将会自动插入到起始位置索引的前面。也可以理解成:删除了哪些元素,就在那些元素的所在位置补充新的内容。

slice()方法和splice()方法很容易搞混,请一定要注意区分。

举例 1:

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:

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:

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:(删除指定元素,用得很多)

const arr4 = ['a', 'b', 'c', 'd'];
arr4.splice(arr4.indexOf('c'), 1); // 删除数组中的'c'这个元素

console.log('arr4:' + JSON.stringify(arr4));

举例 5:(第三个参数的用法)

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));

打印结果:

arr5:["a","千古壹号","vae","e","f"]
result5:["b","c","d"]

我们再看个类似的例子:

// 需求:针对数组 [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()方法的作用是数组合并。

语法:

    新数组 = 数组1.concat(数组2, 数组3 ...);

举例:

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));

打印结果:

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'];

从打印结果中可以看到,原数组并没有被修改。

数组合并的另一种方式:

我们可以使用...这种扩展运算符,将两个数组进行合并。举例如下:

const arr1 = [1, 2, 3];

const result = ['a', 'b', 'c', ...arr1];
console.log(JSON.stringify(result)); // 打印结果:["a","b","c",1,2,3]

备注:数组不能使用加号进行拼接。如果使用加号进行拼接会先转换成字符串再拼接。

slice()

slice():从数组中提取指定的一个或者多个元素,返回结果为新的数组(不会改变原来的数组)。

备注:该方法不会改变原数组,而是将截取到的元素封装到一个新数组中返回。

语法:

新数组 = 原数组.slice(开始位置的索引);

新数组 = 原数组.slice(开始位置的索引, 结束位置的索引);  //注意:提取的元素中,包含开始位置,不包含结束位置

举例:

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));

打印结果:

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()将伪数组,转化为真数组。写法如下:

// 方式1
array = Array.prototype.slice.call(arrayLike);

// 方式2
array = [].slice.call(arrayLike);

ES6 看不下去这种蹩脚的转化方法,于是出了一个新的 API:(专门用来将伪数组转化成真数组)

array = Array.from(arrayLike);

关于这个 API 的详细介绍,上面的内容已经讲了,请往前翻。

fill()

fill():用一个固定值填充数组,返回结果为新的数组。会改变原数组。

语法:

// 用一个固定值填充数组。数组里的每个元素都会被这个固定值填充
新数组 = 数组.fill(固定值);

// 从 startIndex 开始的数组元素,用固定值填充
新数组 = 数组.fill(固定值, startIndex);

// 从 startIndex 到 endIndex 之间的元素(包左不包右),用固定值填充
新数组 = 数组.fill(固定值, startIndex, endIndex);

举例1:

// 创建一个长度为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:

// 指定位置进行填充
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():反转数组,返回结果为反转后的数组(会改变原来的数组)。

语法:

反转后的数组 = 数组.reverse();

举例:

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:(当数组中的元素为字符串时)

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:(当数组中的元素为数字时)

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:

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));

打印结果:

arr = [1, 2, 3, 4, 5, 11];
result = [1, 2, 3, 4, 5, 11];

上方代码的写法太啰嗦了,其实也可以简化为如下写法:

写法 2:(ES5写法)

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写法,箭头函数)

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:(推荐写法)

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 中的箭头函数来写。

举例:将数组从小到大排序

将数组从小到大排序,这个例子很常见。但在实际开发中,总会有一些花样。

下面这段代码,在实际开发中,经常用到,一定要掌握。完整代码如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        
        
        <title>Document</title>
    </head>
    <body>
        
    </body>
</html>

打印结果:

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:

元素的索引 = 数组.indexOf(想要查询的元素);

元素的索引 = 数组.lastIndexOf(想要查询的元素);

备注:indexOf() 是从左往右查找元素的位置。同理,lastIndexOf()是从右往左寻找。

解释:可以检索一个数组中是否含有指定的元素。如果数组中含有该元素,则会返回其第一次出现的索引,并立即停止查找;如果没有找到指定的内容,则返回 -1。

这个方法的作用:

  • 如果找到了指定的元素,就返回元素对应的位置。
  • 如果没有找到指定的元素,就会返回-1。

注意:indexOf()在检索时,是严格类型约束,类似于===。

举例 :

const arr = ['a', 'b', 'c', 'd', 'e', 'd', 'c'];

console.log(arr.indexOf('c')); //从前往后,找第一个"c"在哪个位置
console.log(arr.lastIndexOf('d')); //从后往前,找第一个"d"在哪个位置

打印结果:

2
5

举例:

let arr = ['1', '2', '3', '4', '5'];
console.log(arr.indexOf(2));

打印结果:

-1

语法 2:

这个方法还可以指定第二个参数,用来指定查找的起始位置。语法如下:

索引值 = 数组.indexOf(想要查找的元素, [查找的起始位置]);

这个方法的第二个参数非常巧妙,数据结构与算法的面试题中,时常出现。

举例:(两个参数时,需要特别注意)

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()

语法:

布尔值 = arr.includes(想要查找的元素, [position]);

解释:判断一个数组中是否包含指定的元素。如果是,则会返回 true;否则返回 false。

参数中的 position:如果不指定,则默认为0;如果指定,则规定了检索的起始位置。

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()

语法:

const itemResult = arr.find((currentItem, currentIndex, currentArray) => {
    return true;
});

作用:找出第一个满足「指定条件返回 true」的元素,并立即停止查找;如果没找到,则返回 undefined。

备注:一旦找到符合条件的第一个元素,将不再继续往下遍历。

举例1:

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()

语法:

const indexResult = arr.findIndex((currentItem, currentIndex, currentArray) => {
    return true;
});

作用:找出第一个满足「指定条件返回 true」的元素的索引,并立即停止遍历;如果没找到,则返回 -1。

举例:

我们直接把上面find 方法的代码示例改成 findIndex,看看效果。
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()

语法:

const boolResult = arr.every((currentItem, currentIndex, currentArray) => {
    return true;
});

every():对数组中每一项运行回调函数,如果都返回 true,every 就返回 true;如果有一项返回 false,则停止遍历,此方法返回 false。

注意:every()方法的返回值是 boolean 值,参数是回调函数。

举例:

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():返回数组本身

数组本身 = 数组.valueOf();

这个方法的意义不大。因为我们直接写数组对象的名字,就已经是数组本身了。

遍历数组

概念

遍历数组:获取并操作数组中的每一个元素,然后得到想要的返回结果。在实战开发中使用得非常频繁。

语法:

// ES5语法
数组/boolean/无 = 数组.forEach/map/filter(function (item, index, arr) {
   相关代码和返回值;
})

// ES6语法
数组/boolean/无 = 数组.forEach/map/filter((item, index, arr) => {
   相关代码和返回值;
})

有了上面这些方法(其实远不止这几个),就可以替代 for 循环了。

我们先来看看传统的for循环,然后依次介绍其他方法。

for 循环遍历

举例:

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 循环来遍历即可。

语法

// 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()这种方式来接收,是达不到效果的。

代码举例:

let myArr = ['王一', '王二', '王三'];

myArr.forEach((currentItem, currentIndex, currentArray) => {
    console.log('item:' + currentItem);
    console.log('index:' + currentIndex);
    console.log('arr:' + JSON.stringify(currentArray));
    console.log('----------');
});

打印结果:

item:王一
index:0
arr:["王一","王二","王三"]
----------
item:王二
index:1
arr:["王一","王二","王三"]
----------
item:王三
index:2
arr:["王一","王二","王三"]
----------

forEach() 会不会改变原数组?

forEach() 会不会改变原数组?关于这个问题,大部分人会搞错。我们来看看下面的代码。

1、数组的元素是基本数据类型:(无法改变原数组)

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、数组的元素是引用数据类型:(直接修改整个元素对象时,无法改变原数组)

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、数组的元素是引用数据类型:(修改元素对象里的某个属性时,可以改变原数组)

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 修改原数组:(标准做法,一定要看)

// 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() 方法时,会不会报错?

例1:

const arr1 = undefined;

arr.forEach(item => {
  console.log(item);
  item.name = 'qianguyihao';
});

上面的代码中,数组 arr1 并不存在,所以会报错Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')

例2:

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,可用于循环遍历数组。

语法

for(let value of arr) {
	console.log(value);
}

不要使用 for in 遍历数组

for in 是专门用于遍历对象的。对象的属性是无序的(而数组的元素有顺序),for in循环就是专门用于遍历无序的对象。所以,不要用 for in 遍历数组。

for in语法:

for (let key in obj) {
	console.log(key);
	console.log(obj.key);
}

map()

语法

// 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 方法。代码举例:

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 数组中。代码举例:

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() 方法会不会改变原数组?

答案:不一定。

举例:

      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 会怎么样

举例:

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()

语法

const newArr = arr.filter((currentItem, currentIndex, currentArray) => {
    return true;
});

解释:对数组中的每一项运行回调函数,该函数返回结果是 true 的项,将组成新的数组(返回值就是这个新数组)。不会改变原数组。

作用:对数组进行过滤。

举例

举例 1:找出数组 arr1 中大于 4 的元素,返回一个新的数组。代码如下:

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]

上方代码更简洁的写法如下:

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 中。代码举例如下:

const arr1 = [
  { name: '许嵩', type: '一线' },
  { name: '周杰伦', type: '退居二线' },
  { name: '邓紫棋', type: '一线' },
];

const arr2 = arr1.filter(item => item.type == '一线'); // 筛选出一线歌手

console.log(JSON.stringify(arr2));

打印结果:

[
    { name: '许嵩', type: '一线' },
    { name: '邓紫棋', type: '一线' },
];

两端代码对比

仔细看看下面这两段代码,有什么区别。数组 arr2的打印结果是不一样的。

第一段代码:

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);

第二段代码:

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() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。返回值是回调函数累计处理的结果。

语法:

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(),我们先来看看下面的简单代码,过渡一下:

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、求和:

计算数组中所有元素项的总和。代码实现:

const arr = [2, 0, 1, 9, 6];
// 数组求和
const total = arr.reduce((prev, item) => {
    return prev + item;
});

console.log('total:' + total); // 打印结果:18

举例 2、统计某个元素出现的次数:

代码实现:

// 定义方法:统一 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、求元素的最大值:

代码实现:

const arr = [2, 0, 1, 9, 6];
// 数组求最大值
const maxValue = arr.reduce((prev, item) => {
    return prev > item ? prev : item;
});

console.log(maxValue); // 打印结果:9

参考链接:

数组练习

splice()练习:数组去重

代码实现:

//创建一个数组
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);

清空数组

清空数组,有以下几种方式:

const arr = [1, 2, 3];

arr = []; //方式1:推荐
arr.length = 0; //方式2:length属性可以赋值,在其它语言中length是只读
arr.splice(0); //方式3:删除数组中所有元素。也可以写成 arr.splice(0, arr.length)

join() 练习

问题:将一个字符串数组输出为|分割的形式,比如“千古|宿敌|素颜”。使用两种方式实现。

答案:

方式 1:(不推荐)

var arr = ['千古', '宿敌', '素颜'];
var str = arr[0];
var separator = '|';
for (var i = 1; i < arr.length; i++) {
    str += separator + arr[i]; //从第1个数组元素开始,每个元素前面加上符号"|"
}

console.log(str);

输出结果:

不推荐这种方式,因为:由于字符串的不变性,str 拼接过多的话,容易导致内存溢出(很多个 str 都堆放在栈里)。

方式 2:(推荐。通过 array 数组自带的 api 来实现)

var arr = ['千古', '宿敌', '素颜'];

console.log(arr.join('|'));

结果:

reverse() 练习

题目:将一个字符串数组的元素的顺序进行反转,使用两种种方式实现。提示:第 i 个和第 length-i-1 个进行交换。

答案:

方式 1:

function reverse(array) {
    var newArr = [];
    for (var i = array.length - 1; i >= 0; i--) {
        newArr[newArr.length] = array[i];
    }
    return newArr;
}

方式 2:(算法里比较常见的方式)

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()方法。

练习:数组去重

问题:编写一个方法去掉一个数组中的重复元素。

分析:创建一个新数组,循环遍历,只要新数组中有老数组的值,就不用再添加了。

答案:

//    编写一个方法 去掉一个数组的重复元素
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;
}

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 20-函数简介


函数的介绍

函数:就是一些功能或语句的封装。在需要的时候,通过调用的形式,执行这些语句。

补充:

  • 函数也是一个对象
  • 使用typeof检查一个函数对象时,会返回 function

函数的作用:

  • 一次定义,多次调用。将大量重复的语句抽取出来,写在函数里,以后需要这些语句时,可以直接调用函数,避免重复劳动。
  • 简化代码,可读性更强,让编程模块化。高内聚、低耦合。

来看个例子:

console.log("你好");
sayHello();	// 调用函数
sayHello();	// 再调用一次函数



// 定义函数
function sayHello(){
	console.log("欢迎");
	console.log("welcome");
}

函数的定义/声明

我们使用function关键字定义函数,中文含义是“函数”、“功能”。可以使用如下方式进行定义。

方式一:函数声明(命名函数)

使用函数声明来创建一个函数。语法:

function 函数名([形参1,形参2...形参N]){  // 备注:语法中的中括号,表示“可选”
	// 函数体语句
}

举例:

function sum(a, b){
	return a+b;
}

解释如下:

  • 函数名:命名规定和变量的命名规定一样,必须符合JS标识符的命名规则。只能是字母、数字、下划线、美元符号,不能以数字开头。
  • 圆括号里,是形参列表,可选。即使没有形参,也必须书写圆括号。
  • 大括号里,是函数体语句。

PS:在有些编辑器中,方法写完之后,我们在方法的前面输入/**,然后回车,会发现,注释的格式会自动补齐。

方式二:函数表达式(匿名函数)

使用函数表达式来创建一个函数。语法:

const 变量名  = function([形参1,形参2...形参N]){
	语句....
}

举例:

const fun2 = function() {
	console.log("我是匿名函数中封装的代码");
};

解释如下:

  • 上面的 fun2 是变量名,不是函数名。
  • 函数表达式的声明方式跟声明变量类似,只不过变量里存的是值,而函数表达式里存的是函数。
  • 函数表达式也可以传递参数。

从方式二的举例中可以看出:所谓的“函数表达式”,其实就是将匿名函数赋值给一个变量。因为,一个匿名函数终究还是要给它一个接收对象,进而方便地调用这个函数。

方式三:使用构造函数 new Function()

使用构造函数new Function()来创建一个对象。这种方式,用的少。

语法:

const 变量名/函数名  = new Function('形参1', '形参2', '函数体');

注意,Function 里面的参数都必须是字符串格式。也就是说,形参也必须放在字符串里;函数体也是放在字符串里包裹起来,放在 Function 的最后一个参数的位置。

代码举例:

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:普通函数的调用

函数调用的语法:

// 写法1(最常用)
函数名();

// 写法2
函数名.call();

代码举例:

function fn1() {
	console.log('我是函数体里面的内容1');
}

function fn2() {
	console.log('我是函数体里面的内容2');
}

fn1(); // 调用函数

fn2.call(); // 调用函数

方式2:通过对象的方法来调用

var obj = {
	a: 'qianguyihao',
	fn2: function() {
		console.log('千古壹号,永不止步!');
	},
};

obj.fn2(); // 调用函数

如果一个函数是作为一个对象的属性保存,那么,我们称这个函数是这个对象的方法。

PS:关于函数和方法的区别,本文的后续内容里有讲到,可以往下面翻。

方式3:立即执行函数

代码举例:

(function() {
	console.log('我是立即执行函数');
})();

立即执行函数在定义后,会自动调用。

PS:关于立即执行函数,本文的后续内容里有讲到,可以往下面翻。

上面讲到的这三种方式,是用得最多的。接下来讲到的三种方式,暂时看不懂也没关系,可以等学完其他的知识点,再回过头来看。

方式4:通过构造函数来调用

代码举例:

function Fun3() {
	console.log('千古壹号,永不止步~');
}

new Fun3();

这种方式用得不多。

方式5:绑定事件函数

代码举例:

<!DOCTYPE html>
<html lang="en">
    <head>
        
        
        <title>Document</title>
    </head>
    <body>
        <div id="btn">我是按钮,请点击我</div>

        
    </body>
</html>

这里涉及到DOM操作和事件的知识点,后续再讲。

方式6:定时器函数

代码举例:(每间隔一秒,将 数字 加1)

    let num = 1;
   setInterval(function () {
       num ++;
       console.log(num);
   }, 1000);

这里涉及到定时器的知识点。

函数的参数:形参和实参

定义

函数的参数包括形参和实参。形参是函数内的一些待定值。在调用函数时,需传入这些参数的具体值(即实参)。

可以在函数的()中指定一个或多个参数,也可以不指定参数。多个参数之间用英文逗号隔开。

举例:

// a, b 是形参,表示待定值
function add(a, b) {
const sum = a + b;
console.log(sum);
}

// 1, 2 是实参,表示传入的具体值。调用函数时,传入实参
add(1, 2);

形参:

  • 概念:形式上的参数。定义函数时传递的待定值(此时并不知道是什么值)。
  • 声明形参相当于在函数内部声明了变量,但并不赋值。也可以说,形参的默认值是 undefined。

实参:

  • 概念:实际上的参数。调用函数时传递的具体值。实参将传递给函数中对应的形参。

举例:

// 调用函数
add(3, 4);
add("3", 4);
add("Hello", "World");

// 定义函数:求和
function add(a, b) {
	console.log(a + b);
}

控制台输出结果:

7
34
helloworld

形参和实参的个数

实际参数和形式参数的个数,可以不同。调用函数时,解析器不会检查实参的数量。

  • 如果实参个数 > 形参个数,则末尾的实参是多余的,不会被赋值,因为没有形参能接收它。
  • 如果实参个数 < 形参个数,则末尾的形参是多余的,值是 undefined,因为它没有接收到实参。(undefined参与运算时,表达式的运算结果为NaN)

代码举例:

	function sum(a, b) {
		console.log(a + b);
	}

	sum(1, 2);
	sum(1, 2, 3);
	sum(1);

打印结果:

3
3
NaN

实参的数据类型

函数的实参可以是任意的数据类型。调用函数时,解析器不会检查实参类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型检查。

函数的返回值

return 关键字

函数体内可以没有返回值,也可以根据需要加返回值。语法格式:return 函数的返回值。

举例:

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 语句中的值,同时结束当前的函数体内的代码,退出当前函数。

函数名、函数体和函数加载问题(重要,请记住)

我们要记住:函数名 == 整个函数。举例:

console.log(fn) == console.log(function fn(){alert(1)});

//定义fn方法
function fn(){
	alert(1)
};

我们知道,当我们在调用一个函数时,通常使用函数()这种格式;可如果,我们是直接使用函数这种格式,它的作用相当于整个函数。

函数的加载问题:JS加载的时候,只加载函数名,不加载函数体。所以如果想使用内部的成员变量,需要调用函数。

fn() 和 fn 的区别【重要】

  • fn():调用函数。调用之后,还获取了函数的返回值。
  • fn:函数对象。相当于直接获取了整个函数对象。

方法

函数也可以成为对象的属性。如果一个函数是作为一个对象的属性保存,那么,我们称这个函数是这个对象的方法。

调用这个函数就说调用对象的方法(method)。函数和方法,有什么本质的区别吗?它只是名称上的区别,并没有其他的区别。

函数举例:

	// 调用函数
	fn();

方法举例:

	// 调用方法
	obj.fn();

我们可以这样说,如果直接是fn(),那就说明是函数调用。如果是XX.fn()的这种形式,那就说明是方法调用。

类数组对象 arguments

这部分,初学者可能看不懂,可以以后再来看。

在调用函数时,浏览器每次都会传递进两个隐含的参数:

  • 1.函数的上下文对象 this
  • 2.封装实参的对象 arguments

这一段,我们来讲一下 arguments。例如:

function foo() {
    console.log(arguments);
    console.log(typeof arguments);
}

foo('a', 'b');

打印结果:

定义

函数内的 arguments 是一个类数组对象,里面存储的是它接收到的实参列表。所有函数都内置了一个 arguments 对象,有个讲究的地方是:只有函数才有arguments。

具体来说,在调用函数时,我们所传递的实参都会在 arguments 中保存。arguments 代表的是所有实参。

arguments 的展示形式是一个伪数组。意思是,它和数组有点像,但它并不是数组。它具有以下特点:

  • 可以进行遍历;具有数组的 length 属性,可以获取长度。
  • 可以通过索引(从0开始计数)存储数据、获取和操作数据。比如,我们可以通过索引访问某个实参。
  • 不能调用数组的方法。比如push()、pop() 等方法都没有。

我们看一下 arguments 的使用。

arguments.length 返回函数实参的个数

arguments.length 可以用来获取实参的个数。

举例:

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('----------------');
}

打印结果:

此外,即使我们不定义形参,也可以通过 arguments 来获取实参:arguments[0] 表示第一个实参、arguments[1] 表示第二个实参,以此类推。

举例:将传入的实参进行求和,无论实参的个数有多少。代码实现:

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,这个属性对应一个函数对象,就是当前正在指向的函数对象。

function fun() {
    console.log(arguments.callee == fun); // 打印结果为true
}

fun('hello');

在使用函数递归调用时,推荐使用 arguments.callee 代替函数名本身。

arguments 可以修改元素

arguments 还可以修改元素,但不能改变数组的长度。举例:

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 求函数实参中的最大值。

代码实现:

	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));

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 21-递归函数


递归函数

概念

如果一个函数在内部调用这个函数自身,这个函数就是递归函数。

递归在数据结构和算法中经常用到,可以将很多复杂的数据模型拆解为简单问题进行求解。一定要掌握。

递归的要素

  • 递归模式:把大问题拆解为小问题进行分析。也称为递归体。
  • 边界条件:需要确定递归到何时结束。也称为递归出口。

代码演示:计算阶乘

提问:求一个正整数的阶乘。

普通写法:

// 函数:计算一个正整数的阶乘
function factorial(n) {
  let result = 1;
  for (let i = 1; i <= n; i++) {
    result *= i;
  }
  return result;
}

console.log(factorial(5)); // 120

现在,我们学习了递归函数之后,会有更简洁的写法。

递归写法:

// 递归函数:计算一个正整数的阶乘
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表示一个三位数。请找出所有的喇叭花数。

思路:将计算某个数字的阶乘封装成函数。

代码实现:

// 递归函数:计算一个数的阶乘
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项。

代码实现:

// 递归函数:返回斐波那契数列中下标为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));
}

小结

关于递归的案例,今后我们还会学习更多的应用场景。比如深拷贝就会用到递归。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 22-立即执行函数


立即执行函数 IIFE

概念

函数定义完,就立即被调用,这种函数叫做立即执行函数。英文是 IIFE(Immediately-invoked function expression),立即调用函数表达式。

语法格式

语法1:

(function() {
  // 函数体
})();

语法2:(立即执行函数也可以传参)

(function() {
  // 函数体
})(a, b);

语法解释:

  • function(){}这种写法,需要再加一对圆括号,变成``

举例

现有匿名函数如下:

	function(a, b) {
		console.log("a = " + a);
		console.log("b = " + b);
	};

立即执行函数如下:

	(function(a, b) {
		console.log("a = " + a);
		console.log("b = " + b);
	})(123, 456);

立即执行函数往往只会执行一次。为什么呢?因为没有变量保存它,执行完了之后,就找不到它了。

IIFE的作用

为变量赋值

当给变量赋值需要一些较复杂的计算时,使用IIFE显得语法更紧凑。

const sex = 'male';
const nickName = (function () {
  if (sex == 'male') {
    return '帅哥';
  } else {
    return '美女';
  }
})();

console.log(nickName);

将全局变量变为局部变量

现有如下代码:

var arr = [];
for (var i = 0; i < 5; i++) {
  arr.push(function () {
    console.log(i);
  });
}
arr[2](); // 打印5

我们知道,上方代码中,i 是全局变量,所有函数共享内存中的同一个变量i。

现在,我们通过立即执行函数进行改造:

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 的值,说明形成了闭包)。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 23-作用域、变量提升、函数提升


作用域

作用域(Scope)的概念和分类

  • 概念:作用域是一个变量或函数的作用范围。作用域在函数定义时,就已经确定了。
  • 目的:为了提高程序的可靠性,同时减少命名冲突。

在 JS 中,一共有两种作用域:(ES5 中)

  • 全局作用域:作用于整个 script 标签内部,或者作用于一个独立的 JS 文件。
  • 函数作用域(局部作用域):作用于函数内的代码环境。

全局作用域 和 window 对象

直接编写在 script 标签中的 JS 代码,都在全局作用域。全局作用域在页面打开时创建,在页面关闭时销毁。

在全局作用域中有一个全局对象 window,它代表的是浏览器的窗口,由浏览器创建,我们可以直接使用。相关知识点如下:

  • 创建的变量都会作为 window 对象的属性保存。比如在全局作用域内写 const a = 100,这里的 a 等价于 window.a。
  • 创建的函数都会作为 window 对象的方法保存。

作用域的访问关系

在内部作用域中可以访问外部作用域的变量,在外部作用域中无法访问到内部作用域的变量。

代码举例:

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),这个变量是全局变量。

比如:

 // 声明变量时如果不加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:

console.log(a);
var a = 123;

打印结果:undefined。注意,打印结果并没有报错,而是 undefined,说明变量 a 被提前声明了,只是尚未被赋值。

举例 2:

console.log(a);
a = 123; //此时a相当于window.a

程序会报错:Uncaught ReferenceError: a is not defined。

举例 3:

a = 123; //此时a相当于window.a
console.log(a);

打印结果:123。

举例 4:

foo();

function foo() {
    if (false) {
        var i = 123;
    }
    console.log(i);
}

打印结果:undefined。注意,打印结果并没有报错,而是 undefined。这个例子,再次说明了:变量 i 在函数执行前,就被提前声明了,只是尚未被赋值。

例 4 中, if(false)里面的代码虽然不会被执行,但是整个代码有解析的环节,解析的时候就已经把 变量 i 给提前声明了。

总结:

既然在ES5 中存在变量提升的现象,那么,在实战开发中,为了避免出错,建议先声明一个变量,然后再使用这个变量。

函数的声明提前(函数提升)

函数声明:

使用函数声明的形式创建的函数function foo(){},会被声明提前。

也就是说,整个函数会在所有的代码执行之前就被创建完成。所以,在代码顺序上,我们可以先调用函数,再定义函数。

代码举例:

fn1(); // 虽然 函数 fn1 的定义是在后面,但是因为被提前声明了, 所以此处可以调用函数

function fn1() {
    console.log('我是函数 fn1');
}

函数表达式:

使用函数表达式创建的函数const foo = function(){},不会被声明提前,所以不能在声明前调用。

很好理解,因为此时只是变量 foo 被提升了,且值为 undefined,并没有把 function(){} 赋值给 foo。

所以,下面的例子会报错:

// 不会报错,可以正常执行函数,正常打印结果
fun1();

// 此时 fun2 相当于 undefined。执行时会报错:Uncaught ReferenceError: Cannot access 'fun2' before initialization
fun2();

// 函数声明,会被提前声明
function fun1() {
  console.log('我是 fun1 函数');
}

// 函数表达式,不会被提前声明
const fun2 = function () {
  console.log('我是 fun12 函数');
};

函数提升优先于变量提升

在JS的规则中,函数提升优先于变量提升。来看看下面这段代码,你认为打印结果应该如何:(这是一道经典面试题)

fun(); // 打印 B

// 变量提升
var fun = function () {
  console.log('A');
};

// 函数提升
function fun() {
  console.log('B');
}

fun(); // 打印 A

打印结果:

B
A

当声明被提前后,上方代码的实际顺序可以这样理解:(把它当成伪代码理解即可)

/*伪代码*/

// 函数提升
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 声明的变量都是全局变量,且并不会被提前声明。

举例:

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。

补充:定义形参就相当于在函数作用域中声明了变量。举例如下:

function fun(e) {
    // 这个函数中,因为有了形参 e,此时相当于在函数内部的第一行代码里,写了 var e;
    console.log(e);
}

fun(); //打印结果为 undefined
fun(123); //打印结果为123

ES5中没有块级作用域

在其他编程语言中(如 Java、C#等),存在块级作用域,由{}包括起来。比如在 Java 语言中,if 语句里创建的变量,只能在 if 语句内部使用:

if(true){
    int num = 123;
    system.out.print(num); // 123
}
system.out.print(num); // 报错

但是,在 ES5 中没有块级作用域。举例如下:

if (true) {
    var num = 123;
    console.log(num); //123
}

console.log(num); //123(可以正常打印)

作用域链

先来认识函数的嵌套:

  • 只要是代码,就至少有一个作用域
  • 函数内部有局部作用域
  • 如果函数内部还嵌套了函数,那么在这个作用域中就又诞生了另一个作用域。

基于上面几条内容,我们可以得出作用域链的概念。

作用域链:在嵌套函数中,变量会从内到外逐层寻找它的定义(查找时,采用就近原则)。也就是说,采用的是链式查找的方式来决定取哪个值,这种结构称之为作用域链。

代码举例:

var num = 10;

function fn() {
    // 外部函数
    var num = 20;

    function fun() {
        // 内部函数
        console.log(num);
    }
    fun();
}
fn();

打印结果:20。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



title: 24-预编译


我们在上一篇文章《作用域》中简单讲过“变量提升”,今天来讲一下预编译,这对我们深入理解变量提升会有帮助。

JavaScript 运行三部曲

  • 语法分析
  • 预编译
  • 解释执行

预编译前奏

在讲预编译前,我们先来普及下面两个规律。

两个规律

规律1:任何变量,如果未经声明就赋值,此变量是属于 window 的属性,而且不会做变量提升。(注意,无论在哪个作用域内赋值)

比如说,如果我们直接在代码里写 console.log(a),这肯定会报错的,提示找不到 a。但如果我直接写 a = 100,这就不会报错,此时,这个 a 就是 window.a。

规律2:一切声明的全局变量,全是window的属性。(注意,这里说的是在全局作用域内声明的全局变量,不是说局部变量)

比如,当定义 var a = 200 时,这个 a 就是 window.a。

由此,我们可以看出:window 代表了全局作用域(是说「代表」,没说「等于」)。

举例

掌握了上面两句话之后,我们再来看看下面的例子。

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 对象的属性名,值为整个函数体。

这个地方比较难理解。但只有了解了函数的预编译,才能理解明白函数的执行顺序。

代码举例:

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预编译原理分析:
  • 预编译及变量提升:
  • 宏任务&微任务相关:

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:



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 条的举例:

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 条的举例:

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() 方法的另一个应用:可以实现继承。之所以能实现继承,其实是利用了上面的作用。

语法:

fn1.call(想要将this指向哪里, 函数实参1, 函数实参2);

备注:第一个参数中,如果不需要改变 this 指向,则传 null。

call() 方法举例

举例 1、通过 call() 调用函数:

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 指向:

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() 实现继承:

// 给 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()需要传递数组,所以它有一些巧妙应用,稍后看接下来的应用举例就知道了。

语法:

fn1.apply(想要将this指向哪里, [函数实参1, 函数实参2]);

备注:第一个参数中,如果不需要改变 this 指向,则传 null。

到这里可以看出, call() 和 apply() 方法的作用是相同的。唯一的区别在于,apply() 里面传入的实参,必须是数组(或者伪数组)。

apply() 方法举例

举例、通过 apply() 改变 this 指向:

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() 曲线救国。

举例:求数组中多个元素的最大值:

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() 是最为合适的。

语法:

新函数 = fn1.bind(想要将this指向哪里, 函数实参1, 函数实参2);

参数:

  • 第一个参数:在 fn1 函数运行时,指定 fn1 函数的this 指向。如果不需要改变 this 指向,则传 null。
  • 其他参数:fn1 函数的实参。

解释:它不会调用 fn1 函数,但会返回 由指定this 和指定实参的原函数拷贝。可以看出, bind() 方法是有返回值的。

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:

本文由 GitVP 从 GitHub 收录并在站内全文呈现,版权归原作者所有(仅用于学习交流)。

← 回到全部文章

同分类还有