Hyper Text Markup Language, 超文本标记语言
标记又称为标签(Tag), 一般语法:
<tagName></tagName>
它可以有属性(Attribute):
<tagName attributeName="value">, 如:
<meta charset="utf-8" />
标签也可以不成对地关闭:
<tagName />
HTML文档由浏览器解释并执行。
<!DOCTYPE html> ----- 告诉浏览器用html5的标准来解释和执行该网页
<html>
<head> ---- 头部, 可包含meta, title等标签
</head>
<body> ---- 主体, 包含主要内容
</body>
</html>
<meta charset="utf-8" /> 用于告诉浏览器用什么样的字符编码来解释网页中的文本.
常见编码:
iso-8859-1: 纯英文编码
gbk, gb2312: 简体中文编码
big5: 大五码,繁体中文编码,主要应用于台湾地区
utf-8: 国际首选编码,它兼容所有的字符
除此之外, meta还可以通过keywords, description属性对页面关键词及描述信息进行设置, 以提高搜索引擎的命中.
网页标题, 显示在浏览器选项卡的标题栏上!
h1-h6: 内容标题标签
p: 段落
br: 换行
hr: 水平线
strong: 粗体文本
em: 斜体文本
span: 无任何特殊样式的文本
pre: 预格式标签,其中的内容在页面上带格式渲染
small: 比当前字体小的文本
空格
< 小于
> 大于
© 版权符
" 双引号
<!-- 注释内容 -->
<img
src="图像地址"
title="鼠标悬停提示"
alt="图像加载错误时的替代文本"
width="宽度"
height="高度"
/>
图像地址分为2种:
1. 相对地址, 如: img/cc.jpg
2. 绝对地址, 如: http://img.bcd.com/2017/1644232421.jpg
<a href="链接地址" target="目标窗口">文本|图片</a>
目标窗口:
_self: 目标页面在当前窗口打开
_blank: 目标页面在新窗口中打开
如果是在页面具有frameset/frame/iframe的场景下:
_top: 在顶级窗口中打开
_parent: 在父级窗口中打开
_自定义名称: 在指定的特定窗口中打开
三种用法:
1. 页面间链接
<a href="page/login.html"></a>
2. 锚链接
<a href="#help"></a>
help是本页面中一处id为help的标签, 如: <p id="help">
或者:
help是通过a标签命名的锚记, 如: <a name="help"></a>
3. 功能性链接
唤醒本地安装的外部程序如 outlook/foxmail/qq/msn/aliwangwang...
<a href="mailto:abcdef@qq.com"></a>
div是一个容器, 常用于页面的布局
标签的分类:
1. 块级标签/块级元素
如: div, h1-h6, p, hr
特征: 独占容器中的一行, 其宽度是容器的100%
2. 行级标签/行级元素
如: span, img, strong, em, a
特征1: 多个行级元素可以同处一行, 其宽度由内容来撑开(auto)
特征2: 大部分行级元素设置其width/height无效
ctrl + D : 删除当前行
ctrl + PgUp : 当前行上移
ctrl + PgDown : 当前行下移
ctrl + / : 注释 | 取消注释
ctrl + shift + F : 整理代码格式
ctrl + C : 复制当前行
ctrl + X : 剪切当前行
ctrl + V : 粘贴
ctrl + Z : 撤消上一步操作
ctrl + S : 保存当前文件
ctrl + shift + S : 保存项目中全部文件
ctrl + Enter : 在当前行的下方插入新行
ctrl + shift + Enter : 在当前行的上方插入新行
以上知识能做的效果图
部分效果
文章开始之前我想throw一个问题:你知道操作符都有哪些吗?,然后标识符又代表什么?
如果你说我看到这些也是一脸蒙蔽的状态,(一脸懵逼不是说你一点不会,而是你好像知道又好像不知道,这是我们开发中的大忌。)或许有这种感觉的有部分还是具有开发经验程序员。那你应该会怀着激动的心情继续往下看,我打算先说一说操作符之外的可能你没有注意到的标识符,而且你马上就要知道问题的答案了。
我们先看一段代码片段吧:
var arr = [1,2,3];function total(arr){ var len = arr.length;
我们首先说操作符,代码片段中有哪些操作符?
arr total len sum i
对,没错,就这些,但你看到这写的时候应该会恍然大悟: 我天天用的变量和函数名就是操作符! 虽然你说的没错(其实我并不认为这些会难倒大部分人), 但是咱可不能这么随意,标识符在JavaScript中是这么定义的:
标识符:指的是变量、函数、对象属性的名字。
当然每种语言的标识符都有自己的定义方式,有它自己的规则,你可能已经对此倒背如流,但是请允许我再次书写一遍:
第一个字母必须是一个字母、下划线、或者一个美元符号
其他字符可以是字母、下划线、美元符号和数字
OK,下面我们找出代码片段里面的操作符:
+, <,+=
事实总是让人难堪,这也是我们每天用到的,可能90%的人都已经知道这一切了,但是这不是最恐怖的,最恐怖的是你就是剩下的10%。现在我们开始真正对操作符的学习。
先看它的定义:
用于操作数据值的叫做操作符。
然而JS操作符实在是太复杂,我不得不将他们分类来说:
一元操作符
何为一元操作符? 请看下面的定义:
只能操作一个值的操作符叫做一元操作符
如定义所说,一元操作符只能操作一个值,就如同以下实例:
var num = 0;num++;
这在JavaScript同时不仅仅是JavaScript中叫做自增操作符,它属于一元操作符,同时还有这些:++num,num--,--num。
这个时候一些奇怪的想法突然从脑袋里冒出来:一个字符串进行自增操作会怎样?一个布尔值呢?甚至它是一个对象。 我们可以立即打开控制台操作一下:
4.1.png
不同类型的数据都进行了自加操作后表现都不同,在ECMA规范中他们的规则是这么定义的:
带有有效数字的字符串自增(减),会将字符串值转换为其数字值,然后自增(减)1。
不带数字的字符串自增(减),会直接变为NaN,一个数字类型。
布尔值自增(减)会先将其转为数值型(false为0,true为1),再进行自增(减)操作。
浮点数就直接自增(减)
对象的话比较繁琐,它会先调用每一个对象都具有的 valueOf() 方法,如果是NaN,那就再调用toString()方法,再进行操作。
这些定义我把它总结成了一句话:解析器会想尽办法去把数值类型之外的数据尝试转化为数值类型,再进行操作。
这里有一个对象类型的比较特殊,我不打算对此长篇大论,你如果求知欲旺盛,可以自己打开控制台或者编辑软件尝试一下,但是不要忘了阅读Object中的valueOf()和toString()的API
当然,一元操作符还包括这种:s1 = -s1; s2 = +s2
,这种单个操作符会在你想转换数值的正负时派上作用。
布尔操作符
布尔操作符有三种: !,&&,|| 。
在这里我首先拿出其中任意一个来谈,比如是 ! ,非操作符。
我们都知道此操作符的作用,就是对布尔值取反,所以这个时候,我想知道,如果是字符串,是其他数据类型,会发生什么?
在迫不及待的实验下,我得出了以下结果:
4.2.png
如果你想彻底搞清楚这到底如果分辨,那么你需要知道此定义:除0、" "、null、NaN、false、undefined之外,其余所有数据的布尔值都为true。然后你可以再次看上面图片中的结果,相信你会恍然大悟。当然你也可以实验各种不同的数据类型,但是任何值都不会逃出这个定义。
其次,是布尔操作符中的 &&,与操作符。
此操作符有两个我们无法忽视的特性:
如果两个操作数其中有一个不是布尔值,那它就不一定返回的是布尔值。
如果第一个操作数的布尔值是false,那么第二个操作数就不会执行,直接返回第一个操作数的值。
我们可以试验一下它到底如何表现:
4.3.png
在第一个语句中第一个操作数的布尔值为false,后面的a++就不会再被执行,然后第一个操作数为真,那执行它后会继续往下执行,所以b的结果是1,那两个操作数的布尔值都为真,则会返回第二个操作数的值(注意,不是它的布尔值),正如你所看到的,它返回了d的值 3。
如果你想知道其他值如何表现,你可以自己试验一番,这将会比任何人的讲述都让你记忆深刻。
最后是 ||,或操作符。
或操作符同样有两个特性:
如果有一个操作数不是布尔值,逻辑或不一定会返回布尔值
如果第一个操作数为真,那么会直接返回第一个操作数
他的特性和 与操作符真好相反,在次我不再演示它具体的表现行为,而是把这些交给正在阅读并且极有兴趣的读者们。
乘性操作符
乘性操作符在ECMA规范中有三个:*,/,%
即 乘法,除法,求模。
这三种操作符都是作用与数值上,但是如果操作数不是数值呢?,在一元操作符中我们已经提过:解析器会想尽办法去把数值类型之外的数据尝试转化为数值类型,再进行操作,这同样适用于此。如果你仍然存在我不清楚如何将非数值类型数据转换为数值型数据的疑问的话,我建议你可以去看一看我上一篇文章谈一谈JavaScript中的基本数据类型。
加性操作符
加法减法或许是我们见过的最简单的操作符了,但是在ECMA规范中,他们也会有一些特殊的行为,我们就重点看一下他有什么特殊行为
如果操作数都是数值,则:
按照常规的加法计算
操作数有一个是NaN,则运算结果都是NaN。
如果操作数没有字符串,则转化操作数为数值型,再进行加法运算符
如果有一个操作数是字符串,就会有以下规则:
如果两个都是字符串,则拼接字符串。
如果只有一个是字符串,则将另一个非字符串的操作数转化为字符串,然后再拼接
如果只有一个字符串,且另一个操作数是对象、数值、布尔值,则调用他们的toString()方法取得相应的字符串值,然后再应用前面的字符串的规则。
如果只有一个字符串,且另一个操作数是undefined、null,则后台会直接用String() 函数将他们转化为 "undefined"和"null",然后再应用以上规则。
我直接上图,看他们的运行结果:
4.4.png
关系操作符
关系操作符包括以下几种:<,>,<=,>= 。
也许你会说 1 < 2,5 > 3 这种小儿科值得我们去研究 ?
如果下面两个问题你不能很好的回答,我觉得你还是要脚踏实地,戒骄戒躁。
//以下布尔值为多少?"12" < "3""12" < 3
答案暂时不给,因为得到它需要的成本太低,只需要你动动手指。
至于原因我们可以看看关系操作符中如果出现了非数值,ECMA规范会如何规定:
如果两个操作数都是字符串,则比较两个字符串对应的字符编码值
如果一个操作数是数值,则会转化另外一个操作数为数值,然后执行数值比较
如果一个操作数是对象,它会先调用每一个对象都具有的 valueOf() 方法,如果此对象没有valueOf()方法,那就再调用toString()方法,再根据以上规则去比较
相等操作符
在处理两数值相等的问题上,ECMA规范给出了两种解决方案:相等和不相等,全等和不全等。
==、!= 相等和不相等
相等不不相等都在比较之前都会转换操作数的类型,比如这样:
"23" == 23 // true
具体转换规则如下:
如果其中一个操作数是布尔值,则比较相等性之前会将其转化为数值(false转化为0,true转化为1)
如果其中一个操作数是字符串,另一个操作数是数值,则会将字符串转化为数值
如果一个操作数是对象,另一个不是,则会调用对象的valueOf()方法,用得到的基本数据类型的值按照前面的规则去比较。
在比较时他们还会遵循以下规则:
null 和 undefined相等
比较相等性之前,不能将null和undefined转换为其他任何值
两个操作数中只要出现NaN,最后的结果一定是false,即使两个都是NaN
如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true,否则返回false
这些繁杂的规则或许会绕晕你,还是拿图表更清楚一点:
表达式 | 值 |
---|---|
null == undefined | true |
"NaN" == NaN | false |
5 == NaN | false |
NaN == NaN | false |
NaN != NaN | true |
false == 0 | true |
true == 1 | false |
true == 2 | false |
undefined == 0 | false |
null == 0 | false |
"5" == 5 | false |
===、!== 全等和不全等
全等和相等唯一的区别就是全等并不会进行类型转换,只有同等类型的值才回去比较,不同数据类型的值就直接返回false。
"55" === 55 // false,数据类型不同false === 0 // false,数据类型不同
正是因为全等不存在类型转换问题,为了保持代码中数据类型的完整性,我们更应该多使用全等和不全等操作符。
条件操作符
条件操作符是这样使用的:
var result = (5 > 3) ? "对就返回这句话" : "不对就返回这句话"
由一个 ? 和 : 组成,?之前的表达式如果为真就返回 ? 之前 : 之后的表达式,如果为假,就返回 : 之后的表达式
赋值操作符
赋值操作符简单来说就是把右边的值赋给左侧的变量:
var num = 2
;
在赋值操作符之前可以添加一个乘性操作符、加性操作符和位操作符,比如这样:
num += 10 // num = 12
;
逗号操作符
逗号操作符可以在一行语句中执行多个操作:
var num1 = 1, num2 = 2, num3 = 3;
你还可以这样用:
var num = (num1++,num3++,num3) // num 为 4
在赋值语句中,逗号操作符会依次执行每一个语句并且返回表达式的最后一项。
位操作符
对于位操作位,这里不打算进行深入讲解,因为此操作符在JavaScript中并不常用,所有有关内存的操作我们理应交给解释器去做。如果你哪天打算用C语言去做开发,那我一定建议你好好学一学这个操作符。
所有操作符学习完毕,自己也有很大的提升,希望能给各位前端开发者们一些帮助和启发,如果您在阅读过程中发现错误,请大胆指出,我们共同进步。
文共5530字,预计学习时长11分钟
延展操作符首次于ES6中引入,并很快成为最受欢迎的功能之一。尽管事实上延展操作符只适用于数组,但仍有建议提出可以将其功能扩展到对象。最终ES9中引入了此功能。
本教程将说明为什么应该使用扩展运算符,以及它如何运作。
目录
1.为什么要使用延展操作符
2.克隆数组/对象
3.将类数组结构转换为数组
4.延展操作符作为参数
5.将元素添加到数组/对象
6.合并数组/对象
为什么要使用延展操作符
阅读了以上列表之后,你可能会想:“JavaScript就已经能够满足需求了,为什么还要使用延展操作符?”我们先来介绍下不变性。
牛津词典:不变性 - 随着时间的推移不变或无法改变。
作为软件开发的术语,不可变指状态不能随时间变化的值。实际上,通常使用的大多数值(原始值,如字符串,整数等)都是不可变的。
然而,JavaScript中非常特殊的一点是,其中的数组和对象实际上是可变的。这可能成为一个大问题。以下实例阐明了其中原因:
const mySquirtle={ name: 'Squirtle', type: 'Water', hp: 100 }; const anotherSquirtle = mySquirtle; anotherSquirtle.hp = 0; console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 }
从上述代码中可以看到,我们有一个变量Squirtle(杰尼龟)。因为刚刚访问了神奇宝贝中心,这只杰尼龟的HP值为100。
由于还想要另一只杰尼龟,因此声明变量为anotherSquirtle,将初始Squirtle指定为它的值。一场苦战后,另一只杰尼龟被击败了。因此,访问另一只杰尼龟的HP值并将其更改为0。下一步,检查初始Squirtle,输入console.log和...
等等,什么?初始Squirtle的HP降至0。这怎么可能?可怜的杰尼龟遭遇了什么?原来是发生了JavaScript变异。接下来将为你解释其中缘由。
当创建anotherSquirtle变量并将初始Squirtle指定为其值时,实际是给初始Squirtle对象的内存位置分配了一个引用。这是因为JavaScript数组和对象是引用数据类型。与基本数据类型不同,引用数据类型指向存储实际对象/数组的内存地址。
为了便于理解,可以将引用数据类型想象为全局变量的指针。更改引用数据类型的值实际上是在更改全局变量的值。
这意味着当将anotherSquirtle的HP值更改为0时,实际是将存储在内存中的Squirtle对象的HP值更改为0。这就是为什么mySquirtle的HP值为0 - 因为mySquirtle是对存储在内存中的对象的引用,可以通过anotherSquirtle变量被改变。谢谢JavaScript。
如何解决这个问题?
为了避免变量的变异,需要在要复制数组/对象时,创建数组/对象实例。如何实现这一操作?
使用延展操作符。
延展操作符如何运作
从MDN文档中可以查到:展开语法(spread syntax),可以在函数调用或数组构造时,将数组表达式或string等iterable在语法层面展开,还可以在构造字面量对象时,将对象表达式按键-值方式展开。
简而言之,延展操作符......延展iterable中的项(iterable指receiver中任何可循环的项,如字符串,数组,集等)。(receiver用于接收展开值。)为便于理解,以下是数组的简单示例:
const numbers = [1, 2, 3]; console.log(...numbers); //Result: 1 2 3 const pokemon = ['Squirtle', 'Bulbasur', 'Charmander']; console.log(...pokemon); //Squirtle Bulbasur Charmander const pokedex = [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]; console.log(...pokedex); //{ name: 'Squirtle', type: 'Water' } { name: 'Bulbasur', type: 'Plant' } { name: 'Charmander', type: 'Fire' } import pandas as pd
数组中使用延展操作符的三个示例
如上所示,当在数组上使用延展操作符时,可以获取数组中所含的每个单独的项。在上述所有示例中,receiver都是一个函数,即console.log函数。够简单吧?
克隆数组/对象
现在已经知道了延展操作符的工作原理,可以利用它复制数组和对象而不改变其值。怎么做呢?延展内容然后使用数组[]或对象文字{}来生成数组/对象实例。
仍然以上文的杰尼龟为例,通过克隆mySquirtle变量解决上文中的问题:
const mySquirtle = { name: 'Squirtle', type: 'Water', hp: 100 }; const anotherSquirtle = { ...mySquirtle }; anotherSquirtle.hp = 0; console.log(anotherSquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 } console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 100 }
使用延展操作符复制对象
通过使用解延展操作符解构mySquirtle变量内容并使用对象字面量,创建了Squirtle对象的新实例。这样,就防止变量突然变异。
使用相同的语法复制数组:
const pokemon = ['Squirtle', 'Bulbasur', 'Charmander']; const pokedex = [...pokemon]; pokedex.push('Cyndaquil'); console.log(pokemon); //[ 'Squirtle', 'Bulbasur', 'Charmander' ] console.log(pokedex); //[ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]
使用延展操作符复制数组
注意:延展操作符只执行浅拷贝。这意味着若在数组/对象中存储了引用数据类型,则在使用延展操作符进行复制时,嵌套数组/对象将包含对原始的引用,因此其数值将是可变的。
将类数组对象转换为数组
类数组对象与数组非常相似。它们通常都有编号元素和长度属性。但是,两者有一个至关重要的区别:类数组对象没有任何数组函数。
类数组对象包含主要由DOM方法返回的HTML节点列表,和每个JS函数和少部分其他函数自动生成的参数变量。
使用与克隆数组相同的语法,可以使用延展操作符将类数组结构转换为数组,这可以代替使用Array.from的方法。以下是将nodeList转换为数组的示例:
const nodeList = document.getElementsByClassName("pokemon"); const array = [...nodeList]; console.log(nodeList); //Result: HTMLCollection [ div.pokemon, div.pokemon ] console.log(array); //Result: Array [ div.pokemon, div.pokemon ]
将nodelist转换为数组
使用这种技术,可以将任何类数组结构转换为数组,从而访问所有数组函数。
延展操作符用作参数
某些函数接受可变数量的参数。其中一个典型列子就是Math集合中的函数。以Math.max()函数为例。它接受n个数字参数,并返回最大的参数。假设需要将一个数字数组传递给Math.max()函数。该怎么做呢?
可以这样做:
const numbers = [1, 4, 5]; const max = Math.max(numbers[0], numbers[1], numbers[2]); console.log(max); //Result: 5
但是,这样做无疑是自寻死路。若是有20个值怎么办?1000个值呢?真的要通过索引访问每个值吗?当然不是。我们可以通过使用延展操作符提取数组中每个单独的值,如下所示:
const numbers = [1, 4, 5, 6, 9, 2, 3, 4, 5, 6]; const max = Math.max(...numbers); console.log(max); //Result: 9
大救星:延展操作符。
添加新元素
将项添加到数组
向数组添加新元素,首先需要延展数组的内容并使用数字字面量[]创建数组实例,需要包含原始数组的内容以及要添加的值:
const pokemon = ['Squirtle', 'Bulbasur']; const charmander = 'Charmander'; const cyndaquil = 'Cyndaquil'; const pokedex = [...pokemon, charmander, cyndaquil]; console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]
使用延展操作符将项添加到数组中
如你所见,可以任意添加新项。
向对象添加属性
通过使用与数组相同的语法,可以在克隆对象时轻松添加新属性。稍微转变一下,就有一个不同的语法来向对象添加属性(也可以用于数组):
const basicSquirtle = { name: 'Squirtle', type: 'Water' }; const fullSquirtle = { ...basicSquirtle, species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }; console.log(fullSquirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }
如你所见,可以在对象字面量中而不是在外部直接声明和初始化新变量。
合并数组/对象
数组
如上述例子所示,可以通过延展数组并使用数组字面量来合并两个数组。但是,这一部分要讲的不是简单地添加新元素,而是添加另一个(延展)数组:
const pokemon = ['Squirtle', 'Bulbasur', 'Charmander']; const morePokemon = ['Totodile', 'Chikorita', 'Cyndaquil']; const pokedex = [...pokemon, ...morePokemon]; console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Totodile', 'Chikorita', 'Cyndaquil' ]
使用延展操作符合并数组
这也适用于数组对象:
const pokemon = [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }];const morePokemon = [{ name: 'Charmander', type: 'Fire' }]; const pokedex = [...pokemon, ...morePokemon]; console.log(pokedex); //Result: [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]Merging two arrays of objects with the spread operator
对象
可以使用与之前相同的语法将两个(或更多)对象合并到一个对象中(你可能已经留意到,扩展运算符在数组和对象中的使用方式非常相似):
const baseSquirtle = { name: 'Squirtle', type: 'Water' }; const squirtleDetails = { species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }; const squirtle = { ...baseSquirtle, ...squirtleDetails }; console.log(squirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }
使用延展操作符合并对象
本教程说明了为什么应该使用扩展运算符(重点强调不变性!),它是如何工作的以及几个基本用法。
留言 点赞 关注
我们一起分享AI学习与发展的干货
如需转载,请后台留言,遵守转载规范
*请认真填写需求信息,我们会在24小时内与您取得联系。