全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/13686263.html, 多谢,=。=~(如果对你有帮助的话请帮我点个赞啦)
重新学习JavaScript是因为当年转前端有点儿赶鸭子上架的意味,我一直在反思我的知识点总是很零散,不能在脑海中形成一个完整的体系,所以这次想通过再次学习将知识点都串联起来,结合日常开发的项目,达到温故而知新的效果。与此同时,总结一下我认为很重要但又被我遗漏的知识点~
背景知识
JavaScript既是一种轻量级的脚本语言,又是一种嵌入式语言。
- 脚本语言(script language):不具备开发操作系统的能力,只用来编写控制大型应用程序(例如浏览器)的脚本。
- 嵌入式语言(embedded):通过嵌入大型应用程序调用宿主环境提供的底层API(例如浏览器为JavaScript提供浏览器控制类、DOM类、Web类API;Node为JavaScript提供文件操作、网络通信等API;)实现本身核心语法不支持的复杂功能。
JavaScript历史
- 为什么诞生:Navigator 浏览器需要一种可以嵌入网页的脚本语言,用来控制浏览器行为,因为当时网速很慢而且上网费很贵,有些操作不宜在服务器端完成。
- 需求:不需要太强的功能,语法较简单,容易学习和部署。
JavaScript的编程风格是函数式编程和面向对象编程的一种混合体。
- 语法来源:
- 基本语法:借鉴 C 语言和 Java 语言。
- 数据结构:借鉴 Java 语言,包括将值分成原始值和对象两大类。
- 函数的用法:借鉴 Scheme 语言和 Awk 语言,将函数当作第一等公民,并引入闭包。
- 原型继承模型:借鉴 Self 语言(Smalltalk 的一种变种)。
- 正则表达式:借鉴 Perl 语言。
- 字符串和数组处理:借鉴 Python 语言。
ECMAScript历史
ECMAScript 和 JavaScript 的关系是,前者是后者的规范,后者是前者的一种实现。
- 为什么诞生:微软开发了JScript并内置于IE3.0浏览器中,Netscape 面临丧失浏览器脚本语言的主导权局面,最终决定将 JavaScript 提交给国际标准化组织 ECMA(European Computer Manufacturers Association),希望 JavaScript 能够成为国际标准,以此抵抗微软。
- 需求:规定浏览器脚本语言的标准。
基本语法
- 变量->变量提升:JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有变量的声明语句,都会被提升到代码的头部,对应的也有函数名提升(前提是使用
function
命令声明,如果采用赋值语句定义就会报错)。
// 原代码(如果没有变量提升会报错:a is not defined)
console.log(a);
var a = 1;
// 实际运行代码
var a;
console.log(a);
a = 1;
// 运行不会报错
f();
function f() {}
- 标识符:用来识别各种值(变量、函数)的合法名称,只能以任意 Unicode 字母、美元符号$或下划线_开头,只能包含 Unicode 字母、美元符号、下划线及数字。
- 中文是合法的标识符,可以用作变量名。
- JavaScript保留字:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。
- 注释:JavaScript可以兼容HTML代码注释,所以
<!--
和-->
也被视为合法的单行注释,但-->
只有在行首才会被当作单行注释,否则为正常运算,例如:
function countdown(n) {
while (n --> 0) console.log(n); // 被作为n-- > 0执行
}
countdown(3)
// 2
// 1
// 0
- 区块:使用大括号,将多个相关的语句组合在一起。
- 区块对于
var
命令来说不构成单独的作用域,与不使用区块的情况没有任何区别。例如:
{ var a = 1; } a // 1,在区块外部变量依然有效
- 区块对于
- 条件语句:
- 终于明白条件判断大家为什么要这么写了
if (2 = x)
,因为常量写在运算符的左边,一旦不小心将相等运算符写成赋值运算符,就会报错,因为常量不能被赋值。 - 在没有标明区块(大括号)时,
else
代码块总是与离自己最近的那个if
语句配对。 switch
语句后面的表达式与case
语句后面的表示式比较时,采用的是严格相等运算符(===
),即不会发生类型转换。
- 终于明白条件判断大家为什么要这么写了
- 循环语句:
break
语句用于跳出代码块或循环。continue
语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。- 标签:JavaScript 语言允许语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,例如:
top: for (var i = 0; i < 3; i++){ for (var j = 0; j < 3; j++){ if (i === 1 && j === 1) break top; console.log('i=' + i + ', j=' + j); } } // i=0, j=0 // i=0, j=1 // i=0, j=2 // i=1, j=0
数据类型
- 整数和浮点数:
- JavaScript内部,所有数字都是以64位浮点数形式储存。
1 === 1.0 // true
- 由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
0.1 + 0.2 === 0.3 // false 0.3 / 0.1 // 2.9999999999999996 (0.3 - 0.2) === (0.2 - 0.1) // false
- 数值精度:精度最多只能到53个二进制位,绝对值小于2的53次方的整数,即负的2的53次方到 2的53次方,都可以精确表示。
Math.pow(2, 53) // 9007199254740992(简单的法则就是JavaScript对15位的十进制数都可以精确处理)
- 数值范围:JavaScript 提供Number对象的
MAX_VALUE
和MIN_VALUE
属性,返回可以表示的最大值和最小值,负向溢出时返回0,正向溢出时返回Infinity。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
- 数值表示法:0b11(二进制)、0o377(八进制)、35(十进制)、0xFF(十六进制)、123e3(科学计数法)
- 小数点前的数字多于21位、小数点后的零多于5个时JavaScript自动将数值转为科学计数法表示。
- 默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制。
- 特殊数值:
- 几乎所有场合,
正零
和负零
都会被当作正常的0,是等价的,唯一有区别的场合是+0或-0当作分母,返回的值是不相等的。
(1 / +0) === (1 / -0) // false(除以正零得到+Infinity,除以负零得到-Infinity)
NaN
表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合、还有一些数学函数的运算结果,数据类型属于Number,不等于任何值(包括它本身),布尔运算时被当作false,和任何数运算得到的都是NaN。
typeof NaN // 'number' NaN === NaN // false Boolean(NaN) // false NaN + 32 // NaN
Infinity
表示“无穷”,用来表示两种场景,一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,Infinity有正负之分,大于一切数值(除了NaN),四则运算符合无穷的数学计算规则,与null计算时null会转成0,与undefined计算返回的都是NaN。
Infinity === -Infinity // false Infinity > 1000 // true Infinity > NaN // false 0 * Infinity // NaN(特殊) Infinity - Infinity // NaN(特殊) Infinity / Infinity // NaN(特殊) 0 / Infinity // 0 Infinity / 0 // Infinity
- 几乎所有场合,
- 全局方法:
parseInt()
:用于将字符串转为整数,头部有空格会自动去除,转换时是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分,如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外)返回NaN,可以接受第二个参数(2到36之间,超出返回NaN,为0、undefined、null则忽略)表示被解析的值的进制并返回该值对应的十进制数。
parseInt('15e2') // 15 parseInt('.3') // NaN parseInt('+1') // 1 parseInt('') // NaN parseInt('1000', 2) // 8 parseInt('10', null) // 10
parseFloat()
:用于将一个字符串转为浮点数,需要与Number()
函数区分。
parseFloat('') // NaN Number('') // 0 parseFloat('123.45#') // 123.45 Number('123.45#') // NaN
isNaN()
:用来判断一个值是否为NaN
,只对数值有效,如果传入其他值,会被先转成数值(所以重点关注里面传入的值是否可以被Number转为数值)。
isNaN('Hello') // true // 相当于 isNaN(Number('Hello')) // true //判断NaN更可靠的方法是,利用NaN是唯一一个不等于自身的这个特点 function myIsNaN(value) { return value !== value; }
isFinite()
:返回一个布尔值,表示某个值是否为正常的数值,除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true。
- 字符串:
- 字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始),但无法改变字符串之中的单个字符。
'hello'[1] // "e" var s = 'hello'; delete s[0]; s // "hello" s[1] = 'a'; s // "hello"
- 对于码点在U+10000到U+10FFFF之间的字符,JavaScript 总是认为它们是两个字符(length属性为2),所以JavaScript 返回的字符串长度可能是不正确的。
Base64
转码:就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+和/这64个字符组成的可打印字符,应用场景一个是将不可打印符号转成可打印字符(例如ASCII的0~31),或者以文本格式传递二进制数据,有两个原生方法:btoa()
任意值转为 Base64 编码、atob()
Base64 编码转为原来的值,但不适用于非ASCII的字符(需要先通过encodeURIComponent
进行转码)。
var string = 'Hello World!'; btoa(string) // "SGVsbG8gV29ybGQh" atob('SGVsbG8gV29ybGQh') // "Hello World!" function b64Encode(str) { return btoa(encodeURIComponent(str)); } function b64Decode(str) { return decodeURIComponent(atob(str)); } b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE" b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
- 对象:
- 对于不符合标识名条件的键名必须加上引号,例如第一个字符为数字,或者含有空格或运算符。
var obj = { '1p': 'Hello World', 'h w': 'Hello World', 'p+q': 'Hello World' };
- JavaScript 引擎如果遇到无法确定是对象还是代码块的情况,一律解释为代码块,如果要解释为对象,最好在大括号前加上圆括号。
// eval语句(作用是对字符串求值) eval('{foo: 123}') // 123 eval('({foo: 123})') // {foo: 123}
- 数字键在方括号读取运算符中可以不加引号,但不能使用点运算符读取。
var obj = { 123: 'hello world' }; obj.123 // 报错 obj[123] // "hello world"
Object.keys()
:返回一个对象本身的所有属性。
var obj = { key1: 1, key2: 2 }; Object.keys(obj); // ['key1', 'key2']
delete
:用于删除对象的属性,删除成功后返回true,但删除一个不存在的属性,delete不报错,而且也返回true,只有某属性不可删除时(configurable:false
)才返回false,只能删除对象本身的属性,无法删除继承的属性。in
:用于检查对象是否包含某个属性,如果包含就返回true,否则返回false,但无法识别哪些属性是对象自身的,哪些属性是继承的(可以用hasOwnProperty
判断)。
var obj = { p: 1 }; 'p' in obj // true 'toString' in obj // true var obj = {}; if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')) // false }
for...in
:用来遍历一个对象的全部属性,仅遍历可遍历(enumerable:true
)属性跳过不可遍历属性,不仅遍历对象自身的属性,还遍历继承的属性(前提是继承的属性是可遍历的),如果想仅遍历自身属性可用hasOwnProperty
在循环内部判断。
var obj = {a: 1, b: 2, c: 3}; for (var i in obj) { console.log('键名:', i); console.log('键值:', obj[i]); } // 键名: a // 键值: 1 // 键名: b // 键值: 2 // 键名: c // 键值: 3
with
:操作同一个对象的多个属性时,提供一些书写的方便(在内部可以不使用点运算符就能直接读取属性),如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量,不建议使用with语句,无法判断内部变量为对象属性还是全局变量。
var obj = { p1: 1, p2: 2, }; with (obj) { p1 = 4; p2 = 5; } // 等同于 obj.p1 = 4; obj.p2 = 5;
- 函数:
name属性
:返回函数的名字,常用的是获取参数函数的名字。
var myFunc = function () {}; function test(f) { console.log(f.name); } test(myFunc) // myFunc
length属性
:返回函数预期传入的参数个数,即函数定义之中的参数个数。
function f(a, b) {} f.length // 2
- 函数本身作用域:函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
var a = 1; var x = function () { console.log(a); }; function f() { var a = 2; x(); } f() // 1
- 参数传递方式:如果是原始类型的值(数值、字符串、布尔值),传递方式是
传值传递(passes by value)
,在函数体内修改参数值,不会影响到函数外部;如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)
,在函数内部修改参数,将会影响到原始值,但如果修改参数的地址指向,则不会影响原始值。
var obj = { p: 1 }; function f(o) { o.p = 2; } f(obj); obj.p // 2 function f2(o) { o = { p: 3 }; } f2(obj); obj.p // 2
arguments对象
:包含了函数运行时的所有参数,这个对象只有在函数体内部才可以使用,正常模式下arguments对象可以在运行时修改,但严格模式下不行,它很像数组,但它是一个对象,数组专有的方法(比如slice
和forEach
)无法使用,但可以将它转为真正的数组(slice
方法或遍历填入新数组)。
var f = function(a, b) { 'use strict'; // 开启严格模式 arguments[0] = 3; arguments[1] = 2; return a + b; } f(1, 1) // 2 var args = Array.prototype.slice.call(arguments); // 或者 var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
- 闭包:能够读取其他函数内部变量的函数,由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”,闭包的最大用处,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在,还有一个是封装对象的私有属性和私有方法。
PS:外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
- 立即调用的函数表达式(IIFE-Immediately-Invoked Function Expression):通常情况下,只对匿名函数使用,它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
(function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
eval命令
:接受一个字符串作为参数,并将这个字符串当作语句执行,参数如果不是字符串就会原样返回,eval没有自己的作用域,可能会因为修改当前作用域的变量造成安全问题(使用严格模式,eval内部声明的变量,不会影响到外部作用域,但依然可以读写当前作用域的变量),最常见的场合是解析 JSON 数据的字符串(应使用JSON.parse
替代),凡是使用别名执行eval,eval内部一律是全局作用域。
eval('var a = 1;'); // 生成变量a eval(123) // 123 // 非字符串原样返回
- 数组:
- JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串,之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
var arr = ['a', 'b', 'c']; arr['0'] // 'a' arr[0] // 'a'
- JavaScript 使用一个32位整数保存数组的元素个数,所以数组成员最多只有 4294967295 个(232 - 1)个,length属性的最大值就是 4294967295。
length
属性可写,如果设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length设置的值,可以通过将length
设置为0来清空数组。
var arr = [ 'a', 'b', 'c' ]; arr.length = 2; arr // ["a", "b"] arr.length = 0; arr // []
- 数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值。
var a = []; a['p'] = 'abc'; a.length // 0
for...in
:不仅会遍历数组所有的数字键,还会遍历非数字键,所以不推荐使用它对数组进行遍历,应该使用循环遍历或forEach
方法替代。
var a = [1, 2, 3]; a.foo = true; for (var key in a) { console.log(a[key]); } // 1 // 2 // 3 // true a.forEach(function (item) { console.log(item); }); // 1 // 2 // 3
delete
:删除数组元素后不影响length
属性,删除的位置形成空位,读取时返回undefined
,所以用length
属性遍历时需要注意,是无法跳过空位的。
var a = [, , ,]; a[1] // undefined(空位与该位置值为undefined不一样,空位在forEach、for...in、Object.keys遍历时会跳过
- 类数组对象(array-like object):很像数组的对象,如果一个对象的所有键名都是正整数或零,并且有length属性(非动态,不会随着成员的变化而变化),不具备数组的特有方法,常见的有
arguments
对象、大多数Dom元素集、字符串,slice
方法可以将类数组对象转换为真正的数组,除此之外可以通过call()
把数组方法嫁接到对象上。
var obj = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; obj[3] = 'd'; obj.length // 3 // forEach 方法(本来arguments对象无法调用该方法,但这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。) function logArgs() { Array.prototype.forEach.call(arguments, function (elem, i) { console.log(i + '. ' + elem); }); }
参考资料
JavaScript 语言入门教程 :https://wangdoc.com/javascript/index.html