JavaScript基础知识汇总【全!】

JavaScript 基础

  • JavaScript 运行在客户端的脚本语言,不需要编译,由js解释器(js引擎)逐行解释执行。Node.js也可以用于服务器端编程。
  • JavaScript组成: ECMAScript(JavaScript语法)、DOM(文档对象模型)访问HTML文档的所有元素、BOM(浏览器对象模型)它使JavaScript有能力与浏览器进行对话
  • **JavaScript的作用: **
    • 表单动态校验(密码强度检测)
    • 网页特效
    • 服务端开发(Node.js)
    • 桌面程序(Electron)、App(Cordova)、控制硬件-物联网(Ruff)、游戏开发(cocos2d-js)

1.javascript几种形式

  • JS有三种书写方式, 分别为行内、内嵌和外部。

行内式

<input type="button" value="请点击我" onclick="alert('Hello World')" />

内嵌式

<script>alert('你好, JavaScript')</script>

外部式

<script src="./js1.js" ></script>

2. 基础语法

2.1 结束符

  • JavaScript 程序的执行单位为行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句。
  • 语句以分号;结尾,一个分号就表示一个语句结束。多个语句可以写在一行内。
  • ;分号前面可以没有任何内容,JavaScript 引擎将其视为空语句。
  • 换行也标志着结束

2.2 注释

单行注释: //

多行注释: /*

......

*/

2.3 打印(输出内容)

输出到控制台: console.log() 输出到页面: document.write() 弹出: alert()(警告框)

// 几种弹框 alert() //警告框, 没有返回值 confirm() //确认框, 返回布尔值 prompt() //输入框, 点确定:返回用户输入的内容, 点取消:返回null

2.4 获取页面中的元素作为js对象

document.getElementById() # 返回对象(通常会称作元素对象) /* 元素对象与HTML元素存在映射关系, 元素对象用来描述某个HTML元素 HTML元素的属性, 会映射成元素对象的属性 举个例子: <script> document.write('<p id="demo">段落,独占一行</p>'); p = document.getElementById('demo'); p.innerHTML='行内标签'; </script> */

2.5 变量

  • Javascript 使用关键字var、let、const来定义变量
1) var 定义变量
var 变量名 = 值;
// var:关键字
// 变量名(标识符): 变量名由字母、数字、下划线和$组成且不能以*数字*开头,变量名不能使用关键字。中文是合法的标识符, 可以用作变量名。
// 声明变量而没给赋值, 默认是undefined, undefined 是一个特殊的值, 表示‘无意义’。
// 如果变量赋值的时候忘了写var 关键字, 这条语句也是有效的; 
// 但是不写var 不利于表达意图, 而且容易不知不觉的创建全局变量, 所以建议使用var声明变量。
// 如果一个变量没有声明就直接使用, JavaScript 会报错, 告诉你变量未定义: ReferenceError: x is not defined。

var a, b;
// var 关键字还可以同时声明多个变量

var a = 1;
a = "js";
// JavaScript 是一种动态类型语言, 也就是说, 变量的类型没有限制,变量可以随时更改类型。
// 变量a 先被赋值了一个数值, 后又被重新赋值为一盒字符串。第二次赋值的时候因为变量a 已经存在, 所以不需要使用var 关键字。
var a;
// 如果同一个变量被声明了两次, 第二次声明是无效的。
var a = 2;
// 但是如果第二次声明的时候还进行了赋值, 则会覆盖掉前面的值。

==*var 变量提升: ** 使用var关键字声明的变量会自动提升到函数作用域的顶部; 也就是把所有变量声明都拉到函数作用域的顶部==

标识符: 开发人员为变量属性函数参数名取的名字, 标识符不能是关键字或保留字 关键字: JS 本身已经用了的名字,不能再用这些充当变量名、方法名。 保留字: 就是预留的关键字, 还不是关键字未来可能会成为关键字, 同样不能使用它们当变量名或方法名。 关键字和保留字:

2) let 定义变量
  • let 跟var 的作用差不多, 但是有着非常重要的区别。最明显的区别是, ==let 声明的范围是块作用域, 而var 声明的范围是函数作用域==。
    if (true) {
        var name = "zy";
        console.log(name);      // zy    
    }
    console.log(name);          // zy
    // 换成let 定义变量
    if (true) {
        let age = 18;
        console.log(age);      // 18    
    }
    console.log(age);          // arn_js.js:5 Uncaught ReferenceError: age is not defined
    // 可以看到age 变量不能再if 块外部被引用, 以为它的作用域仅限于该块内部。
    // 块作用域是函数作用域的子集, 所以使用域var 的作用域限制同样也适用于let。
    
    var name;
    var name;
    
    var age;
    let age;     // ntaxError: Identifier 'age' has already been declared
    // let 也不允许同一个作用域中出现出现冗余声明, 会导致报错;
    // Javascript 引擎会记录用于变量声明的标识符及其所在的块作用域, 所以嵌套使用相同的标识符不会报错, 因为在同一个块中没有重复声明。
    // 对声明冗余报错不会因混用let 和var 而受影响; 这两个关键字声明的并不是不同类型的变量, 他们只是指出变量在相关作用域如何存在。
    

==*let变量提升:**let 与var 的另一个区别就越是let 声明的变量不会在作用域中被提升==。

3) const定义常量
  • conset 与let 基本相同, 唯一一个重要的区别是用它声明变量时必须同时初始化变量, 且尝试修改const 声明的变量会导致运行时错误。
const name = "dada";
name = "da";        // typeError: Assignment to constant variable
// const 也不允许重复声明, 声明的作用域也是块。
// const 声明的限制只适用于它指向的变量的引用;换句话说, 如果const 变量引用的是一个对象, 那么修改这个对象内部的属性并不违反const的限制。

2.6 区块

  • Javascript 使用大括号, 将多个相关的语句组合在一起, 称为“区块”(block)。
  • 对于 var 关键字来说, Javascript 的区块不构成单独的作用域(scope)。

2.7 条件语句

  • Javascript 提供if结构和switch结构, 完成条件判断, 即只有满足预设的条件才会执行相应的语句。
1) if 结构
  • if结构先判断一个表达式的布尔值, 然后根据布尔值的真伪, 执行不同的语句。布尔值: true、false
2) if...else 结构
  • if代码块后面,还可以跟一个else代码块, 表示不满足条件时, 所要执行的代码。
3) switch 结构
  • 多个if...else连在一起使用的时候, 可以转为更方便的 switch结构。
4) 三元运算符[ ?: ]
  • Javascript 还有一个三元运算符(即该运算符需要三个运算子)?:, 也可以用于逻辑判断。 (条件) ? 表达式1 : 表达式2

2.8 循环语句

  • 循环语句用于重复执行某个操作, 它有多种形式。
1) while 循环
  • while语句包括一个循环条件和一段代码块, 只要条件为真, 就不断循环执行代码块。
2) for 循环
  • for语句是循环命令的另一种形式, 可以指定循环的起点、终点和终止条件。
3) do...while 循环
  • do...while循环与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。
4) break、continue 语句
  • break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行。

  • break语句用于跳出代码块或循环。

     var i = 0; while(i < 100) { console.log('i 当前为:' + i); i++; if (i === 10) break; } // 上面代码只会执行10次循环, 一旦i等于10, 就会跳出循环 // for循环也可以使用break语句跳出循环。 for (var i = 0; i < 5; i++) { console.log(i); if (i === 3) break; } 
  • continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。

     var i = 0; while (i < 100){ i++; if (i % 2 === 0) continue; console.log('i 当前为:' + i); } // 上面代码只有在i为奇数时, 才会输出i的值。如果i为偶数, 则直接进入下一轮循环。 
  • 如果存在多重循环,不带参数的break 语句和continue语句都只针对最内层循环。

2.9 代码规范:

1) 标识符命名规范
  • 变量、函数的命名必须要有意义
  • 变量的名称一般用名词
  • 函数的名称一般用动词
2) 单行注释规范
  • // 单行注释前面注意有个空格
3) 其它规范
  • 关键词 操作符空格

3. 数据类型

  • JavaScript 语言的每一个值, 都属于某一种数据类型。Javascript 有以下几种数据类型:
  • undefined(未定义): 表示"未定义" 或不存在, 由于没有定义, 所以此处暂时没有任何值; 默认值 "undefined"
  • boolean(布尔): 表示真(true)和假()的两个特殊的值; 默认值 false
  • string(字符串): 文本; 默认值 " "
  • number(数字): 整数和小数; 默认值 0
  • object(对象): 各种值组成的集合
  • array(数组): 数组
  • function(函数): 函数
  • symbol(符号): 符号类型
  • null(空值): 即此处的值为空, 默认值 null】 其中Null 和undefined 都是被动产生

3.1 typeof 运算符

  • typeof运算符可以返回一个值的数据类型。

3.2 null 、undefined 和 NaN

  • nullundefined都可以表示“没有”, 含义非常相似; 将一个变量赋值为undefinednull, 语法效果几乎没区别。

  • null表示空值, 即该处的值现在为空。调用函数时, 某个参数未设置任何值, 这时就可以传入null, 表示该参数为空。

  • if语句中,它们都会被自动转为false, 相等运算符(==)甚至直接报告两者相等

3.3 Boolean 布尔值

  • 布尔值往往用于程序流程的控制

    // 注意, 空数组([])和空对象({})对应的布尔值, 都是true if ([]) { console.log('true'); // true } if ({}) { console.log('true'); // true }

3.4 Number 数字

1) 整数和浮点数
  • Javascript 内部所有数字都是以64为浮点数形式存储, 即使整数也是如此。

    // 1 与 1.0 是相同的, 是同一个数 1 === 1.0 // true // Javascript 语言的底层根本没有整数, 所有数字都是小数(64位浮点数)。 // 容易造成混淆的是, 某些运算只有整数才能完成, 此时Javascript 会自动把64位浮点数, 转成32位整数然后再进行运算。 // 注意浮点数不是精确的值, 所以涉及小数的比较和运算要特别小心。 0.1 + 0.2 === 0.3; // false 0.3 / 0.1; // 2.9999999999999996 (0.3 - 0.2) === (0.2 - 0.1) // flase
2) 数值精度
  • 根据国际标准 IEEE 754, JavaScript 浮点数的64个二进制位, 从最左边开始, 是这样组成的。

    • 第1位:符号位, 0表示正数, 1表示负数

    • 第2位到第12位(共11位):指数部分

    • 第13位到第64位(共52位):小数部分(即有效数字)

      ==符号位决定了一个数的正负, 指数部分决定了数值的大小, 小数部分决定了数值的精度。==

  • 指数部分一共有11个二进制位, 因此大小范围就是0到2047。

  • IEEE 754 规定, 如果指数部分的值在0到2047之间(不含两个端点), 那么有效数字的第一位默认总是1, 不保存在64位浮点数之中。

  • 也就是说, 有效数字这时总是1.xx...xx的形式,其中xx..xx的部分保存在64位浮点数之中, 最长可能为52位。因此, JavaScript 提供的有效数字最长为53个二进制位。

3) 数值范围
  • 64位浮点数的指数部分的长度是11个二进制位, 意味着指数部分的最大值是2047(2的11次方减1)。

  • 也就是说, 64位浮点数的指数部分的值最大为2047, 分出一半表示负数, 则 JavaScript 能够表示的数值范围为21024到2-1023(开区间), 超出这个范围的数无法表。

    // 如果一个数大于等于2的1024次方, 那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数, 这时就会返回`Infinity` Math.pow(2, 1024); // Infinity // 如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位), 那么就会发生为“负向溢出”, 即Javascript 无法表示这么小的数, 这时会直接返回0。 Math.pow(2, -1076); // 0 // 例子: var x = 0.5; for(var i = 0; i < 25; i++) { x = x * x; // 0.5连续做25次平方, 由于最后结果太接近0, 超出了可表示的范围Javascript 就直接将其转为 0。 }
    
  • Javascript 提供Number对象的MAX_VALUEMIN_VALUE属性, 返回可以表示的具体的最大值和最小值。

    Number.MAX_VALUE // 1.7976931348623157e+308 Number.MIN_VALUE // 5e-324
    
5) 数值表示法
  • 数值也可以采用科学计数法表示, 科学计数法允许字母eE的后面跟着一个整数, 表示这个数值的指数部分。

6) 数值的进制
  • 使用字面量直接表示一个数值时, Javascript 对整数提供四种进制的表示方法: 十进制、二进制、八进制和十六进制。

    • 十进制:没有前缀0的数值。
    • 八进制:有前缀0o0O的数值, 或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
    • 十六进制:有前缀0x0X的数值。
    • 二进制:有前缀0b0B的数值。
    // Javascript 内部会自动将八进制、十六进制、二进制转为十进制。 0xff // 255 0o377 // 255 0b11 // 3 // 如果八进制、十六进制、二进制的数值里面, 出现不属于该进制的数字就会报错 0xzz // 报错 0o88 // 报错 0b22 // 报错 // 十六进制出现了字母z、八进制出现数字8、二进制出现数字2, 因此报错。 // 有前导0的数值会被视为八进制, 但是如果前导0后面有数字8和9, 则该数值被视为十进制。 0888 // 888 0777 // 511 // 处理时很容易造成混乱。ES5 的严格模式和ES6已经废除了这种表示法, 但是浏览器为了兼容以前的代码, 目前还继续支持这种表示法。
    
7) 正零和负零
  • Javascript 的64位浮点数之中, 有一个二进制位是符号位。这意味着, 任何一个数都有一个对应的负值, 就连0也不例外。

    // Javascript 内部实际上存在2个0:一个是+0, 一个是-0, 区别就是64位浮点数表示法的符号位不同。它们是等价的。 -0 === +0 // true 0 === -0 // true 0 === +0 // true // 几乎所有场合, 正零和负零都会被当作正常的0 +0 // 0 -0 // 0 (-0).toString() // '0' (+0).toString() // '0' // 唯一有区别的场合是, +0或-0当作分母, 返回的值是不相等的。 (1 / +0) === (1 / -0) // 结果是false 是因为除以正零得到+Infinity, 除以负零得到-Infinity, 这两者是不相等的。
    
8) Infinity 无穷
  • Infinity表示“无穷”, 用来表示两种场景。一种是一个正的数值太大或一个负的数值太小无法表示;另一种是非0数值除以0得到Infinity

    // 场景1 Math.pow(2, 1024) // Infinity // 场景2 0 / 0 // NaN 1 / 0 // Infinity 0除以0会得到NaN,而非0数值除以0,会返回Infinity。
    
  • Infinity有正负之分, Infinity表示正的无穷, -Infinity表示负的无穷。

    Infinity === -Infinity // false 1 / -0 // -Infinity -1 / -0 // Infinity // 非零正数除以-0,会得到-Infinity, 负数除以-0,会得到Infinity
  • 由于数值正向溢出(overflow)、负向溢出(underflow)和被0除, JavaScript 都不报错, 所以单纯的数学运算几乎没有可能抛出错误。

    // Infinity大于一切数值(除了NaN), -Infinity小于一切数值(除了NaN) Infinity > 1000 // true -Infinity < -1000 // true // Infinity与NaN比较, 总是返回false。 Infinity > NaN // false -Infinity > NaN // false Infinity < NaN // false -Infinity < NaN // false
    
  • Infinity 运算

    • Infinity的四则运算, 符合无穷的数学计算规则
    • 0乘以Infinity返回NaN;0除以Infinity返回0Infinity除以0返回Infinity
    • Infinity加上或乘以Infinity 返回的还是Infinity
    • Infinity减去或除以Infinity得到NaN
    • Infinitynull计算时null会转成0,等同于与0的计算。
    • Infinityundefined计算, 返回的都是NaN
9) 数值相关的全局方法
  • parseInt()方法用于将字符串转为整数。

  • parseFloat()方法用于将一个字符串转为浮点数。

  • isNaN()方法可以用来判断一个值是否为NaNisFinite(-1) // true

3.5 String 字符串

  • 字符串就是零个或多个排在一起的字符, 放在单引号'或双引号"之中。

  • 单引号字符串的内部可以使用双引号。双引号字符串的内部可以使用单引号。

  • 如果要在单引号字符串的内部使用单引号,就必须在内部的单引号前面加上反斜杠\用来转义,双引号字符串内部使用双引号也是如此。

  • 字符串默认只能写在一行内, 分成多行将会报错; 如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。

1) 转义
  • 反斜杠\在字符串内有特殊含义用来表示一些特殊字符, 所以又称为转义符

  • 主要的反斜杠转义字符主要有以下这些:

    转义符号含义unicode编码
    \0null\u0000
    \b后退键\u0008
    \f换页符\u000C
    \n换行符\u000A
    \r回车键\u000D
    \t制表符\u0009
    \v垂直制表符\u000B
    \'单引号\u0027
    \"双引号\u0022
    \\反斜杠\u005C
2) 字符串与数组
  • 字符串可以被视为字符数组, 可以使用数组的方括号[]运算符用来返回某个位置的字符(位置索引从0开始)

  • 如果方括号中的数字超过字符串的长度, 或者方括号中根本不是数字, 则返回undefined

3) length 长度
  • length属性返回字符串的长度,该属性也是无法改变的。

4) 字符集
  • Javascript 使用 Unicode 字符集。JavaScript 引擎内部所有字符都用 Unicode 表示。

  • Javascript 不仅以 Unicode 储存字符, 还允许直接在程序中使用 Unicode 码点表示字符, 即将字符写成\uxxxx的形式, 其中xxxx代表该字符的 Unicode 码点。

5) Base64 转码
  • 有时文本里面包含一些不可打印的符号, 比如ASCII 码0到31的符号都无法打印出来, 这时可以使用Base64 编码, 将它们转成可以打印的字符;

    另一个场景是, 有时需要以文本格式传递二进制数据, 那么也可以使用 Base64 编码。

  • Base64 就是一种编码方法可以将任意值转成 0~9、A~Z、a-z、+/这64个字符组成的可打印字符。使用它的主要目的不是为了加密, 而是为了不出现特殊字符, 简化程序的处理。

  • JavaScript 原生提供两个Base64 相关的方法:

    • btoa():从二进制数据“字符串”创建一个 Base-64 编码的 ASCII 字符串(“btoa”应读作“binary to ASCII”)
    • atob():解码通过 Base-64 编码的字符串数据(“atob”应读作“ASCII to binary”)

3.6 object 对象

  • 对象(object)是 JavaScript 语言的核心概念。也是最重要的数据类型

  • 什么是对象?简单说, 对象就是一组“键值对”(key-value)的集合, 是一种无序的复合数据集合。

1) 键名
  • 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名), 所以加不加引号都可以。

  • 如果键名是数值, 会被自动转为字符串

  • 对象的每一个键名又称为“属性”(property), 它的“键值”可以是任何数据类型; 如果一个属性的值为函数, 通常把这个属性称为“方法”, 它可以像函数那样调用。

  • 如果属性的值还是一个对象, 就形成了链式引用。

  • 对象的属性之间用逗号分隔, 最后一个属性后面可以加逗号, 也可以不加。

  • 属性可以动态创建,不必在对象声明时就指定。

2) 对象的引用
  • 如果不同的变量名指向同一个对象, 那么它们都是这个对象的引用, 也就是说指向同一个内存地址; 修改其中一个变量, 会影响到其他所有变量。

  • 如果取消某一个变量对于原对象的引用, 不会影响到另一个变量

3) 属性的操作

属性的读取:

  • 读取对象的属性有两种方法: 一种是使用点.运算符,还有一种是使用方括号[]运算符。

属性的赋值:

  • 点运算符和方括号运算符, 不仅可以用来读取值, 还可以用来赋值

属性的查看:

属性的删除:

  • delete命令用于删除对象的属性, 删除成功后返回 true。

属性是否存在:

  • in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值), 如果包含就返回true否则返回false;

  • 左边是一个字符串, 表示属性名, 右边是一个对象。

属性的遍历:

  • for...in循环用来遍历一个对象的全部属性。

  • for...in 循环需要注意两点:

    • 它遍历的是对象所有可遍历(enumerable)的属性, 会跳过不可遍历的属性;
    • 它不仅遍历对象自身的属性,还遍历继承的属性。

with语句:

  • 它的作用是操作同一个对象的多个属性时, 提供一些书写的方便。语法:with (对象) { 语句; }
  • 注意: 如果with区块内部有变量的赋值操作, 必须是当前对象已经存在的属性, 否则会创造一个当前作用域的全局变量。

     

3.7 函数

  • 在 Javascript 中, 函数是头等 (*first-class*)*对象,因为它们可以像任何其它*对象一样具有属性和方法, 它们与其他对象的区别在于函数可以被调用。
  • 在 Javascript 中, 每个函数其实都是一个Function对象;
  • 如果一个函数中没有使用 return 语句, 则它默认返回undefined; 要想返回一个特定的值, 则函数必须使用 [return] 语句来指定一个要返回的值。
1) 函数的声明(创建)
2) 圆括号运算符, return 语句和递归
  • 调用函数时, 要使用圆括号运算符; 圆括号之中可以加入函数的参数。

  • 函数可以调用自身, 这就是递归(recursion)。

3) 第一等公民
  • Javascript 语言将函数看作一种值, 与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方就能使用函数。

  • 比如, 可以把函数赋值给变量和对象的属性, 也可以当作参数传入其他函数, 或者作为函数的结果返回;

  • 函数只是一个可以执行的值, 此外并无特殊之处。

4) 函数名的提升
  • Javascript 引擎将函数名视同变量名, 所以采用function命令声明函数时, 整个函数会像变量声明一样被提升到代码头部。

  • 如果采用赋值语句定义函数, Javascript 就会报错。

  • 注意: 如果像下面例子那样, 采用function 命令和var 赋值语句声明同一个函数, 由于存在函数提升, 最后会采用var赋值语句的定义。

5) 函数的属性和方法
  • name属性: 函数的name属性返回函数的名字。

  • length属性: 函数的length属性返回函数预期传入的参数个数, 即函数定义之中的参数个数。

  • toString()方法: 返回一个字符串, 内容是函数的源码。

6) 函数作用域
  • 作用域(scope)指的是变量存在的范围。

  • 在 ES5 的规范中, Javascript 只有两种作用域:

    • 一种是全局作用域, 变量在整个程序中一直存在, 所有地方都可以读取;

    • 另一种是函数作用域, 变量只在函数内部存在。ES6 又新增了块级作用域。

  • 对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。

  • 函数内部定义的变量, 会在该作用域内覆盖同名全局变量。

7) 函数内部的变量提升
  • 与全局作用域一样, 函数作用域内部也会产生“变量提升”现象。var命令声明的变量不管在什么位置, 变量声明都会被提升到函数体的头部。

8) 函数本身作用域
  • 函数本身也是一个值, 也有自己的作用域。它的作用域与变量一样, 就是其声明时所在的作用域, ==与其运行(或者说调用)时所在的作用域无关==。

9) 函数参数
  • 函数运行的时候, 有时需要提供外部数据,不同的外部数据会得到不通的结果, 这种外部数据就叫参数。

  • 函数的参数不是必须的, Javascript 允许省略参数。

  • 没有办法只省略靠前的参数, 而保留靠后的参数。如果一定要省略靠前的参数, 只有显式传入undefined

  • 【函数传递方式】 函数参数如果是原始类型的值(数值、字符串、布尔值), 传递方式是==传值传递==。这就意味着函数体内修改参数值, 不会影响到函数外部。

  • **【函数传递方式】**函数参数如果是复合类型的值(数组、对象、其他函数), 传递方式是==传址传递==。也就是说传入函数的的是原始值的地址, 所以在函数内部修改参数将会影响到原始值

  • 如果有同名的参数, 则取最后出现的那个值。

10) 函数的闭包
  • 闭包(closure)是Javascript 语言的一个难点, 也是它的特色。很多高级应用都要依靠闭包实现。
  • 理解闭包, 首先必须理解变量作用域。前面提到Javascript 有两种作用域:全局作用域和函数作用域。

// 函数内部可以直接读取全局变量; 正常情况下, 函数外部无法读取函数内部声明的变量。 // 如果出于种种原因, 需要得到函数内的局部变量。正常情况下这是办不到的, 只有通过变通方法才能实现。那就是在函数的内部, 再定义一个函数。 function fun() { var parm = 1; function inner() { console.log(parm); } } // 上面代码中, 函数inner就在函数fun内部, 这时fun的所有局部变量对inner都是可见的; // 但是反过来就不行, inner内部的局部变量, 对fun就是不可见的。 // 这就是Javascript 语言特有的"链式作用域"结构(chain scope), 子对象会一级一级地向上寻找所有父对象的变量; // 所以, 父对象的所有变量, 对子对象都是可见的, 反之则不成立。 // 既然inner可以读取fun的局部变量, 那么只要把inner作为返回值, 就可以在f1外部读取它的内部变量了 function fun() { var parm = 1; function inner() { console.log(parm); } return inner; } var newFun = fun(); newFun() // 1 // 上面代码中, 函数fun的返回值就是函数inner, 由于inner可以读取fun的内部变量, 所以就可以在外部获得fun的内部变量了。 // 闭包就是函数inner, 即能够读取其他函数内部变量的函数。由于在Javascript 语言中, 只有函数内部的子函数才能读取内部变量; // 所以可以把闭包简单理解成“定义在一个函数内部的函数”。 // 闭包最大的特点, 就是它可以“记住”诞生的环境, 比如inner记住了它诞生的环境fun, 所以从inner可以得到fun的内部变量。 // 在本质上, 闭包就是将函数内部和函数外部连接起来的一座桥梁。

  • 闭包的最大用处有两个: 一个是可以读取外层函数内部的变量, 另一个就是让这些变量始终保持在内存中, 即闭包可以使得它诞生环境一直存在。
11) 立即调用的函数表达式
  • 根据Javascript 的语法, 圆括号()跟在函数名之后, 表示调用该函数。
  • 有时我们需要在定义函数之后, 立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。

// function这个关键字既可以当作语句,也可以当作表达式。 // 语句 function f() {} // 表达式 var f = function f() {} // 当作表达式时, 函数可以定义后直接加圆括号调用。 var f = function f() { return 1}(); f // 1 函数定义后直接加圆括号调用没有报错。原因就是function作为表达式, 引擎就把函数定义当作一个值。这种情况下就不会报错。

  • 为了避免解析的歧义, Javascript 规定, 如果function关键字出现在行首, 一律解释成语句。

    因此,引擎看到行首是function关键字之后, 认为这一段都是函数的定义, 不应该以圆括号结尾, 所以就报错了。

// 函数定义后立即调用的解决方法, 就是不要让function出现在行首让引擎将其理解成一个表达式。最简单的处理, 就是将其放在一个圆括号里面。 (function(){ /* code */ }()); // 或者 (function(){ /* code */ })(); // 上面两种写法都是以圆括号开头, 引擎就会认为后面跟的是一个表达式, 而不是函数定义语句, 所以就避免了错误。 // 注意:上面两种写法最后的分号都是必须的。

  • 推而广之, 任何让解释器以表达式来处理函数定义的方法, 都能产生同样的效果, 比如下面三种写法。

var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); // 甚至像这样写也是可以的。 !function () { /* code */ }(); ~function () { /* code */ }(); -function () { /* code */ }(); +function () { /* code */ }();

  • 通常情况下, 只对匿名函数使用这种“立即执行的函数表达式”。

    它的目的有两个:

    • 一是不必为函数命名,避免了污染全局变量;
    • 二是 IIFE(Immediately-Invoked Function Expression) 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

// 写法一 var tmp = newData; processData(tmp); storeData(tmp); // 写法二(比写法一更好, 完全避免了污染全局变量) (function () { var tmp = newData; processData(tmp); storeData(tmp); }());

12) eval
  • eval命令接受一个字符串作为参数, 并将这个字符串当作语句执行。
  • 如果参数字符串无法当作语句运行, 那么就会报错。
  • 放在eval中的字符串, 应该有独自存在的意义, 不能用来与eval以外的命令配合使用。

eval('return;'); // SyntaxError: Illegal return statement // 因为return不能单独使用, 必须在函数中使用。

  • 如果eval的参数不是字符串, 那么会原样返回。

  • eval没有自己的作用域, 都在当前作用域内执行, 因此可能会修改当前作用域的变量的值, 造成安全问题。

var a = 1; eval('a = 2'); a // 2 // 为了防止这种风险Javascript 规定, 如果使用严格模式, eval内部声明的变量不会影响到外部作用域。 'use strict' var a = 1; eval('var a = 2'); console.log(a) // 1 // 不过,即使在严格模式下,eval依然可以读写当前作用域的变量 (function f() { 'use strict'; var foo = 1; eval('foo = 2'); console.log(foo); // 2 })() // 总之, eval的本质是在当前作用域之中注入代码。由于安全风险和不利于Javascript 引擎优化执行速度, 一般不推荐使用。 // 通常情况下, eval最常见的场合是解析JSON数据的字符串, 不过正确的做法应该是使用原生的JSON.parse方法。

3.8 数组

  • 数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始), 整个数组用方括号表示。

// 除了在定义时赋值,数组也可以先定义后赋值。 var arr = []; arr[0] = 'a'; arr[1] = 1; console.log(arr) // ['a', 1] // 任何类型的数据, 都可以放入数组。 var arr = [ {a:1}, [1, 2, 3], function fun() { console.log('函数') } ]; arr[0]; arr[1]; arr[2]; // 如果数组的元素还是数组, 就形成了多维数组。 var a = [[1, 'a'], [2, 'b'], [3, 'c']] a[0][1] // a a[1][1] // b

  • 本质上数组属于一种特殊的对象。typeof运算符会返回数组的类型是object
  • 数组的特殊性体现在, 它的键名是按次序排列的一组整数(0,1,2...)。

var a = [[1, 'a'], [2, 'b'], [3, 'c']] Object.keys(a) // ['0', '1', '2'] Object.keys方法返回数组的所有键名 // 由于数组成员的键名是固定的(默认总是0、1、2...), 因此数组不用为每个元素指定键名, 而对象的每个成员都必须指定键名。 // Javascript 语言规定对象的键名一律为字符串。 // 所以, 数组的键名其实也是字符串。之所以可以用数值读取, 是因为非字符串的键名会被转为字符串。 a['1'] // [2, 'b'] a[1] // [2, 'b'] 数值键名被自动转为了字符串

  • 对象有两种读取成员的方法:点结构(object.key)和方括号结构(object[key])。但是, 对于数值的键名,不能使用点结构。

var arr = [1, 2, 3]; arr.0 // SyntaxError arr.0的写法不合法, 因为单独的数值不能作为标识符(identifier)。所以数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。

1) length 属性
  • length属性返回数组的成员数量。

[[1, 'a'], [2, 'b'], [3, 'c']].length // 3 // Javascript 使用一个32位整数, 保存数组的元素个数。这意味着, 数组成员最多只有4294967295个(232 - 1)个,也就是说length属性的最大值就是4294967295。

  • length属性是可写的。如果人为设置一个小于当前成员个数的值, 该数组的成员数量会自动减少到length设置的值。

var a = [1, 'a', 2, 'b', 3, 'c']; a.length // 6 a.length = 2 a // [1, 'a'] // 当数组的length属性设为2(即最大的整数键只能是1)那么整数键2(值为2)之后的元素就已经不在数组中了, 被自动删除了。 // 清空数组的一个有效方法, 就是将length属性设为0。 // 如果人为设置length大于当前元素个数, 则数组的成员数量会增加到这个值, 新增的位置都是空位。 var a = [1, 'a']; a.length = 2 a[3] // undefined 读取新增的位置都会返回undefined // 如果人为设置length为不合法的值, Javascript 会报错。

  • 注意:由于数组本质上是一种对象, 所以可以为数组添加属性, 但是这不影响length属性的值。

var a = []; a['p'] = 'abc'; a.length // 0 a[1.2] = 'abc' a.length // 0 // 上面代码将数组的键分别设为字符串和小数, 结果都不影响length属性。 // 因为length属性的值就是等于最大的数字键加1, 而这个数组没有整数键, 所以length属性保持为0。

  • 如果数组的键名是添加超出范围的数值, 该键名会自动转为字符串。

var arr = []; arr[-1] = 'a'; arr[Math.pow(2, 32)] = 'b'; console.log(arr.length); // 0 console.log(arr[-1]); // a console.log(arr[4294967296]); // b // 数组arr添加了两个不合法的数字键, 结果length属性没有发生变化。这些数字键都变成了字符串键名。 // 之所以会取到值, 是因为取键值时, 数字键名会默认转为字符串。

2) in 运算符
  • 检查某个键名是否存在的运算符in, 适用于对象, 也适用于数组。

var arr = [1, 2, 3, 'a', 'b', 'c']; 2 in arr // true '2' in arr // true 由于键名都是字符, 所以数值2会自动转成字符串。 6 in arr // false 如果数组的某个位置是空位, in运算符返回false

3) for...in 循环和数组的遍历
  • for...in循环不仅可以遍历对象, 也可以遍历数组, 数组也是一种特殊对象。

// for...in 循环遍历的是数组的数字键 var arr = [1, 2, 3, 'a', 'b', 'c']; for (let i in arr) { console.log(i); } // 0 // 1 // 2 // 3 // 4 // 5 // for...in不仅会遍历数组所有的数字键,还会遍历非数字键 var arr = [1, 2, 3, 'a', 'b', 'c']; arr.length = 3; arr.foo = true for (let i in arr) { console.log(i); } // 0 // 1 // 2 // foo 非整数键foo也遍历到了, 所以不推荐for...in遍历数组

  • 数组的遍历可以考虑使用forwhile循环。

var arr = ['a', 'b', 'c', 2, 3]; // for for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } // while var i = 0; while (i < arr.length) { console.log(arr[i]); i++; } // while 从右往左遍历 var arrLen = arr.length; while (arrLen--) { console.log(arr[arrLen]); }

  • forEach()方法也可以遍历数组。

var arr = ['a', 'b', 'c', 2, 3]; arr.forEach(element => console.log(element));

4) 数组的空位
  • 当数组的某个位置是空元素, 即两个逗号之间没有任何值, 我们称该数组存在空位。
  • 数组的空位是可以读取的, 返回undefined
  • 使用delete命令删除一个数组成员, 会形成空位, 并且不会影响length属性。
  • 数组的某个位置是空位, 与某个位置是undefined是不一样的。如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。
5) 类似数组的对象
  • 如果一个对象的所有键名都是正整数或零, 并且有length属性, 那么这个对象就很像数组, 语法上称为“类似数组的对象”(array-like object)。

4 运算符

4.1 算数运算符

  • Javascript 共提供10个算术运算符, 用来完成基本的算术运算。
运算符描述
+加法运算符
-减法运算符
*乘法运算符
\除法运算符
**指数运算符
%余数运算符
++xx++自增运算符
--xx--自减运算符
+x数值运算符
-x负数值运算符
  • 对象的相加:如果运算子是对象, 必须先转成原始类型的值, 然后再相加。

4.2 赋值运算符

  • 赋值运算符(Assignment Operators)用于给变量赋值, 最常见的等号=赋值运算符。

4.3 比较运算符

  • 比较运算符用于比较两个值的大小, 然后返回一个布尔值, 表示是否满足指定的条件。
运算符描述
>大于运算符
<小于运算符
>=大于或等于运算符
<=小于或等于运算符
==相等运算符
===严格相等运算符
!=不相等运算符
!==严格不相等运算符
  • 比较运算符分成两类:相等比较和非相等比较。两者的规则是不一样。
  • 对于非相等的比较, 算法是先看两个运算子是否都是字符串, 如果是字符串, 就按照字典顺序比较(实际上是比较 Unicode 码点);否则, 将两个运算子都转成数值, 再比较数值的大小。
  • 相等运算符和严格相等运算符

    • Javascript 提供两种相等运算符:=====

    • 相等操作符==:

      • 在比较之前, 会进行类型转换。它会尝试将两个值转换为相同类型, 然后再进行比较。
      • ==*类型转换规则==:
        • 如果两个操作数的类型相同(例如两个数字、两个字符串等), 则不进行类型转换, 直接进行比较;
        • 如果其中一个操作数是 null, 另一个操作数是 undefined, 则它们会被视为相等;
        • 如果一个操作数是数字, 另一个操作数是字符串, 会将字符串转换为数字, 然后比较它们的数值;
        • 如果一个操作数是布尔值, 另一个操作数是非布尔值(除了 nullundefined), 会将布尔值转换为数字(true 转换为 1, false 转换为 0), 然后进行比较;
        • 如果一个操作数是对象, 另一个操作数是原始值(字符串、数字、布尔值), 会首先调用对象的 valueOf方法获取原始值, 如果返回的还是对象, 再接着调用toString方法,然后按照上述规则进行比较;
        • 如果一个操作数是 NaN, 另一个操作数也是 NaN, 它们被视为相等;
        • undefinednull只有与自身比较, 或者互相比较时才会返回true; 与其他类型的值比较时, 结果都为false;
        • 在其他情况下,返回 false
  • 不相等运算符和严格不相等运算符

4.4 布尔运算符(逻辑运算符)

  • 布尔运算符用于将表达式转为布尔值
运算符描述
!取反运算符
&&且运算符
||或运算符
?:三元条件运算符
 

4.5 二进制位运算符

  • 二进制位运算符用于直接对二进制位进行计算,一共有7个。

下面这些位运算符只对整数起作用, 如果一个运算子不是整数, 会自动转为整数后再执行:

运算符描述
\二进制或运算符: 表示若两个二进制位都为0, 则结果为0, 否则为1。
&二进制与运算符: 表示若两个二进制位都为1, 则结果为1, 否则为0。
~二进制否运算符: 表示对一个二进制位取反。
^异或运算符: 表示若两个二进制位不相同, 则结果为1, 否则为0。
<<左移运算符
>>右移运算符
>>>头部补零的右移运算符

4.6 其它运算符,运算顺序

  • void运算符的作用是执行一个表达式, 然后不返回任何值, 或者说返回undefined
 

运算符优先级:

image-20230614234355808

5 数据类型转换

  • Javascript 是一种动态类型语言, 变量没有类型限制, 可以随时赋予任意值。
  • 如果运算符发现, 运算子的类型与预期不符, 就会自动转换类型。

5.1 强制类型转换

  • 强制转换主要指使用Number()String()Boolean()三个函数, 手动将各种类型的值, 分别转换成数字、字符串或者布尔值。

  • Number() : 使用Number()函数, 可以将任意类型的值转化成数值。

     

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟庭师兄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值