诞生
1、早期的服务端语言表单校验、用户交互困难
2、与java没有关系,仅仅为了蹭热度
JavaScript实现组成
1、核心语言(ECMAScript)
2、文档对象(DOM)
3、浏览器(BOM)
浏览器只是ECMAScript实现的宿主环境
DOM
DOM将页面映射为树结构,提供给开发人员控制页面内容和结构的能力。
DOM1级:映射文档结构,如document等对象API
DOM2级:鼠标及键盘事件处理接口、样式接口、遍历和操作文档树的接口
BOM
提供操作浏览器的对象模型
1、移动、缩放、打开、关闭窗口及window对象
2、浏览器详细信息的navigator对象
3、窗口加载的页面详细信息location对象
4、显示器分辨率信息screen
5、cookies
6、XMLHttpRequest和IE的ActiveObject等自定义对象
BOM的实现根据浏览器不同而存在差异,HTML5的出现让BOM的实现朝着兼容性越来越高的方向发展
脚本嵌入-script标签
1、脚本默认是阻塞串行运行的,并且会阻塞DOM渲染(浏览器遇到body才开始呈现内容),一般的处理方式是将脚本放在底部(尾部body之前),避免白屏
2、defer属性,规定多个defer按顺序执行脚本,且在文档渲染完成,DOMContentLoaded事件前执行
3、async属性的脚本将并行请求及执行。async脚本谁先加载完成谁先执行,因此async脚本互不依赖很重要
4、推荐使用外部脚本文件,具有更好的可维护性,且可缓存(同一个脚本文件被多个页面公用只需要下载一次)
defer与async性能优化
明确一点: js的执行不可能和DOM同步进行,因为js拥有修改DOM的权利。但下载可以
1、defer规定多个defer按顺序执行脚本,实际情况是,多个defer并不一定会按顺序执行,也不一定在 DOMContentLoaded事件前。因此最好只使用一个defer脚本
2、正常脚本执行流程:所有脚本串行下载执行,每个脚本下载并执行完成才处理下一个脚本;脚本处理完成后开始解析html(浏览器遇到body开始呈现)
2、defer脚本执行流程:html解析与defer脚本并行下载,两者均完成后执行defer脚本
3、async脚本执行流程:html解析与async脚本并行下载,如果async脚本下载完成,立即执行(阻塞html解析),多个async脚本
4、defer使用:如果脚本依赖于DOM或者脚本作为依赖或补丁注入(被依赖)使用defer脚本;
5、defer最主要的场景是依赖注入补丁文件polyfill.js、依赖DOM如按钮点击事件
6、async的脚本是谁先下载完成谁先执行,无法处理依赖,且执行期间会阻塞html解析,如果你的不关心DOM,且不存在文件依赖,可以使用async脚本。如百度统计、集成的第三方聊天、评论服务
7、正常脚本与异步脚本如何加载?
doctype文档声明
1、规定了文档以哪种方式渲染html
2、如果没有声明,每个浏览器按自己的标准解析,称为混杂模式
3、w3c标准模式,使用html5的规范解析,提高兼容性
关键字
变量、语句-条件分支-循环及中断-异常捕获-类型捕获
var function delete new this debugger void finally with
if else do while for in switch case default break return continue
try catch throw instanceof typeof
其他的一些语法词都是保留字
8大数据类型
基础数据类型:String、Number、Boolean、Undefined、Null及ES6的Symbol、BigInt
复杂类型:Object
typeof
typeof(Number)等数据类型,检验的是数据类型的构造函数,返回function
typeof的数据返回
1、未声明或声明未初始化 =>"undefined"
2、字符串 => "string"
3、数字 => "number"
4、函数、正则表达式 => "function"
5、Symbol类型数据 => "symbol"
6、Boolean类型数据 => "boolean"
7、null、数组、{}、普通对象等 => "object"
8、函数 => "function"
函数是一组独立的功能集合,因此把它和object区分出来
typeof null 返回object。因为null存储的是一个空指针引用,因此如果变量将存储对象,可以声明为null或{}
实际上,undefined派生自null
typeof只能用来检测基本数据类型,检测复杂类型使用instanceof,通用检测使用Object.prototype.toString.call(a)
检测数组isArray
注意:如果变量未定义,直接条件判断将报错,而使用typeof检测确能返回undefined
Undefined类型
Undefined类型只有一个值undefined,指声明但未进行初始化的变量,引入这个值是为了正式区分空对象指针与未经初始化的变量
返回undefined的情况,一般是程序解析给的默认值
1、声明但未初始化
2、数组中,某一项没有值,值是undefined
3、形参接收不到值,接收到的就是undefined
4、函数没有返回值,相当于返回了undefined
5、对象没有这个属性,非要获取这个属性的值,这个属性的值也是undefined
undefined派生于null,引入undefined的目的就是为了和null区分,null一般是主动指定的,undefined则是程序解析得到
Null类型
Null类型只有一个值,那就是null,null表示一个空对象指针,如果定义的变量将用来存储对象,那么最好将它初始化为null(而不是{})
null与{}的区别
1、{}不是一个完全空的对象,原型链上有Object
2、null是原型链的顶端
Object.prototype.__proto__ === null返回true,没有原型链
3、在转化为布尔值时,对于对象而言,只有null会转化为false,其他的[]或{}都会转化为true
Boolean类型
布尔类型只有true和false两个字面量,他们与1和0数字值不是一个概念。在控制流语句(如if)中,会将其他类型的变量转化为布尔类型
其他类型转化为布尔类型
1、undefined转化为false
2、对于数字,只有0和NaN转化为false,其他数字转化为true
3、对于字符串,只有空字符""转化为false,其他非空字符串如"0"转化为true
4、对于对象,只有null转化为false,其他对象包括{}、[]转化为true
Number类型
js的数字是以64位双精度浮点数的形式存储,格式为(s) * (m) * (2^e),s 是符号位,表示正负。m 是尾数,有 52 bits。e 是指数,有 11 bits,e 的范围是 [-1074, 971]
因此Number.MAX_VALUE的值为1 * (Math.pow(2, 53) - 1) * Math.pow(2, 971);Number.MIN_VALUE的值为1 * 1 * Math.pow(2, -1074)
十进制转二进制:
1、整数部分 "除二取余,倒序排列",小数部分 "乘二取整,顺序排列"
2、小数部分转化为2进制:用2X小数,积的整数部分记录,小数部分如果不为0继续X2获取积,直到积的小数部分为0或者达到最大尾数(53位对应到10进制就是17位小数)
3、因为上述原因导致了0.1 + 0.2 === 0.3返回false
科学计数法:使用1.2e-7这种形式表示 代表的涵义为1.2乘以10的-7次方。对于极大或极小数可以精简代码
NaN: 与任意数操作均返回NaN,且NaN不等于NaN本身。唯一的检测手段是isNaN函数
常用属性和方法:
1、Number.NaN、Number.MAX_VALUE、正负无穷大、Number.MIN_VALUE
2、toFixed(2)保留2位小数
3、toString(2)将数字转化为2进制的字符串
4、toPrecision(2)转化为2位科学计数法表示
5、toExponential返回指数表示法
其他类型转Number
Number(a)的转化规则
1、null,返回0
2、undefined,返回NaN
3、不可转化的字符串将返回NaN,可以转化的字符串将被转化为对应的整数或浮点数。其他进制的字符串将被转化为10进制
4、对象,先调用toString转化为字符串(Object的toString返回"[object Object]",而数组则是扁平化),然后转化为数字
parseInt:解析所有数字符号,如果第一个字符不是数字或者正负号,将返回NaN(注意,小数点也不是有效的数字字符)
parseInt("a123") // 返回NaN
parseInt("a123", 16) // 41251进制转化
parserInt("123ab") // 123
parseInt("12.3") // 1
parseInt("07") // 7
parseInt进制转化,第一个参数为字符串,第二个参数代表参数的进制数(默认为10进制);返回转化的10进制数
parseFloat的区别
1、能解析第一个小数点(不能解析第二个)
2、parseFloat没有第二个参数,会忽略前置的0,并对16进制始终返回0
parseFloat("1.23") // 1.23
parseFloat("1.23.4") // 1.23
parseFloat("071") // 71
数据转化
1、显式转化,调用Number、Boolean、parseInt等进行转化
2、隐式转化,程序自己转化
隐式转化:
1、四则运算,特别注意加法,一个是字符串都将转化为字符串返回String;而加法之外的其他运算,一个是数字,都将转化为Number
2、特殊符号,如~等,转化为Number
3、判断语句中的表达式被转化为Boolean
4、宿主原生代码调用,如alert,接受String类型,如果是对象会调用toString返回object,而数组则会返回扁平化的字符串
String类型
字符字面量:也称为转义字符,用于html中正确解析为字符串
字符串的特点-不可变:填充新的字符串销毁老的
let str = "Java"; str = str + "Script"
先创建10个字符的字符串,然后销毁原来的字符串"Java"和"Script"。这是早期字符串拼接慢的原因(现在浏览器已经解决)
转化为字符串:除了null和undefined,基本所有的数据都有toString方法用来转化为字符串
toString可以接受一个参数,转化的进制
let num = 10; num.toString(2); // 将10转化为2机制,得到1010
Object类型
Object
js的对象实质是一组数据和功能的集合,Object具有的所有属性和方法都存在于更具体的对象中
实例共享:
1、构造函数constructor,保存着用于创建对象的函数
2、hasOwnProperty,用于检查给定的属性在当前对象实例中(不包括原型)
3、 parent.isPrototypeOf(child):用于检查传入的对象是否是传入对象的原型
4、propertyIsEnumerable(propertyName) :用于检查给定的属性是否能够使用 for-in 语句 (本章后面将会讨论)来枚举。
5、toString():返回对象的字符串表示"[object, Object]"或者重写的的return值
6、valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString() 方法的返回值 相同。
注意toString方法,可以通过原始Objec的toString来检测数据类型Object.property.toString.call() 而不能直接调用toString,因为派生的如Array等都重写了toString方法,只能得到重写后的结果,而达不到检测数据类型的效果
操作符
针对对象的转化
先调用对象的valueOf方法获得对象的表示值,如果没有valueOf方法,将调用toString方法转化为字符串,如果需要转化为数值,再对字符串Number强转
js操作符包括算术操作符(如加号和减号)、位操作符、关系操作符和相等操作符。js操作符适用如字符串、数字值、布尔值,甚至对象。 不过,在应用于对象时,相应的操作符通常都会调用对象的valueOf()或toString()方法,以便取得可以操作的值
前置与后置递增(减)
1、对于前置操作,变量的值是在求值之前改变的
let a = 20; let b = --a + 1; console.log(a); console.log(b); // 输出19、20,因为a先改变再参与求值
2、对于后置操作,变量在求值之后改变
let a = 20; let b = a-- + 1; console.log(a); console.log(b); // 输出19、21,先求值后改变a
隐式转化:+、-、*、~等
用于隐式转化变量,相当于调用Number(a),将其转化为数字或数字的负数(-)
布尔操作符
1、逻辑非!,隐式转化,相当于Boolean(a)的取非值。我们可以通过!!a来隐式的将a转化为布尔类型(效果等同显式转化Boolean(a))
2、逻辑与&&,必须两者同时为真才返回真。
可以用来简化单个分支语句,if (a) { console.log(1); }可以简化为a && console.log(1);
3、逻辑或||,任一为真就返回真
常用来处理为null或undefined数据设置默认值。let a = obj.a || '默认'; (ES6可以通过解构赋值的形式设置默认)
比如使用后台返回数据的某个字段,如果返回的数据为空,那么拿不到属性而报错
if (res.names) {
res.names.map(..);
}
可以简化为result.name && result.names.map(item => item.name)
4、注意,上述为了避免null和undefined情况,特别注意0的情况,0 || "默认值"将返回默认值,解构赋值则可以避免这个问题(解构赋值使用的是iterator)
*乘法
1、一个操作数为NaN返回NaN
2、无穷大与0相乘,返回NaN;与非0数(包括无穷大)相乘返回无穷大
3、不是数值,将调用Number(a)转化后运算
let arr1 = [1];
let arr2 = [1,2];
let obj = { id: 1 };
arr1*1; // 返回1
arr2*1; // 返回NaN,先转化为字符串"1,2",然后转化为数字NaN
obj*1; // 返回NaN
/除法
1、一个操作数为NaN返回NaN
2、无穷大除以无穷大,返回NaN
3、0除以0返回NaN,无穷大除以0,返回无穷大,有限数除以0返回无穷大
4、不是数值,将调用Number(a)转化后运算
%求模(余数)
规则相当于是做除法
+加法
1、一个操作数为NaN返回NaN
2、正无穷与负无穷相加返回NaN。同正同负返回本身
3、一个操作数是字符串,转为字符串拼接(因此要特别注意加法运算,考虑字符串的情况,可以通过Number显式转化,或-0、~等隐式转化)
-减法
1、一个操作数为NaN返回NaN
2、正无穷减正无穷返回NaN,减其他任意数(包括负无穷)均返回正无穷;负无穷减负无穷返回NaN,减其他任意数(包括正无穷)均返回负无穷
3、不是数字,将调用Number(a)转化后运算
关系操作符
关系操作符包括大于(>)、小于(<)、大于等于(>=)、小于等于(<=)
1、两个操作数都是字符串,比较ASCII编码(区分大小写).
'13' < '3';返回true,比较的是ASCII码
2、一个为数字,另一个不是数字,转化为数字。
'13' < 3;返回false,先转化为Number
3、NaN与任意数值比较均返回false(既不大于也不小于和等于)
'a' > 3; 返回false,'a'转化为Number是NaN,NaN与任意值比较返回false
相等操作符
包括相等( == )、不等(!=)、恒等( === )、不恒等( !== )
对于相等与不等比较
1、类型相同,基本类型,比较值是否相同;对象,比较引用地址是否一致
{ id: 1 } == { id: 1 }返回false,他们引用不同,不是一个对象
2、字符串与数值比较,转化为Number
3、对象与基础类型比较,对象遵循上述转化规则去比较
let a = {}; a == 0;返回false,先valueOf返回{},不等于0
4、null与undefined相等,注意null与undefined在比较相等时不做任何转化,即undefined、null与0、""、false等比较均返回false
5、NaN与任何数据比较相等返回false,比较不等返回true
对于全等于不全等,不仅需要比较数据相等,还需要数据类型保持一致,为了保持代码中数据类型的完整性,我们推荐使用全等和不全等操作符。
undefined == null返回true;但undefined === null返回false,因为类型不同
三元表达式
expression ? trueValue : falseValue
常用来优化条件分支,可以让代码更简洁
let a = [1];
let b = 0;
if (a.length) {
b = 1;
} else {
b = 2;
}
// 使用三元表达式优化
a.length ? b = 1 : b = 2;
总结
1、比较相等,null与undefined相等,但null与undefined不做转化,他们与0相比均不相等
2、对于字符串与数字相等,将转化为数字然后比较,因此0与‘0’比较相等
3、对于条件分支if中的语句,将转化为布尔值,因此通过if(a)的形式,可以排除undefined、null、0、false等情况;但对于'0'、{}、'false'等都将返回true
4、特别注意加法,数字与字符串相加将转化为字符串计算
5、调用toFixed之前一定要注意操作数必须是一个数字,操作前将数据转化为Number,不然将报错
6、尽量使用Number、String、Boolean显式转化,更语义化。但在计算时,可以通过隐式转化来处理
严格模式
为JavaScript容易出错的地方添加了限制
语句
条件分支及循环
1、条件分支if,即使代码只有一句,也不要省略代码块{},这样容易让修改发生错误。如果分支简单,使用&&或三元表达式进行优化
2、while与do-while的区别是do-while语句至少会执行一次,while可能一次都不执行
3、for循环,要么使用倒序for循环(只需要获取一次len),要么缓存len
4、for-in用来枚举对象属性(不包括继承的属性和Symbol),注意,for-in循环的属性没有顺序。如果for-in的是一个null或undefined数据,早期是会抛出错误的,ES6修正了这个问题(不再执行循环),但为了最大兼容性,for-in之前先检测null与undefined
5、循环可以省略var声明i(相当于声明了全局变量),为了保持局部变量,不要省略
6、for循环永远是性能最好的,但==为了可维护性与简洁性,尽量使用ES6自带的处理函数(如map、filter、reduce、find)==
7、更清晰的条件分支switch,结构更清晰,推荐使用switch使用的是全等比较,不会出现类型转化
break、continue、return
1、continue是结束本次循环,继续下一次循环体
2、break是结束循环,循环之后的内容继续执行
3、return则是直接跳出函数,后续的代码都不在执行
尽可能早的break可以优化循环
with
关联了内部变量的对象
with (location) {
// 相当于location.search等获取
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}
with会导致性能问题(延长执行环境),且调试困难,不要使用;严格模式下使用with将直接抛出语法错误
函数
1、让函数始终返回一个值,要么永远不要返回值
// bad:有时返回false,有时不返回
function diff(num1, num2) {
if (num1 < num2) {
return false;
} else {
num1 - num2;
}
}
// 直接return; 将不会返回值
2、没有重载,后面定义的覆盖前面定义的,即使你的参数不同
3、 通过arguments对象来访问这个参数,注意,arguments对象只是与数组类似(它并不是Array的实例,可以通过arguments.length的形式访问)