js完整包括:
js是一门用来与网页交互的脚本语言,包含:
- 核心(es):由ECMA-262定义并提供核心功能
- dom文档对象模型:提供与网页内容交互的方法和接口
- bom浏览器对象模型:提供与浏览器交互的方法和接口
es(ECMAScript):
- ECMA-262将es这门语言作为基准来定义,以便在它之上再构建更稳健的脚本语言。
- 浏览器只是es实现可能存在的一种宿主环境。
- 宿主环境提供es的基准实现和与环境自身交互必需的扩展,扩展如dom使用es核心类型和语法,提供特定于环境的额外功能。
- 其他宿主环境还有服务器端js平台node.js和被淘汰的adobe flash。
- 不涉及浏览器,ECMA-262定义了以下部分:
- 语法
- 类型
- 语句
- 关键字
- 保留字
- 操作符
- 全局对象
- ECMAScript只是对实现这个规范描述的所有方面的一门语言的称呼。
- js实现了es,而adobe actionScript同样实现了es。
- 2009年12月正式发布了es3.1,为第5版。2015年6月发布了es6,更新了有史以来最重要的一些特性。
- es符合性
- 要成为es实现,需满足:
- 支持es中描述的所有类型,值,对象,属性,函数,程序语法与语义
- 支持Unicode字符标准
- 还可以:增加额外类型,值,对象,属性,函数。支持es没定义的程序和正则表达式语法
- 提供了极大权限和灵魂度
dom
是一个应用编程接口(api),用于再HTML(旨在显示信息)中使用扩展的XML(标记语言,用于传输和存储数据)。 dom将整个页面抽象为一组分层节点。html或者xml页面的每个组成部分都是一种节点,包含不同的数据。
文档对象模型,或“DOM”,是web页面的接口。它本质上是页面的API,允许程序读取和操作页面的内容、结构和样式
<html>
<head>
<title>sample page</title>
</head>
<body>
<p>hello world</p>
</body>
</html>
通过dom表示一组分层的节点:
- 通过dom创建的表示文档的树,开发者可用随心所欲的控制网页的内容和结构。使用dom api可轻松删除,添加,替换,修改节点。
什么是dom:是一个应用编程接口,用于在HTML中使用扩展的xml。dom将页面抽象为一组分层节点,组成文档树,页面每部分都是一种节点,包含不同数据。开发者可以通过dom创建的文档树控制网页内容和结构。增删改查节点。
指定dom标准防止web分裂。
BOM
HTML5 正式形式涵盖了尽可能多bom特性。 主要针对浏览器窗口和子窗口(frame),任何特定于浏览器的扩展都归到bom。
- 弹出新浏览器窗口
- 移动缩放关闭浏览器
- navigator,location,screen,preformance,cookie,xmlHttpRequest
HTML中的javaScript
- 使用script元素
- 行内和外部脚本比较
- 文档模式对js有什么影响
- 确保js不可用时的用户体验 script 有8个属性:
- async: 立即开始下载脚本,不能阻止其他动作。只对外部脚本有效。
- crossorigin: 跨源资源共享设置,默认不使用。anonymous配置文件请求不必配置凭据标志。use-credentials设置凭据标志,出站请求会包含凭据:就是加载外部非本站的资源时必须有跨域的设置才能加载。
- defer: 脚本可延迟到文档被完全解析和显示后加载。外部脚本有效
- integrity:比对接收到的资源和指定的加密签名以验证子资源完整性。
- src: 包含要执行代码的外部文件
- type: 脚本语言的内容类型。也称mime类型。一般始终是text/javascript.值为module时,被当成ES模块,才能出现import export
- language: 废弃
- charset: 使用src属性指定的代码字符集,很少使用
行内脚本需要用 \ 转义 "</script>" 外部文件的扩展名为.js不是必需的,为动态生成或者扩展语言转译为js提供了可能。服务器经常根据扩展名确定mime类型,没有了.js要确保服务器返回正确的mime类型。
标签占位符
script便签一般放在<body>元素中的页面内容后面,这样页面会在处理js代码之前完全渲染页面。用户感觉页面加载快,空白屏出现时间短。
推迟脚本执行
defer针对外部脚本: 立即下载但延迟执行。最好只包含一个这样的脚本,因为顺序可能乱或者在DOMContentLoaded之前执行。
异步执行脚本
async 不保证顺序 加载期间不修改dom 不要使用document.write
动态加载脚本
let script = document.createElement('script')
script.src = 'xx.js'
script.async = false
document.head.appendChild(script)
影响性能,需让预加载器知道 显示声明: <link rel="proload" href="xx.js">
行内和外部文件
推荐外部文件引入js代码
- 可维护性
- 缓存 两个页面都用到同一文件
- 适应未来 spdy/http2
文档模式
文档模式有:1、混杂模式(quirks mode);2、标准模式(standards mode);3、准标准呢模式(almost standards mode)。 通常「准标准模式」和「标准模式」非常接近,很少需要区分。所以说到“标准模式”的时候, 可能是指的其中任何一个。
noscript 元素
用于不支持或者禁用js的浏览器。显示包含在内发内容。可以显示任何可以出现在body中的元素。script除外。
语言基础
- 语法 - 数据类型 - 流控制语句 - 理解函数
任何语言的核心所描述的都是这门语言在最基本的层面上如何工作,涉及/语法、操作符、数据类型以及内置功能/,在此基础之上才可以构建复杂的解决方案。如前所述,ECMA-262以一个名为ECMAScript的伪语言的形式,定义了JavaScript的所有这些方面。 ECMA-262第5版(ES5)定义的ECMAScript,是目前为止实现得最为广泛(即受浏览器支持最好)的一个版本。第6版(ES6)在浏览器中的实现(即受支持)程度次之。到2017年底,大多数主流浏览器几乎或全部实现了这一版的规范
语法
标识符
标识符,就是变量、函数、属性或函数参数的名称。 字母,_, $ 开头。剩余可以是以上和数字。 对象的属性是字符串,不是标识符。
标识符(Identifier) 就是名称的专业术语。JavaScript 标识符包括变量名、函数名、参数名和属性名。
合法的标识符应该注意以下强制规则:
- 第一个字符必须是字母、下划线(_)或美元符号($)。
- 除了第一个字符外,其他位置可以使用 Unicode 字符。一般建议仅使用 ASCII 编码的字母,不建议使用双字节的字符。
- 不能与 JavaScript 关键字、保留字重名。
- 可以使用 Unicode 转义序列。例如,字符 a 可以使用“\u0061”表示。
严格模式
不同的js解析和执行模式 es3的一些不规范写法会被处理
在脚本开头加上:“use strict” 预处理指令。函数开头也可以加上。
语句
最好加分号有好处。 多条语句可以合并到{}中。if条件语句最好都加{}即使只有一条。
关键字和保留字
break do in typeof case else instanceof var catch export new void class extends return while const finally super with continue for switch yield debugger function this default if throw delete import try
以上关键字:有特殊用途。表示空盒子语句开始结束,或者执行特定的操作,不能用作标识符或者属性名。同样的还有保留字。保留给未来做关键字用的
始终保留: enum 严格模式下保留: implements package public interface protected static let private 模块代码中保留: await
变量
松散类型,变量是命名占位符,可以保存任意数据。
var关键字
- 声明作用域
- 逗号隔开声明多个变量
- 在严格模式下,不能定义名为eval和arguments的变量
- 声明提升
- 自动提升到函数作用域顶部,把所有变量声明都拉到函数作用域的顶部
- 反复多次使用var声明同一个变量也没有问题
let
- 声明范围是块作用域,var是函数作用域。块作用域是函数作用域的子集。
- 禁止出现冗余声明。嵌套使用相同的标识符不会报错。对声明冗余报错不会因混用let和var而受影响
- 暂时性死区:let声明的变量不会在作用域中被提升
在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”(temporal deadzone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError
- 全局声明:与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)
- 不能使用let进行条件式声明是件好事
- for循环中let声明
在let出现之前,for循环定义的迭代变量会渗透到循环体外部
for (let i = 0;i<5;i++) {
console.log(i)
} // 0,1,2,3,4
使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例,所以console.log输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值 这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,包括for-in和for-of循环。
for (var i = 0;i<5;i++) {
console.log(i)
} // 5,5,5,5,5
以上,退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个最终值
const
声明变量时必须同时初始化变量,尝试修改const声明的变量会导致运行时错误。
适用于它指向的变量的引用。如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制
其余与let相同
想用const声明一个不会被修改的for循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对for-of和for-in循环特别有意义
最佳实践
- 不使用 var
- const 优先 let次之(只有提前知道未来会有修改再使用let)
数据类型
简单数据类型: undefined,null,boolean,number,string,symbol,bigInt 复杂数据类型:Object 所有值都可以用上述8种数据类型表示。
typeof
确定任意变量的数据类型。返回字符串:
- "undefined"表示值未定义;
- "boolean"表示值为布尔值;
- "string"表示值为字符串;
- "number"表示值为数值;
- "object"表示值为对象(而不是函数)或null;
- "function"表示值为函数;
- "symbol"表示值为符号。
typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数) 调用typeofnull返回的是"object"。这是因为特殊值null被认为是一个对空对象的引用 严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeof操作符来区分函数和其他对象
Undefined
只有一个值undefined,var let 声明变量没有初始化时,就是它。
增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别
对未声明的变量,只能执行一个有用的操作,就是对它调用typeof(调用delete也不会报错)
声明还是未声明,typeof返回的都是字符串"undefined" undefined是一个假值
即使未初始化的变量会被自动赋予undefined值,但我们仍然建议在声明变量的同时进行初始化。这样,当typeof返回"undefined"时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化
Null类型
只有一个值null,表示一个空对象指针。 在定义将来要保存对象值的变量时,建议使用null来初始化。检查这个变量是不是null就可以知道这个变量是否在后面被重新赋予了一个对象的引用。
undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等(==, 这个操作符会为了比较而转换它的操作数)
任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义,并进一步将其与undefined区分开来
null是一个假值
Boolean 类型
两个值:true和false Boolean()转型函数。所有其他ECMAScript类型的值都有相应布尔值的等价形式。Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。什么值能转换为true或false的规则取决于数据类型和实际的值。
像if等流控制语句会自动执行其他类型值到布尔值的转换
Number类型
使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值) 不同的数值类型相应地也有不同的数值字面量格式。十进制八进制十六进制
对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值0
7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数。八进制字面量在严格模式下是无效的 创建十六进制字面量,数值前缀0x(区分大小写),然后是十六进制数字(09以及A~F).十六进制数字中的字母大小写均可 let num1 = 0xA, num2 = 0x1f
使用八进制和十六进制格式创建的数值在所有数学操作中都被视为十进制数值
正零和负零在所有情况下都被认为是等同的
- 浮点数 要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字 小数点前面不是必须有整数,但推荐加上
因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转换为整数
科学计数法:一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂 至少包含6个零的浮点值转换为科学记数法 1.23e7 3,123e-8
浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。算术计算中远不如整数精确
0.1加0.2得到的不是0.3,而是0.30000000000000004。由于这种微小的舍入错误,导致很难测试特定的浮点值
永远不要测试某个特定的浮点值
注意 之所以存在这种舍入错误,是因为使用了IEEE 754数值,这种错误并非ECMAScript所独有。其他使用相同格式的语言也有这个问题。
- 值的范围 内存限制:最小数值:Number.MIN_VALUE: 5e-324。最大: Number.MXA_VALUE: 1.797693134862315 7e+308。 无穷大和无穷小:Infinity -Infinity,返回了这两个值就不能进一步计算。 isFinite() 函数可判断某个值是否是有限的。
Number.NEGATIVE_INFINITY和Number.POSITIVE_INFINITY也可以获取正、负Infinity
- NaN 不是数值(Not a Number),表示本来要返回数值的操作失败了(而不是抛出错误) -0,+0 ,0 相互除会返回NaN。分子是非零,分母是0或者有符号0 则会返回Infinity 或者-Infinity
独特属性: 任何涉及NaN的操作都会返回NaN。不等于任何值包括自身。 isNaN() 函数 用于判断 是否 "不是数值"。把一个值传给isNaN()后,该函数会尝试把它转换为数值。不能转换为数值的值都会导致这个函数返回true。某些非数值的值可以直接转换成数值,如字符串"10"或布尔值。
调用对象的valueOf()方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用toString()方法,并测试其返回值。
- 数值转换 3个函数可以将非数值转换成数值:
- Number(), 转型函数,可用于任何数据类型
- ❑ 布尔值,true转换为1,false转换为0。
- ❑ 数值,直接返回。
- ❑ null,返回0。
- ❑ undefined,返回NaN。
- 字符串:
- 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number("1")返回1,Number("123")返回123,Number("011")返回11(忽略前面的零)。
- 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。
- 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值。
- 如果是空字符串(不包含字符),则返回0。
- 如果字符串包含除上述情况之外的其他字符,则返回NaN。
- ❑ 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。
一元加操作符与Number()函数遵循相同的转换规则 Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数
- parseInt(),将字符串转换为数值。有第二个参数,表示进制
- 需要得到整数时有限考虑parseInt()函数。更专注于字符串是否包含数值模式。
- 字符串前面的空格会忽略
- 第一个非空字符开始转换,不是数值,加减号立即NaN。空字符串也会返回NaN
- 遇到非数值字符或者到了末尾
- 能识别不同的整数格式。十进制八进制十六进制(八进制: 0 或 0o 或 0O 十六进制: 0x 或 0X 二进制: 0b 或者 0B)
- 提供了十六进制参数16,那么字符串前面的"0x"可以省掉.建议始终传给它第二个参数
- parseFloat()将字符串转换为数值。跟parseInt类似
- 解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略
- parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零
- parseFloat()只解析十进制值,因此不能指定底数
- 字符串表示整数(没有小数点或者小数点后面只有一个零),则parseFloat()返回整数
String类型
String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)标示
- 字符串字面量
字面量(literal)是用于表达源代码中一个固定值的表示法(notation)
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符
这些字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释 转义序列表示一个字符,所以只算一个字符
有length属性
字符串中包含双字节字符,那么length属性返回的值可能不是准确的字符数
- 字符串的特点 ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量
let lang = 'java'
lang = lang + 'Script'
首先会分配一个足够容纳10个字符的空间,然后填充上"Java"和"Script"。最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了。所有处理都是在后台发生的
3 转换为字符串 toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物。 toString()方法可见于数值、布尔值、对象和字符串值
null和undefined值没有toString()方法
对于数值有底数参数,默认十进制,可得到二进制,八进制,十六进制或者其他有效基础的字符串。
不确定一个值是不是null或undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串
- ❑ 如果值有toString()方法,则调用该方法(不传参数)并返回结果。
- ❑ 如果值是null,返回"null"。
- ❑ 如果值是undefined,返回"undefined"。
加号操作符给一个值加上一个空字符串""也可以将其转换为字符串
- 模板字符串 可保留换行符,跨行定义字符串 在定义模板时特别有用。比如HTML模板 会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模板字符串看起来可能会缩进不当
- 字符串插值 模板字面量最常用的一个特性是支持字符串插值。在一个连续定义中插入一个或多个值
技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串.模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值
字符串插值通过在${}中使用一个JavaScript表达式实现
所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript表达式都可以用于插值
嵌套的模板字符串无须转义
模板也可以插入自己之前的值
- 模板字面量标签函数 通过标签函数可以自定义插值行为
标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果 是一个常规函数,通过前缀到模板字面量来应用自定义行为
let a = 6
let b = 9
function simpleTag(strings, ...expressions) {
console.log(strings)
for(const expression of expressions) {
console.log(expression)
}
return 'foobar'
}
let untaggedResult = `${a} + ${b} = ${a + b}`
let taggedResult = simpleTag`${a} + ${b} = ${a + b}`
console.log(untaggedResult)
console.log(taggedResult)
// [ '', ' + ', ' = ', '' ]
// 6
// 9
// 15
// 6 + 9 = 15
// foobar
对于有n个插值的模板字面量,传给标签函数的表达式参数的个数始终是n(就是expressions的个数),而传给标签函数的第一个参数所包含的字符串个数则始终是n+1
let a = 6;
let b = 9;
function zipTag(strings, ...expressions) {
console.log('个数',expressions.length);
console.log(strings);
for (const expression of expressions) {
console.log(expression);
}
return (
strings[0] + expressions.map((e, i) => {
console.log(e, i, strings[i + 1])
return `${e}${strings[i + 1]}`
}).join("")
);
}
let untaggedResult = `${a} + ${b} = ${a + b}`;
let taggedResult = zipTag`${a} + ${b} = ${a + b}`;
console.log(untaggedResult);
console.log(taggedResult);
// 个数 3
// [ '', ' + ', ' = ', '' ]
// 6
// 9
// 15
// 6 0 +
// 9 1 =
// 15 2
// 6 + 9 = 15
// 6 + 9 = 15
- 原始字符串 使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或Unicode字符),而不是被转换后的字符表示。为此,可以使用默认的String.raw标签函数
console.log(`first line \n second line`)
console.log(String.raw`first line \n second line`)
first line
second line
first line \n second line
对实际的换行符不行。比如:
a b这种,不会被转换成转义序列的形式。
也可以通过标签函数的第一个参数,即字符串数组的.raw属性取得每个字符串的原始内容
function printRaw(strings) {
console.log("actual characters");
for (const string of strings) {
console.log(string);
}
console.log("excaped characters");
for (const string of strings.raw) {
console.log(string);
}
}
printRaw`\u00A9${"and"}\n`;
// actual characters
// ©
// excaped characters
// \u00A9
// \n
Symbol类型
Symbol(符号)是ECMAScript 6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险 符号并不是为了提供私有属性的行为才增加的 符号就是用来创建唯一记号,进而用作非字符串形式的对象属性
- 符号的基本用法 符号需要使用Symbol()函数初始化
调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关 符号没有字面量语法,这也是它们发挥作用的关键
只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性 Symbol()函数不能与new关键字一起作为构造函数使用。避免创建符号包装对象,像使用Boolean、String或Number那样
借用Object()函数,使用符号包装对象
- 使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。需要使用Symbol.for()方法
Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例
const foo = Symbol.for('foo')
let another = Symbol.for('foo')
log(another === foo) // true
采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串
注册表中使用的键同时也会被用作符号描述。
Symbol.keyFor() 可查询全局注册表,接收符号,返回对应的字符串键,不是全局符号,返回undefined。不是符号,则报错TypeError
- 使用符号作为属性 凡是可以使用字符串或数值作为属性的地方,都可以使用符号
包括了对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性 对象字面量只能在计算属性语法中使用符号作为属性
const s1 = Symbol('foo')
const s2 = Symbol('bar')
const s3 = Symbol('baz')
const s4 = Symbol('qux')
let o = {
[s1]: 'foo val',
s1: 's1'
}
log(o[s1])
log(o.s1)
log(o)
Object.defineProperties(o, {
[s3]: {value: 'baz val'},
[s4]: {value: 'qux val'},
})
log(o)
类似于Object.getOwnPropertyNames()返回对象实例的常规属性数组 Object.getOwnPropertySymbols()返回对象实例的符号属性数组 彼此互斥
Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象
Reflect.ownKeys()会返回两种类型的键
const s1 = Symbol('foo')
const s2 = Symbol('bar')
let o = {
[s1]: 'foo',
[s2]: 'bar',
baz: 'baz',
qux: 'qux'
}
console.log('哈哈1', Object.getOwnPropertySymbols(o))
console.log('哈哈12', Object.getOwnPropertyNames(o))
console.log('哈哈13', Object.getOwnPropertyDescriptors(o))
console.log('哈哈14', Reflect.ownKeys(o))
哈哈1 [ Symbol(foo), Symbol(bar) ]
哈哈12 [ 'baz', 'qux' ]
哈哈13 {
baz: {
value: 'baz',
writable: true,
enumerable: true,
configurable: true
},
qux: {
value: 'qux',
writable: true,
enumerable: true,
configurable: true
},
[Symbol(foo)]: {
value: 'foo',
writable: true,
enumerable: true,
configurable: true
},
[Symbol(bar)]: {
value: 'bar',
writable: true,
enumerable: true,
configurable: true
}
}
哈哈14 [ 'baz', 'qux', Symbol(foo), Symbol(bar) ]
符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失 如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键
这句话的意思就是说没有定义一个变量去声明符号。就无法拿到。如下:
let o = {
[Symbol('foo')]: 'foo',
[Symbol('bar')]: 'bar',
}
console.log(o[Symbol('foo')]) // undefined
拿不到值。必须要遍历才行:
let o = {
[Symbol('foo')]: 'foo',
[Symbol('bar')]: 'bar',
}
console.log(o[Symbol('foo')])
let bar = Object.getOwnPropertySymbols(o).find(symbol => symbol.toString().match(/bar/))
console.log(bar)
- 常用的内置符号 ECMAScript 6也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为.以Symbol工厂函数字符串属性的形式存在. 最重要的用途之一是重新定义它们,从而改变原生结构的行为。
我们知道for-of循环会在相关对象上使用Symbol.iterator属性,那么就可以通过在自定义对象上重新定义Symbol.iterator的值,来改变for-of在迭代该对象时的行为 这些内置符号也没有什么特别之处,它们就是全局函数Symbol的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的
const s = () => {}
s.a = Symbol('foo')
const obj = {
[s.a]: function() {
console.log('aaa')
}
}
obj[s.a] = () => {
console.log('bbb')
}
console.log(obj[s.a]())
在提到ECMAScript规范时,经常会引用符号在规范中的名称,前缀为@@。比如,@@iterator指的就是Symbol.iterator。
- Symbol.asyncIterator 一个方法,该方法返回对象默认的AsyncIterator。由for-await-of语句使用.实现异步迭代器API的函数。 for-await-of循环会利用这个函数执行异步迭代操作。
循环时,它们会调用以Symbol.asyncIterator为键的函数,并期望这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象是实现该API的AsyncGenerator
技术上,这个由Symbol.asyncIterator函数生成的对象应该通过其next()方法陆续返回Promise实例。可以通过显式地调用next()方法返回,也可以隐式地通过异步生成器函数返回:
class Emitter {
constructor(max) {
this.max = max
this.asyncIdx = 0
}
async * [Symbol.asyncIterator]() {
while(this.asyncIdx < this.max) {
yield new Promise((resolve) => resolve(this.asyncIdx++))
}
}
}
async function asyncCount() {
let emitter = new Emitter(5)
for await(const x of emitter) {
console.log(x)
}
}
asyncCount()
- Symbol.hasInstance 一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用
instanceof操作符可以用来确定一个对象实例的原型链上是否有原型
原型:原型是function/函数对象(包括[构造函数])的一个属性,它定义了函数构造出的对象的公共祖先。通过该函数构造出的对象,可以继承该原型的属性和方法。原型也是对象
原型链:通过函数创建出来的对象,默认会有一个__proto__属性指向它继承的构造函数的prototype,当使用这个对象不存在的属性或方法时,这个对象会通过__proto__向上查找它继承的原型,如果没有,再通过__proto__向上查找,一直找到Object的null,这样形成一条有终点的链条就称为原型链
在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。以Symbol.hasInstance为键的函数会执行同样的操作,只是操作数对调了一下
function Foo() {}
const f = new Foo()
console.log(Foo[Symbol.hasInstance](f))
这个属性定义在Function的原型上,因此默认在所有函数和类上都可以调用
由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数
class Bar {}
class Baz extends Bar {
static[Symbol.hasInstance]() {
return false
}
}
let b = new Baz()
console.log(Bar[Symbol.hasInstance](b))
console.log(Baz[Symbol.hasInstance](b))
- Symbol.isConcatSpreadable
一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素
内置的**Symbol.isConcatSpreadable**符号用于配置某对象作为[Array.prototype.concat()]方法的参数时是否展开其数组元素。
Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例.覆盖Symbol.isConcatSpreadable的值可以修改这个行为。
const arr = {
length:2,
0:'a',
1:'b',
}
console.log(arr[Symbol.isConcatSpreadable]) // undefined
arr[Symbol.isConcatSpreadable] = true
const b = [1]
const c = b.concat(arr)
console.log(c) // [1,'a','b']
- Symbol.iterator 一个方法,该方法返回对象默认的迭代器。由for-of语句使用 实现迭代器API的函数
for-of循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以Symbol.iterator为键的函数,并默认这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象是实现该API的Generator:
function log() {
console.log(...arguments);
}
class Foo {
* [Symbol.iterator]() {}
}
let f = new Foo()
log(f[Symbol.iterator]())
技术上,这个由Symbol.iterator函数生成的对象应该通过其next()方法陆续返回值。可以通过显式地调用next()方法返回,也可以隐式地通过生成器函数返回
class Emitter {
constructor(max) {
this.max = max
this.idx = 0
}
*[Symbol.iterator]() {
while(this.idx < this.max) {
yield this.idx++
}
}
}
function count() {
let emitter = new Emitter(5)
for (const x of emitter) {
console.log(x)
}
}
count() // 0 1 2 3 4
- Symbol.match
一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用
String.prototype.match()方法会使用以Symbol.match为键的函数来对正则表达式求值
正则表达式的原型上默认有这个函数(以Symbol.match为键的函数)的定义,因此所有正则表达式实例默认是这个String方法的有效参数
这句话的意思是,String.prototype.match()方法传入正则表达式之后,因为正则表达式原型上有这个以Symbol.match为键的函数,所以就能调用这个Symbol.match为键的函数,所以所有正则表达式实例默认是这个String方法的有效参数
给这个方法传入非正则表达式值会导致该值被转换为RegExp对象。如果想改变这种行为,让方法直接使用参数,则可以重新定义Symbol.match函数以取代默认对正则表达式求值的行为,从而让match()方法使用非正则表达式实例
Symbol.match函数接收一个参数,就是调用match()方法的字符串实例。返回的值没有限制
class FooMatchers {
static[Symbol.match](target) {
return target.includes('foo')
}
}
console.log('foobar'.match(FooMatchers))
console.log('barbaz'.match(FooMatchers))
class StringMatcher {
constructor(str) {
this.str = str
}
[Symbol.match](target) {
return target.includes(this.str)
}
}
console.log('foobar'.match(new StringMatcher('foo')))
console.log('barbaz'.match(new StringMatcher('qux')))
- mdn例子
下面代码会抛出一个 [
TypeError]:
"/bar/".startsWith(/bar/);
// Throws TypeError,因为 /bar/ 是一个正则表达式
// 且 Symbol.match 没有修改。
但是,如果你将 Symbol.match 置为 false,使用 match 属性的表达式检查会认为该象不是正则表达式对象。startsWith 和 endsWith 方法将不会抛出 TypeError。
var re = /foo/;
re[Symbol.match] = false;
"/foo/".startsWith(re); // true
"/baz/".endsWith(re); // false
10 Symbol.replace 一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()方法使用
String.prototype.replace()方法会使用以Symbol.replace为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
给这个方法传入非正则表达式值会导致该值被转换为RegExp对象。如果想改变这种行为,让方法直接使用参数,可以重新定义Symbol.replace函数以取代默认对正则表达式求值的行为,从而让replace()方法使用非正则表达式实例。Symbol.replace函数接收两个参数,即调用replace()方法的字符串实例和替换字符串
class FooReplacer {
static[Symbol.replace](target, replacement) {
return target.split('foo').join(replacement)
}
}
console.log('barfoobaz'.replace(FooReplacer, 'qux'))
class StringReplacer {
constructor(str) {
this.str = str
}
[Symbol.replace](target, replacement) {
return target.split(this.str).join(replacement)
}
}
console.log('barfoobaz'.replace(new StringReplacer('foo'), 'qux'))
- Symbol.search 一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由String.prototype.search()方法使用
String.prototype.search()方法会使用以Symbol.search为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
给这个方法传入非正则表达式值会导致该值被转换为RegExp对象。如果想改变这种行为,让方法直接使用参数,可以重新定义Symbol.search函数以取代默认对正则表达式求值的行为,从而让search()方法使用非正则表达式实例。Symbol.search函数接收一个参数,就是调用match()方法的字符串实例
class FooSearcher {
static[Symbol.search](target) {
return target.indexOf('foo')
}
}
console.log('foobar'.search(FooSearcher))
console.log('barfoo'.search(FooSearcher))
console.log('barbaz'.search(FooSearcher))
class StringSearcher {
constructor(str) {
this.str = str
}
[Symbol.search](target) {
return target.indexOf(this.str)
}
}
console.log('foobar'.search(new StringSearcher('foo')))
console.log('barfoo'.search(new StringSearcher('foo')))
console.log('barbaz'.search(new StringSearcher('qux')))
- Symbol.species
一个函数值,该函数作为创建派生对象的构造函数
这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方法。用Symbol.species定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义
class Baz extends Array {
static get[Symbol.species]() {
return Array
}
}
let baz = new Baz()
baz = baz.concat('baz')
console.log(baz)
console.log(baz instanceof Baz) // false
- Symbol.split
一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由String.prototype.split()方法使用
String.prototype. split()方法会使用以Symbol.split为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
给这个方法传入非正则表达式值会导致该值被转换为RegExp对象。如果想改变这种行为,让方法直接使用参数,可以重新定义Symbol.split函数以取代默认对正则表达式求值的行为,从而让split()方法使用非正则表达式实例。Symbol.split函数接收一个参数,就是调用match()方法的字符串实例。返回的值没有限制
class FooSplitter {
static[Symbol.split](target) {
return target.split('foo')
}
}
console.log('barfoobaz'.split(FooSplitter))
class StringSplitter {
constructor(str) {
this.str = str
}
[Symbol.split](target) {
return target.split(this.str)
}
}
console.log('barfoobaz'.split(new StringSplitter('foo')))
14.Symbol.toPrimitive
一个方法,该方法将对象转换为相应的原始值.由ToPrimitive抽象操作使用
很多内置操作都会尝试强制将对象转换为原始值,包括字符串、数值和未指定的原始类型。对于一个自定义对象实例,通过在这个实例的Symbol.toPrimitive属性上定义一个函数可以改变默认行为
15.Symbol.toStringTag
一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法Object.prototype.toString()使用
通过toString()方法获取对象标识时,会检索由Symbol.toStringTag指定的实例标识符,默认为"Object"。内置类型已经指定了这个值,但自定义类实例还需要明确定义
let s = new Set();
console.log(s.toString());
console.log(s[Symbol.toStringTag]); // Set
class Foo {}
let foo = new Foo();
foo[Symbol.toStringTag] = "Foo";
console.log(foo.toString()); // [object Foo]
console.log(foo[Symbol.toStringTag]); // Foo
class Bar {
constructor() {
this[Symbol.toStringTag] = "Bar";
}
}
let bar = new Bar();
console.log(bar.toString()); // [object Bar]
- Symbol.unscopables
一个对象,该对象所有的以及继承的属性,都会从关联对象的with环境绑定中排除
设置这个符号并让其映射对应属性的键值为true,就可以阻止该属性出现在with环境绑定中
注意不推荐使用with,因此也不推荐使用Symbol.unscopables
Object 类型
ECMAScript中的对象其实就是一组数据和功能的集合 对象通过new操作符后跟对象类型的名称来创建 可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法
给构造函数提供参数时使用括号。如果没有参数,如上面的例子所示,那么完全可以省略括号(不推荐)
ECMAScript中的Object也是派生其他对象的基类。Object类型的所有属性和方法在派生的对象上同样存在。
每个Object实例都有如下属性和方法:
- constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
- hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty("name"))或符号。
- isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。(第8章将详细介绍原型。)
- propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用(本章稍后讨论的)for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
- toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
- toString():返回对象的字符串表示。
- valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。
因为在ECMAScript中Object是所有对象的基类,所以任何对象都有这些属性和方法
严格来讲,ECMA-262中对象的行为不一定适合JavaScript中的其他对象。比如浏览器环境中的BOM和DOM对象,都是由宿主环境定义和提供的宿主对象。而宿主对象不受ECMA-262约束,所以它们可能会也可能不会继承Object
操作符
用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等
可用于各种值,包括字符串、数值、布尔值,甚至还有对象
应用给对象时,操作符通常会调用valueOf()和/或toString()方法来取得可以计算的值
1.1 只操作一个值的操作符叫一元操作符
- 递增 和递减 前缀版和后缀版
这两个的主要区别: 前缀有副作用,后缀没有副作用。就是说前缀进行当前运算的时候马上就会用+1或者-1后的值,而后缀还是用用当前值进行当前运算,下次运算才会用+1或者-1的值去运算。
这四个操作符可用于任何值,字符串,布尔值,浮点值,对象
-
对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
-
对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值。
-
对于布尔值,如果是false,则转换为0再应用改变(true转换为1)。变量类型从布尔值变成数值。
-
对于浮点值,加1或减1。
-
如果是对象,则调用其(第5章会详细介绍的)valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。
-
一元加和减
一元加应用到非数值,则会执行与使用Number()转型函数一样的类型转换
应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值
用于基本的算术,但也可以像上面的例子那样,用于数据类型转换
1.2 位操作符
- 数值的底层操作,操作内存中表示数据的比特(位)。
- es所有数值以IEEE 754 64位格式存储,位操作不直接应用到64位表示。而是先把值转换为32位整数,再进行位操作,之后再把结果转换位64位。对开发者而言只需要考虑32位整数即可。
- 前31位表示整数值,32位表示符号,如0正,1负。 待写...
1.3 布尔操作符 逻辑非、逻辑与和逻辑或
-
! 逻辑非:首先将操作数转换为布尔值,再对其取反。
-
如果操作数是对象,则返回false。
-
如果操作数是空字符串,则返回true。
-
如果操作数是非空字符串,则返回false。
-
如果操作数是数值0,则返回true。
-
如果操作数是非0数值(包括Infinity),则返回false。
-
如果操作数是null,则返回true。
-
如果操作数是NaN,则返回true。
-
如果操作数是undefined,则返回true
逻辑非可以转换任意值为布尔值。!! 相当于调用了转型函数Boolean()
-
-
逻辑与
- &&: 应用到两个值,只有同时为真返回真,其余返回假
- 可用于任何操作数。有操作数不是布尔值时,遵循如下规则:
- 短路操作符:第一个操作数决定了结果,永远不会对第二个操作数求值。第一个为false,无论第二个是什么,结果不可能为true。
let a = true const b = a && c console.log(b) // 报错c未定义 // a = false时不会报错
-
逻辑或
- || : 有一个false就为false
- 第一个操作数求值为true,第二个操作数就不会再被求值
利用这个特性可以避免给变量赋值null或者undefined。主要用来备用很方便。 '' 0 NaN null undefined false 这几种会转换false。
1.4 乘性操作符 处理数值时包含一些自动的类型转换。处理非数值会用Number()自动转换。 '' 转换0,true 转为1
- 乘法
- ❑ 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果ECMAScript不能表示乘积,则返回Infinity或-Infinity。
- ❑ 如果有任一操作数是NaN,则返回NaN。
- ❑ 如果是Infinity乘以0,则返回NaN。
- ❑ 如果是Infinity乘以非0的有限数值,则根据第二个操作数的符号返回Infinity或-Infinity。
- ❑ 如果是Infinity乘以Infinity,则返回Infinity。
- ❑ 如果有不是数值的操作数,则先在后台用Number()将其转换为数值,然后再应用上述规则。
- 除法
- ❑ 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果ECMAScript不能表示商,则返回Infinity或-Infinity。❑ 如果有任一操作数是NaN,则返回NaN。
- ❑ 如果是Infinity除以Infinity,则返回NaN。
- ❑ 如果是0除以0,则返回NaN。
- ❑ 如果是非0的有限值除以0,则根据第一个操作数的符号返回Infinity或-Infinity。
- ❑ 如果是Infinity除以任何数值,则根据第二个操作数的符号返回Infinity或-Infinity。
- ❑ 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。
- 取模
取余数。% 表示
- ❑ 如果操作数是数值,则执行常规除法运算,返回余数。
- ❑ 如果被除数是无限值,除数是有限值,则返回NaN。
- ❑ 如果被除数是有限值,除数是0,则返回NaN。
- ❑ 如果是Infinity除以Infinity,则返回NaN。
- ❑ 如果被除数是有限值,除数是无限值,则返回被除数。
- ❑ 如果被除数是0,除数不是0,则返回0。
- ❑ 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。 1.5 指数操作符 Math.pow() 有了自己的操作符: **
Math.pow(4.3) ==> 4**3
- 指数赋值操作符**=
let c = 3
c **= 3
let d = 16
d **= 0.5
console.log(c) // 27
console.log(d) // 4
1.6 加性操作符 加法: 也会发生一些不同数据类型的转换。
-
都是数值类型时:
- ❑ 如果有任一操作数是NaN,则返回NaN;
- ❑ 如果是Infinity加Infinity,则返回Infinity;
- ❑ 如果是-Infinity加-Infinity,则返回-Infinity;
- ❑ 如果是Infinity加-Infinity,则返回NaN;
- ❑ 如果是+0加+0,则返回+0;
- ❑ 如果是-0加+0,则返回+0;
- ❑ 如果是-0加-0,则返回-0。
-
有一个操作数时字符串时:
- ❑ 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
- ❑ 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
-
如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则。对于undefined和null,则调用String()函数,分别获取"undefined"和"null"
undefined 隐式转换为NaN,所以:1 + undefined = NaN。NUll + 1 = 1;但是'1' + undefined = 1undefined。
减法:
- ❑ 如果两个操作数都是数值,则执行数学减法运算并返回结果。
- ❑ 如果有任一操作数是NaN,则返回NaN。
- ❑ 如果是Infinity减Infinity,则返回NaN。
- ❑ 如果是-Infinity减-Infinity,则返回NaN。
- ❑ 如果是Infinity减-Infinity,则返回Infinity。
- ❑ 如果是-Infinity减Infinity,则返回-Infinity。
- ❑ 如果是+0减+0,则返回+0。
- ❑ 如果是+0减-0,则返回-0。
- ❑ 如果是-0减-0,则返回+0。
- ❑ 如果有任一操作数是字符串、布尔值、null或undefined,则先在后台使用Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是NaN,则减法计算的结果是NaN。
- ❑ 如果有任一操作数是对象,则调用其valueOf()方法取得表示它的数值。如果该值是NaN,则减法计算的结果是NaN。如果对象没有valueOf()方法,则调用其toString()方法,然后再将得到的字符串转换为数值。
1.7 关系操作符 小于(<)、大于(>)、小于等于(<=)和大于等于(>=) 返回布尔值,和数学课上一样。 规则:
- ❑ 如果操作数都是数值,则执行数值比较。
- ❑ 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
- ❑ 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
- ❑ 如果有任一操作数是对象,则调用其valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较。
- ❑ 如果有任一操作数是布尔值,则将其转换为数值再执行比较。
比较两个字符串时,对字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值,大写字母的编码都小于小写字母的编码.把两者都转换为相同的大小写形式(全大写或全小写),然后再比较 比较两个字符串的数字时,是按开头数字比较。其中一个为数值类型时,就按大小比较(只要是数值和字符串比较,字符串就会先被转换为数值,然后进行数值比较) 'a' < 3;结果时false,因为字符"a"不能转换成任何有意义的数值,所以只能转换为NaN。这里有一个规则,即任何关系操作符在涉及比较NaN时都返回false。
NaN<3 false;Nan > 3 false;
在大多数比较的场景中,如果一个值不小于另一个值,那就一定大于或等于它。但在比较NaN时,无论是小于还是大于等于,比较的结果都会返回false
1.8 相等操作符 比较字符串、数值和布尔值是否相等时,过程都很直观
比较两个对象是否相等时,情形就比较复杂
-
第一组是等于(==)和不等于(!=),它们在比较之前执行转换。
- 两个等于号(==)表示,如果操作数相等,则会返回true。
- 先进行类型转换(通常称为强制类型转换)再确定操作数是否相等
- ❑ 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false转换为0, true转换为1。
- ❑ 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
- ❑ 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较。在进行比较时,这两个操作符会遵循如下规则。
- ❑ null和undefined相等。
- ❑ null和undefined不能转换为其他类型的值再进行比较。
- ❑ 如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。记住:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN。
- ❑ 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两者不相等。
-
第二组是全等(===)和不全等(!==),它们在比较之前不执行转换。 两个操作数在不转换的前提下相等才返回true/不相等才返回true
由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。这样有助于在代码中保持数据类型的完整性
1.9 条件操作符 三元表达式 boolean_expression ? true_value : false_value 1.10 赋值操作符 简答赋值: =
复合赋值:乘性,加性,位操作符跟 = 号。
- ❑ +=
- ❑ 乘后赋值(*=)
- ❑ 除后赋值(/=)
- ❑ 取模后赋值(%=)
- ❑ 加后赋值(+=)
- ❑ 减后赋值(-=)
- ❑ 左移后赋值(<<=)
- ❑ 右移后赋值(>>=)
- ❑ 无符号右移后赋值(>>>=)
1.11 逗号操作符 可以再一条语句中执行多个操作
let num1 = 1,num2 = 2, num3 = 3;
辅助赋值: let num = (5,1,4,8,0); // num 的值位0 使用场景不多
语句
也称为流控制语句。语法大部分体现再语句中。 语句通常使用一个或者多个关键字完成既定的任务。
-
js中 标识符,关键字,保留字,操作符/运算符,表达式,语句
- 表达式和语句区别:
表达式: 是由运算符构成,并运算产生结果的语法结构。每个表达式都会产生一个值,它可以放在任何需要一个值的地方, 语句则是由“;(分号)”分隔的句子或命令。如果在表达式后面加上一个“;”分隔符,这就被称为“表达式语句”。它表明“只有表达式,而没有其他语法元素的语句”。
- 一般的javascript中的语句分为下面几种:
- (1)声明语句:变量声明和函数声明
- (2)赋值语句
- (3)控制语句:能够对语句执行顺序产生改变,包括条件语句和循环语句,当然还有比较特殊的标签语句。
- (4)表达式语句:这些语句去掉最后分号,都也可当表达式用的。常见的有:对象操作(new、delete)、函数调用(函数执行,必有返回值)等。
var num = 9; //声明、赋值语句
vloop: //标签语句
{ //其实这里大括号可以不需要的,在这里我只想向大家展示一下这种代码块结构而已
for(var i=1; i<10; i++) { //循环语句
if(i==num){ //条件语句
break vloop;
}else{
num = num - 1;
}
}
}
console.log(num); //表达式语句,输出:5
由上面可以看出,表达式和语句还是存在很大区别的,可也说表达式是语句的构成部分,而语句则又可以组成可执行代码块。一般而已,我们都可以很直观的看出两者的区别,但是,一些特殊情况就不太好区别。
if 语句
do-while语句
后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。 循环体内的代码至少执行一次
注意 后测试循环经常用于这种情形:循环体内代码在退出前至少要执行一次。
let i = 2
do{
i += 2
}while(i<100)
console.log(i)
while语句
while语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while循环体内的代码有可能不会执行
for语句
无法通过while循环实现的逻辑,同样也无法使用for循环实现。因此for循环只是将循环相关的代码封装在了一起而已
使用let声明迭代器变量,这样就可以将这个变量的作用域限定在循环中
初始化、条件表达式和循环后表达式都不是必需的。因此,下面这种写法可以创建一个无穷循环:
for(; ;){doSomething()} // 无穷循环
如果只包含条件表达式,for循环实际就变成了while循环。
let count = 10;
let i = 0;
for (; i< count;){
console.log(i)
i++
}
for 语句多功能性,使用广泛。
3.6.5 for in 语句
严格迭代语句,枚举对象中非符号键属性。
for (property in expression) statement
for(const prop in window) {document.write(prop)}
const 不是必须的,确保局部变量不被修改。推荐使用const.
不能保证返回对象属性的顺序 for-in循环要迭代的变量是null或undefined,则不执行循环体。
注意是prop是属性,不是值
3.6.6 for-of 语句
for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素
for (const el of [2,4,5]){
document.write(el)
}
同样const不是必须的。 for-of循环会按照可迭代对象的next()方法产生值的顺序迭代元素。关于可迭代对象,本书将在第7章详细介绍。
增加了for-await-of循环,以支持生成期约(promise)的异步可迭代对象
3.6.7 标签语句
用于给语句加标签: label: statement
start:for(let i = 0;i<count;i++){
console.log(i)
}
start是标签,可通过break或continue语句引用。嵌套循环常用。
3.6.7 break 和continue 语句
break和continue语句为执行循环代码提供了更严格的控制手段。其中,break语句用于立即退出循环,强制执行循环后的下一条语句。而continue语句也用于立即退出循环,但会再次从循环顶部开始执行。
break和continue都可以与标签语句一起使用,返回代码中特定的位置。这通常是在嵌套循环中。
let num = 0;
mylabel:
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break mylabel;
}
num++;
}
}
console.log(num); // 55
let num = 0;
mylabel:
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
continue mylabel;
}
num++;
}
}
console.log(num); // 95
3.6.9 with语句
将代码作用域设置为特定对象。
with (expression) statement;
with(location) {
let qs = search.substring(1)
let hostName = hostname
let url = href
}
with语句用于连接location对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。如果没有找到该局部变量,则会搜索location对象,看它是否有一个同名的属性。如果有,则该变量会被求值为location对象的属性
严格模式不允许使用with语句,否则会抛出错误。警告 由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句。
### 3.6.10 switch语句
```js
switch (expression) {
case value1:
statement
break;
case value2:
statement
break;
default:
statement
}
这里的每个case(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”break关键字会导致代码执行跳出switch语句。如果没有break,则代码会继续匹配下一个条件。default关键字用于在任何条件都没有满足时指定默认执行的语句(相当于else语句)
最好给每个条件后面都加上break语句
如果确实需要连续匹配几个条件,那么推荐写个注释表明是故意忽略了break
switch(i) {
case 25:
// 跳过
case 35:
console.log('25 or 35')
break;
default:
console.log('other')
}
首先,switch语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象。其次,条件的值不需要是常量,也可以是变量或表达式
switch('hello world') {
case 'hello' + 'world':
console.log('greeting was found')
break;
case 'goodbay'
...
let num = 25
switch(true) {
case num < 0:
console.log('less than 0')
break;
case num >= 0 && num <= 10:
console.log('between 0 and 10')
...
}
switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值10)
3.7 函数
核心组件。可封装语句,在任何地方,时间执行。
函数不需要指定是否返回值 除了return语句之外没有任何特殊声明表明该函数有返回值 碰到return语句,函数就会立即停止执行并退出 return语句也可以不带返回值。这时候,函数会立即停止执行并返回undefined。这种用法最常用于提前终止函数执行,并不是为了返回值
最佳实践是函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时
3.8 总结
JavaScript的核心语言特性在ECMA-262中以伪语言ECMAScript的形式来定义
- ❑ ECMAScript中的基本数据类型包括Undefined、Null、Boolean、Number、String和Symbol。
- ❑ 与其他语言不同,ECMAScript不区分整数和浮点值,只有Number一种数值数据类型。
- ❑ Object是一种复杂数据类型,它是这门语言中所有对象的基类。
- ❑ 严格模式为这门语言中某些容易出错的部分施加了限制。
- ❑ ECMAScript提供了C语言和类C语言中常见的很多基本操作符,包括数学操作符、布尔操作符、关系操作符、相等操作符和赋值操作符等。
- ❑ 这门语言中的流控制语句大多是从其他语言中借鉴而来的,比如if语句、for语句和switch语句等。ECMAScript中的函数与其他语言中的函数不一样。
- ❑ 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
- ❑ 不指定返回值的函数实际上会返回特殊值undefined。