一、入门
1.1 历史
1995年5月程序员Brendan Eich用了10天设计了语言的第一版,语法有多个来源。
- 基本语法:借鉴了 C 和 java
- 数据结构:借鉴 java ,将值分为原始值和对象
- 函数用法:借鉴 scheme 和 AWK ,将函数当做第一等公民,引入闭包
- 原型继承模型: 借鉴 self
- 正则表达式: 借鉴 Perl
- 字符串和数组处理:借鉴 python
1.2 基本语法
1. 语句(statement):语句是为了完成某种任务而进行的操作,下面就是一行赋值语句。
let a = 1 + 3;
复制代码
这里 1+3是表达式(expression),指一个为了得到返回值的计算式,语句和表达式的区别在于,前者主要为了进行某种操作,一般情况下不需要返回值;后者则是为了得到返回值,一定会返回一个值。
变量提升。
JavaScript引擎工作方式是先解析代码,获取所有被声明的变量,然后再一行一行的运行。即所有变量的声明语句,都会被提到代码的头部,称作变量提升(hoising)这里主要指的是使用关键字 var 和function声明的变量。
1.3 三元运算符 ?:
它可以看做是 if…else… 的简写形式
1.4 标签(label)
JavaScript 语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,标签的格式如下。
label:
语句
复制代码
标签通常与break和continue语句配合使用,跳出特定循环
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);
}
}
复制代码
上面代码为一个双重循环区块,break命令后面加上了top标签(注意,top不用加引号),满足条件时,直接跳出双层循环。如果break语句后面不使用标签,则只能跳出内层循环,进入下一次的外层循环。用于跳出双层循环
二、数据类型
2.1 null和undefined
null和undefined都表示“没有”吗,在第一版JavaScript中,null是像在java中一样被当做对象,Brendan Eich 觉得表示“无”的值最好不是对象。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果null自动转为0,很不容易发现错误。因此,他又设计了一个undefined,区别是:null表示一个空对象,转为数值时为0,undefined表示一个“此处无定义”的原始值,转为数值时为NaN。
2.2 数值
JavaScript内部所有数字都是以64位浮点数形式存储,JavaScript 语言的底层根本没有整数。
1 === 1.0 // true
复制代码
JavaScript的64个二进制位,从左边开始:
- 第一位:符号位,0表示整数,1表示负数
- 第2~11位(共11位):指数部分
- 第13~64位(共52位):小数部分,即有效数字
JavaScript的Number对象提供了MAX_VALUE和MIN_VALUE属性,返回可以具体可以表示的最大值和最小值。
2.2.1 正0和负0
avaScript 的64位浮点数之中,有一个二进制位是符号位。这意味着,任何一个数都有一个对应的负值,就连0也不例外。
javaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。
+0 === -0 // true
0 === +0 // true
0 === -0 // true
(1 / +0) === (1 / -0) // false
复制代码
最后一个出现这样结果,是因为除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的。
2.2.2 NaN
NaN表示“非数字”,主要出现在将字符串解析成数字出错的场合,0除以0也会得到NaN。NaN不是独立的数据类型,它属于Number。
2.2.3 相关方法
- parseInt(): 对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果,它的返回结果只有数字或者NaN。
parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1
parseInt(0.0000008) // 8
// 等同于
parseInt('8e-7') // 8
复制代码
注意:
parseFloat(true) // NaN
Number(true) // 1
parseFloat(null) // NaN
Number(null) // 0
parseFloat('') // NaN
Number('') // 0
parseFloat('123.45#') // 123.45
Number('123.45#') // NaN
复制代码
2.3 字符串
字符串默认只能写在一行内,分成多行将会报错。如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。
var longString = 'Long \
long \
long \
string';
longString
复制代码
2.3.1 字符串与数组
字符串可以看做字符串数组,因此可以使用数组的方括号运算,
var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"
复制代码
2.3.2 Base64 转码
有时候文本里包含一些不可打印的符号,Base64 就是一种编码方法,keyi 将任意值转成 0~9、A~Z、a-z、+和/这64个字符组成的可打印字符。js提供两个Base64相关的方法,
- btoa(): 将任意值转为base64编码
- atob(): base64拜纳姆转为原来的值
let str = 'Hello Word';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
btoa('你好') // 报错
复制代码
注意,这两个方法不适合非 ASCII 码的字符,会报错。要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
复制代码
2.4 对象
2.4.1 对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量
2.4.2 in运算符
in运算符用于检测对象是否包含某个属性,但是它不能识别这个属性是对象自身的还是继承过来的,因此需要配合对象的hasOwnProperty方法判断一下。
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
复制代码
2.4.3 for…in 循环
for…in循环用来遍历一个对象的全部属性。for…in循环有两个使用注意点。
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性。
2.5 函数
2.5.1 函数的重复声明
如果同一个函数被多次声明,后面的声明会覆盖前面的声明。由于函数名的提升,前一次声明在任何时候都是无效的,这一点要特别注意。
function tyt(){
console.log(1)
}
tyt() // 2
function tyt(){
console.log(2)
}
复制代码
2.5.2 第一等公民
JS将函数看做是一种值,与其他值地位相同(数值、字符串),凡是可以使用值的地方就可以使用函数,可以把函数赋值给变量和对象属性,可以当做参数传入其他函数,或者作为函数返回结果。
2.5.3 函数名的提升
var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1
复制代码
2.5.6 length 属性
函数的length属性返回函数 预期传入的参数个数,定了默认值后,length属性将失真。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
复制代码
2.5.7 toString()
对于自定义的函数,调用函数的 toString()方法会返回函数的源码,对于原生的函数,则会返回 function (){[native code]}
2.5.8 函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
2.5.9 函数本身的作用域
函数本身也是一个值,也有自己的作用域,它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
let a = 1;
let x = test(){
console.log(a)
}
function test2(){
let a = 2;
x();
}
test2() // 1
复制代码
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
2.5.10 参数传递方式
如果参数是原始类型值(数值、字符串、布尔值),传递方式是传值传递。也就是说,在函数体内部修改参数值,不会影响到函数外部。
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
复制代码
上面代码中,变量p是一个原始类型的值,传入函数f的方式是传值传递。因此,在函数内部,p的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
复制代码
注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
复制代码
上面代码中,在函数f()内部,参数对象obj被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)的值实际是参数obj的地址,重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响。
2.5.11 闭包(closure)
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
复制代码
闭包就是函数f2,即能够读取其他函数内部变量的函数,由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是他可以记住诞生的环境。本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包最大的用处有两个,一个就是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,闭包可以使它诞生环境一直存在。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
复制代码
闭包的另一个用处,是封装对象的私有属性和私有方法。外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
2.5.11 立即调用的函数表达式
根据JS语法,圆括号()跟在函数名之后,表示调用该函数,有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。
function(){ /* code */ }(); // SyntaxError: Unexpected token
复制代码
产生这个错误的原因是,function这个关键字既可以当作语句,也可以当作表达式。
// 语句
function f() {}
// 表达式
var f = function f() {}
复制代码
为了避免解析的歧义,JavaScript 规定,如果function关键字出现在行首,一律解释成语句。因此,引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。
推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
复制代码
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
2.5数组
2.5.1 length属性
length属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length设置的值。
var arr = [ 'a', 'b', 'c' ];
arr.length // 3
arr.length = 2;
arr // ["a", "b"];
arr.length = 0;
arr // []
复制代码
清空数组的一个有效方法,就是将length属性设为0。
2.5.2 数组的空位
var a = [1, , 1];
a.length // 3
a[1] // undefined
var b = ['a'];
b.length = 3;
b[1] // undefined
复制代码
数组的某个位置是空位,与某个位置是undefined,是不一样的, length属性不过滤空位。如果是空位,使用数组的forEach方法、for…in结构、以及Object.keys方法进行遍历,空位都会被跳过。
var a = [, , ,];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
})
// 不产生任何输出
for (var i in a) {
console.log(i);
}
// 不产生任何输出
Object.keys(a)
// []
复制代码






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)
![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
