准备工作
1-参考材料
- 基础
- 进阶
- 现代JavaScript高级教程:序言 | 编程时光 (coding-time.cn)
4-知识大纲(未完)
JavaScript基础
1 入门
1-1 基本语法
- 语法:
- 规则:每个语句以
;结束,语句块用{...},但是不强制要求;
- 注释
1-2 数据类型、变量和运算符
- 有哪些运算符?
- 算数运算符:+、-、*、/、%、++、--
- 赋值运算符:=、+=、-=、*=、/=、%=
- 比较运算符:==、===绝对等于(值和类型同时)、!=、!==、>、<、>=、<=
- 通过比较运算符对Number做比较,得到一个布尔值
==:自动转换数据类型再比较,不推荐使用===:不自动转换数据类型,要求数值和数据类型双一致- 特殊:
- NaN这个特殊的Number与所有其他值都不相等(包括它自己),只能通过
isNaN(NaN);中isNaN函数判断 - 注意浮点数的小数部分,要比较是否相等,只能计算它们之差的绝对值,看是否小于某个阈值
- Number不区分整数和浮点数,即12.00===12
- NaN这个特殊的Number与所有其他值都不相等(包括它自己),只能通过
- 逻辑运算符:&&、||、!
- 条件运算符:
var=(condition)?value1:value2
- 数据类型:
- 有哪些数据类型:
- 基本数据类型(数值):字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
- 三种对象类型object:对象(Object)、数组(Array)、函数(Function),正则(RegExp)、日期(Date)
- Number:不区分整数和浮点数,统一用Number表示;包括整数123、浮点数0、指数1.2345e3、负数-99、无法计算结果NaN、无限大Infinity;
- 字符串:以单引号
'或双引号"括起来的任意文本,''或""本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc'只有a,b,c这3个字符 - 布尔值:一个布尔值只有true、false两种值,支持&&与运算、||或运算、!非运算
- BigInt:表示比
2^53还大的整数,在整数后加一个n;BigInt之间可以加减乘除等,但是不能把一个BigInt和一个Number放在一起运算 - null和undefined:null表示一个空的值,而undefined表示值未定义;
- 0是一个数值,
''表示长度为0的字符串; - undefined仅仅在判断函数参数是否传递的情况下有用,underfined===null=>true
- 0是一个数值,
- 数组:一组按顺序排列的集合,集合的每个值称为元素;元素可以是任意数据类型;用
[]表示,元素之间用,分隔;定义:var arr=[];,通过Array()函数实现,var arr = new Array(1, 2, 3); - 对象:一组由键-值组成的无序集合,每个键又称为对象的属性;键都是字符串类型,值可以是任意数据类型;
对象变量.属性名来获取对象的属性值
- 变量
- 动态语言
- 定义:可以是任意数据类型;
- 语法:只能用var申明一次;
- 不赋值:用
var 变量名;表示,如var a;,值为undefined - 包装对象:用关键词 "new"创建,包括String、Number、Boolean、Array、Object,如
var carname=new String('str');;虽然数值看似一致,但是类型转变为对象; - 赋值:用
=赋值,可以赋值任意数据类型、反复赋值、不同类型 - 变量名:大小写英文、数字、
$和_的组合,不能用数字开头,不能是JavaScript的关键字;
- 不赋值:用
- 打印变量:
console.log('hello'+ x);
- 静态语言
- 定义:定义时必须指定变量类型,如果赋值时类型不匹配,就会报错
- 语法:
类型 变量名 = 值;,如int a = 123;
- 严格模式
- 定义:不用var申明的变量会被视为全局变量
- 语法:
'use strict';
- 类型转换
- 查看变量的数据类型:
- 操作符typeof:
typeof x,NaN 的数据类型是 number,数组(Array)的数据类型是 object,日期(Date)的数据类型为 object,null、[]、{} 的数据类型是 object,undefined的数据类型为 undefined - 函数isNaN():
isNaN(x);,只有一个值是NaN或者能被转换为NaN的时候才返回true- e、NaN:返回true,e可转化
- "11"、null:返回false,因为字符串可以被转换为数字、null可以被转换为0
- 操作符typeof:
- 类型转换 => 类型转换【笔试】
- 数字/布尔/日期转字符串:
String(x);或x.toString() - 字符串/布尔/日期转数字:
Number(x),空字符串转换为 0Number(null)、Number(empty)、Number()、Number('')、Number(false)、Number([])、Number([0])=>0
- Number/字符串转BigInt:
BigInt(x) - 函数parseInt():
parseInt(string);:解析字符串并返回整数(默认十进制),string字符串只会被解析从第一个字符开始直到不是数字的字符部分,当字符串中间存在非数字,那么就只解析前面是数字的部分字符,如果字符串中第一个字符就不是数字,那么返回NaN,如parseInt("1a")=>1parseInt(string,raix);:第二个参数指的就是进制,这个参数小于2或者大于36的时候,都会返回NaN,如果string>raix返回NaN
- 数字/布尔/日期转字符串:
1-3 字符串
- 字符串
- 定义:由一串字符构成
- 语法:
- 新建:用
''或""括起来的字符表示,如var answer = "It's alright";- 包括
'时:用""括起来 - 包括
'和":用转义字符\来标识,\n表示换行,\t表示制表符,字符\本身也要转义,\表示的字符就是\\
- 包括
- 问:用索引位置来访问字符串中的每个字符,索引从 0 开始,如
var char = carname[7];- 无法赋值:字符串不可变,无法对字符串的某个索引赋值,可以调用方法返回一个新字符串
- 新建:用
- 多行字符串:ES6标准新增了一种多行字符串的表示方法,用反引号
...
console.log("多行\n字符串\n测试\n");
console.log(`多行\
字符串`);
- 操作字符串:
var str = 'Hello world!';
- 获取长度:
arr.length;,获取字符串长度 - +运算符:
- 定义:+ 运算符用于把文本值或字符串变量连接起来
- 连接多个字符串/字符串变量:
str3 = str1 + "-连接-" + str2; - 数字与字符串相加:
z="Hello"+5;,返回字符串"Hello5"- console.log(1+ "2"+"2");=>122
- console.log(1+ +"2"+"2")=>32,(+"2")应用了一元加操作符,一元加操作符相当于Number()函数,会将(+"2")转换为2
- console.log("A"- "B"+"2");=>NaN2,调用Number()函数将字符串转换为数字,不能转换则返回NaN,NaN+"2"="NaN2"
- console.log("A"- "B"+2);=>NaN,加法规则中,如果有一个操作数是NaN,则结果为NaN,NaN+2=NaN
- 模板字符串:
- 定义:ES6新增的表示,允许多行字符串、带嵌入表达式的字符串插值和一种叫带标签的模板的特殊结构,允许在字符串中引用变量、执行函数调用和进行任意的JavaScript表达式
- 语法:使用反引号
``作为字符串的定界符分隔- 引用的字符串变量:let message =
${name},今年${age}岁;,用${var}表示 - 调用的函数:let msg =
function:${func()};,用${func()}表示\ - js表达式:let total =
Total: ${(price * (1 + var)).toFixed(2)};
- 引用的字符串变量:let message =
- 连接
concat():str = str1.concat(str2);连接两个或多个字符串 - 大写
toUpperCase():str.toUpperCase(),把字符串全部变为大写 - 小写
toLowerCase():str.toLowerCase(),把一个字符串全部变为小写 - 访问
charAt():str.charAt(0);返回指定位置的字符,索引从0开始 - 搜索
indexOf():str.indexOf('ar')会搜索指定字符串出现的位置,找到了返回位置,没有找到返回-1 - 最后搜索
lastindexOf():str.lastindexOf('ar')会搜索指定字符串最后出现的位置,找到了返回位置,没有找到返回-1 search():str.reach("str");查找子字符串,- 匹配
match():str.match("str")是否有匹配的子字符串,找到了则返回字符串,没有找到返回null;可以match字符串、正则表达式,不可以match变量; - 包括
includes():str.includes("str");,是否包含子字符串,返回true和false;可以includes字符串、变量,不可以includes正则表达式; - 开头
startsWith():str.startsWith("hello"),查看字符串是否以指定的子字符串开头 - 结尾
endsWith():str.endWith("!"),判断字符串是否是以指定的子字符串结尾的(区分大小写),确定返回ture,否则返回false - 修剪trim:
str.trim(); - 替换
replace():用字符替换字符,返回替换字符后的字符串str.replace("str1","str2"):替换指定字符str.replace("str1",""):删除指定字符
- 替换所有
replaceAll():查找匹配的子串,并替换与正则表达式匹配的所有子串 - 拆分
split():str.split(",");字符串以逗号分隔,根据逗号将字符串转为数组 - 子串
substring():返回指定索引区间的子串str.substring(a,b)返回指定索引区间[a,b)的子串str.substring(a)返回索引[a,结束]的子串
1-4 数组
- 数组
- 定义:可以包含任意数据类型
- 语法:
- 新建:
var arr=[1,'a','-']或var arr= new Array;或var arr= new Array(1,'a','-'); - 访问:
arr[0],通过索引来访问每个元素,索引从0开始\ - 赋值:
arr[0] = 1;,通过索引把对应的元素修改为新的值,会直接修改这个Array;索引超过了范围,会导致Array大小的变化,剩下值为underfined
- 新建:
- 多维数组:
- 定义:
arr=[[1,2,3],['a','b','c'],'-'] - 访问:
arr[0][0]
- 定义:
- 操作:数组内元素 => 数组常用方法【面试】
- 常见操作:
- 长度length:
var l = arr.length;取得Array的长度,给arr.length赋一个新的值会导致Array大小的变化,剩下值为underfined - 搜索
indexOf():arr.indexOf()来搜索一个指定的元素的位置,找到了返回索引,没找到返回-1;indexOf(10)和indexOf('10'),数字和字符串是不同的元素; - 末尾添加
push():arr.push('a','b'),数组的末尾增加a和b,此时数组长度+2 - 末尾删除
pop():arr.pop(),删除并返回数组的最后一个元素,此时数组长度-1;弹完了所有元素时返回arr为[],即返回underfined - 开头添加
unshift():arr.unshift('a','b'),Array的头部增加a和b,数组长度+2 - 开头删除
shift():arr.shift(),把Array的第一个元素删掉,数组长度-1,删除所有元素后arr为[]返回undefined - 排序
sort():arr.sort(),按照默认顺序排序,会直接修改当前Array的元素位置 - 反转
reverse():arr.reverse(),把整个Array的元素给调个个,也就是反转 - 切片
slice():对应String的substring(),它截取Array的部分元素,然后返回一个新的Arrayarr.slice():从头到尾截取所有元素,即复制一个arr\arr.slice(a):从索引a开始到结束arr.slice(a,b):[a,b),不包括结束索引
- 增删
splice():修改Array的“万能方法”,从指定的索引开始删除若干元素,然后再从该位置添加若干元素arr.splice(a,b,'c','d'):又删又添,从索引a开始删除b个元素,在删除的位置上添加c和d两个元素,返回删除的b个元素\arr.splice(a,b):只删除不添加,从索引a开始删除b个元素,返回删除的b个元素arr.splice(a,0,'b','c'):只添加不删除,从索引a开始添加b和c两个元素,因为无删除所以返回[]
- 连接
concat():尾部链接另一个数组arr.concat(array);:在当前的Array的尾部链接另一个Array,并返回一个新的Arrayarr.concat([1,2,['a','b']]):在arr末尾增加1,2,'a'和'b'四个元素;可以接收任意个元素和Array,并且自动把Array拆开,然后全部添加到新的Array里
- 串联
join():arr.join('-'),把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串;元素不是字符串时将自动转换为字符串后再连接
- 长度length:
- 分类 => 不更改原数组的操作方法【笔试】
- 会改变原数组:push()、pop()、shift()、unshift()、splice()、sort()、reverse()、forEach()
- 不改变原数组:filter()、concat() 、slice()、map()
1-5 对象
- 对象
- 定义:无序的集合数据类型,它由若干键值对组成,用于描述现实世界中的某个对象;所有属性都是字符串,属性对应的值可以是任意数据类型;
- 语法:
- 新建:
var obj = { prop:xxx },{...}表示一个对象,键值对以xxx: xxx形式申明,用,隔开,最后一个键值对不需要在末尾加,- 包含特殊字符的非有效变量名:必须用
''括起来,如'middle-school'
- 包含特殊字符的非有效变量名:必须用
- 访问属性:返回对应属性值,如果不存在不报错,返回undefined
- 有效属性名:
obj.prop,通过.操作符完成的; - 非有效属性名:必须用
obj['xxx'],如obj['middle-school'];
- 有效属性名:
- 赋值:
obj.prop = 'xxx';,对象是动态类型,可以修改属性值
- 新建:
- 操作
- 新增属性:
- 新增属性:
obj.prop;,值为undefined - 新增键值对:
obj.prop = 'xxx';,新增属性和对应值
- 新增属性:
- 删除属性delete:
delete obj.prop;,删除不存在属性不报错 - 检查属性:
- 操作符in:
'prop' in obj;,用in操作符检查prop属性是否存在obj中,存在返回true,错误返回false;用in判断属性存在,属性不一定是obj的,它可能是obj继承得到的,如所有对象最终都会在原型链上指向object,所以obj也拥有toString属性(toString定义在object对象中) - 自身拥有
hasOwnProperty():obj.hasOwnProperty('prop');,检查属性是否是obj自身拥有,而不是继承
- 操作符in:
1-6 条件
- 条件判断
- 语法:如果某个条件成立,则后续就不再继续判断了
- 一行:if() { ... }
- 两行:if() { ... } else { ... }
- 三行:if() { ... } else if() { ... } else { ... }, - 注意:
- 条件判断:如果()的判断结果不是true和false,把null、undefined、0、NaN和空字符串''视为false,其他值一概视为true
- 建议永远都要写上{}:如果语句块只包含一条语句,那么可以省略{},但是如果想添加语句却忘了写{},就改变了if...else...的语义,因为if和else控制范围只有一行
1-7 循环
- 定义:通过初始条件、结束条件和递增条件来循环执行语句块
- for循环:
- 语法:
for(i=1; i<=10000; i++) { x = x + i; },3个条件都是可以省略的,如果没有退出循环的判断条件,就必须使用break语句退出循环;注意初始条件和判断条件,尤其是边界值,如i < 100和i <= 100 - 遍历数组的所有索引:
- 语法:for(i=0;i<arr.length;i++) { x = arr[i]; }
- 注意:逆序需要将数组通过arr.reverse()调换顺序后再正序遍历 for...in
- 遍历对象的所有属性:for(var key in obj) { ... }
- 遍历数组的所有索引/属性:for(var i in arr){ consloe.log(i,a[i];) }
- 原因:数组也是对象,每个元素的索引被视为对象的属性
- 注意:遍历数组后得到的元素是string不是number,即'2'
- while循环
- 和for循环区别:for循环在已知循环的初始和结束条件时非常有用。而上述忽略了条件的for循环容易让人看不清循环的逻辑,此时用while循环更佳
- 概念:只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环
- 语法:
while(n>0){...}
do...while循环
- 和while循环的区别:不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件
- 语法:
do { ... } while(n>0)
- 注意:循环体会至少执行1次,而for和while循环则可能一次都不执行
1-8 Map和Set
- 由来:js对象的键必须是字符串,为了让number和其他数据类型作为键,最新的ES6规范引入了新的数据类型Map
- Map
- 定义:Map对象是一组键值对的结构,键可以为任何类型。Map保存键值对,并且能够记住键的原始插入顺序,具有极快的查找速度。
- 语法:
- 新建:var m = new Map();或var m = new Map([[k1,v1],[k2,v2],...,[kn,vn]]),键和值可以为任何类型 - 操作:
- 添加键值:m.set(key, value);,多次在一个key上重复添加,后面的值会冲掉前面的值
- 搜索键:m.has(key);,返回true和false
- 获取值:m.get(key);,如果存在值返回值,不存在返回underfined
- 删除键:m.delete(key);
- Set
- 定义:Set对象是一组不重复的键构成,没有值,键为任意类型
- 语法:
- 新建:var s1 = new Set();或var s2 = new Set([1, 2, 3]);
- 注意:①重复元素在Set中自动被过滤;②数字3和字符串'3'是不同的元素; - 方法:
- 添加元素:s.add(key);,可以重复添加但不会有效果
- 删除元素:s.delete(key);
1-9 iterable
- iterable object可迭代对象
- 定义:数组是可迭代的,为了迭代任何对象,S6标准引入了新的iterable类型,具有iterable类型的集合可以通过
for...of循环来遍历。可迭代(Iterable) 对象是数组的泛化。 - 常见:Array、Map和Set
for...of
- 语法:
- 遍历:for(var i of xxx) {...}
- 遍历数组的元素:for(var i of arr) { return i,arr[i]; }
- 遍历set的元素:for(var x of s) { return x; }
- 遍历map的键和值:for(var x of m) { return x[0],x[1]; },x[0]为键,x[1]为值,因为Map是用多维数组表示的
- for of和for in的区别:
- for in(index):由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。如果手动给Array对象添加了额外的属性后,for ... in循环将把新添加的属性包括在内,但是继承的length属性不包括在内。
- for of(value):遍历集合本身的元素,如数组元素而不是索引foreach()方法:
- 定义:iterable内置的forEach方法接收一个函数,每次迭代就自动回调该函数
- 语法:
- 调用:xxx.forEach(function (element, index, array) { ... });
- 数组:element指向当前元素的值,index指向当前索引,array指向Array对象本身
arr.forEach(function (element, index, array) { console.log(element + ':' + index); });
- Set:Set没有索引,因此回调函数的前两个参数都是元素本身
s.forEach(function (element, sameElement, set) { console.log(element);});,
- Map:Map的回调函数参数依次为value、key和map本身
m.forEach(function (value, key, map) { console.log(value);});,
- 省略:如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们,如arr.forEach(function(element) {...})
2-函数
2-1 函数定义和调用
- 函数
- 定义:js的函数是一个对象,定义一个函数func实际上是一个函数对象,函数名func可以视为指向该函数的变量
- 语法:
- 新建函数:function func(var1,var2) { ...return xxx; },分别为函数名、参数、函数体
- 返回return:函数体一旦执行到return时,函数就执行完毕,并将结果返回;如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined;注意return后跟{或者;
- 新建函数对象:var func = function(var1,var2) { ... };,function(var1,var2)为匿名函数没有函数名,通过赋值给了变量func,通过变量func就可以调用该函数;需要在函数体末尾加一个;,表示赋值语句结束
- 调用:func(var1,var2)顺序传参,传入参数多了不报错,只传入要求数目的参数,传入参数少了返回NaN
- func(x)和func(1,2,3):只传入1,但可以用arguments获得所有参数组成的数组
- func(x1,x2)和func(1,2):传入x1和x2的值1和2,但是作为变量无法遍历
- arguments关键词:
- 由来:由于JS函数允许接收任意个参数,不得不用arguments来获取所有参数
- 定义:关键字arguments只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array,但它不是一个Array
- 用法1:可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值,如
function abs() {...} - 用法2:最常用于判断传入参数的个数
- rest参数:
-
由来:为了获取除了已定义参数a、b之外的参数rest,使用arguments获取参数,并且循环要从索引2开始以便排除前两个参数,ES6标准引入了rest参数
-
定义:传入的参数先绑定a、b,多余的参数以数组形式交给变量rest,所以不再需要arguments我们就获取了全部参数
-
语法:
function func(a, b, ...rest) {...}
- 多参数调用:除了a和b其他的都是rest数组
- 少参数调用:定义的b为underfined,rest为空数组
2-2 变量作用域与解构赋值
- 变量作用域
- 单函数:一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量
- 双函数但独立:两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响
- 双函数但嵌套:
- 定义不同变量:内部函数可以访问外部函数定义的变量,反过来则不行
- 定义重名变量:内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量,因为JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。
- 变量提升和变量声明
- 定义:JavaScript的函数定义会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但是只自动提升变量的声明,不会提升变量的赋值
- 关键词var:申明局部作用域的变量 - 注意:请严格遵守“在函数内部首先申明所有变量”这一规则 - 语法:用一个var申明函数内部用到的所有变量
function func() {
var
x = 1, // x初始化为1
y = x + 1, // y初始化为2
z, i; // z和i为undefined
//其他语句
}
- 缺点:在for循环等语句块中无法定义具有局部作用域的变量,即`for (var i=0; i<100; i++) {}`中定义的变量i在外围的函数中仍然可以使用
- 关键词let:申明块级作用域的变量
- 定义:ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量,此时for循环等语句块定义的变量无法在局部作用域中使用
- 语法:for (let i=0; i<100; i++) {...} - 关键词const:申明块级作用域的常量
- ES6之前:用全部大写的变量来表示,如var PI = 3.14;
- 定义:ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域
- 语法:const PI = 3.14;
- 全局作用域
- 全局作用域:
- 全局变量:不在任何函数内定义的变量就具有全局作用域。
- 全局函数:以变量方式var foo = function () {}定义的顶层函数实际上也是一个全局变量,并绑定到window对象 - 访问:JavaScript默认只有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性
- 全局变量:直接访问全局变量course等价于 访问window.course
- 全局函数:直接调用foo()等价于 通过window.foo()调用
- 访问失败:JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。
- 名字空间
- 由来:全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
- 名字空间:把自己的所有变量和函数全部绑定到一个全局变量中
- 语法:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
- 解构赋值
- ES6之前:把一个数组的元素分别赋值给若干变量,需要逐一对变量赋值
- 定义:ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值,直接对多个变量同时赋值,目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等
- 数组:获取数组内的元素
- 语法:var [x, y, z] = ['hello', 'JavaScript', 'ES6'];,对数组元素进行解构赋值时,多个变量要用[...]括起来
- 数组内有嵌套:let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];,嵌套层次和位置要保持一致
- 忽略部分元素:let [, , z] = ['hello', 'JavaScript', 'ES6'];,忽略前两个元素,只对z赋值第三个元素 - 对象:从一个对象中取出若干属性,便于快速获取对象的部分指定属性
- 变量名和属性名一致:var {name, age, passport} = person;,存在属性则返回属性值,不存在则返回undefined
- 变量名和属性名不一致:let {name, passport:id} = person;,passport不是变量,而是为了让变量id获得passport属性
- 添加默认值:var {name, single=true} = person;,如果person对象没有single属性,默认赋值为true,避免了不存在返回undefined
- 对象内有嵌套:var {name, address: {city, zipcode}} = person;,要保证对应的层次是一致的
- 注意:address不是变量,而是为了让city和zipcode获得嵌套的address对象的属性,如果获取address会报错(Uncaught ReferenceError: address is not defined)
- 注意:变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误,因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法
- 错误:
{x, y} = { name: '小明', x: 100, y: 200};
- 正确:
var x, y;
({x, y} = { name: '小明', x: 100, y: 200});
- 使用场景:
- 交换变量:[x, y] = [y, x]
- 获取当前页面的域名和路径:var {hostname:domain, pathname:path} = location;
- 如果一个函数接收一个对象作为参数,直接把对象的属性绑定到变量中:function buildDate({year, month, day, hour=0, minute=0, second=0}) {}
2-3 方法
- 方法
- 定义:绑定到对象上的函数称为方法
- 语法:
- 新建:
var obj = {
var: 'xxx',
method: function () {
return this.var; //this指向obj
}
};
- 调用:
obj.method();,由于method内含有this指向obj,必须通过对象的方法形式调用
- this
- 以对象的方法形式调用:函数的this指向被调用的对象obj
- 单独调用函数/函数内部定义的函数:ECMA决定,在strict模式下让函数的this指向undefined,非strict模式下让函数的this指向全局对象,也就是window
- 用that变量捕获this:
var that = this; // 在方法内部一开始就捕获this - apply方法 => apply和call【笔试】
- 定义:用于控制this的指向
- 语法:
return func.apply(obj, arguments);,函数本身的apply方法它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是数组Array,表示函数本身的参数 - 用apply修复函数内的this问题:
func.apply(obj, []);,此时this指向obj, 参数为空 - 动态改变函数的行为:
return func.apply(null, arguments);,返回/调用普通函数
- call方法:
- 定义:和apply作用类似,用于控制this的指向
- 语法:
Math.max.call(null, 3, 5, 4);,对普通函数调用通常把this绑定为null
- apply和call的区别:
Math.max.apply(null, [3, 5, 4]);:apply()把参数打包成Array再传入(记忆:apply和array都是a开头)Math.max.call(null, 3, 5, 4);:call()把参数按顺序传入
2-4 高阶函数
- 高阶函数:
- 定义:为了让函数的参数能够接收别的函数,而高阶函数就可以接收另一个函数作为参数
- Array对象提供了很多常见:map()/reduce()、filter()、sort()、Array()、every()、find()、findIndex()、foreach()
map()和reduce()
map()
- 定义:定义在JavaScript的Array中,可以传入一个函数作为参数,对数组的每一个元素进行函数对应操作,再返回操作后的数组
- 语法:var results = arr.map(func);,function func(x) { return x * x; },此时func的参数x就是arr的每一个元素
- 把Array的所有数字转为字符串:arr.map(String);
- 把数组内的字符串变成证书:arr.map(Number);reduce()
- 定义:Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算
- 理解:[x1, x2, x3, x4].reduce(func) = func(func(func(x1, x2), x3), x4)
- 语法:arr.reduce(function (x, y) {...}
- 数组元素只有1个:要提供一个额外的初始参数以便至少凑够两
var arr = [123]; arr.reduce(function (x, y) {return x + y; }, 0);
- 数组元素求和:
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; //x*y求积;});- 错题:
- 利用map和reduce操作实现一个string2int()函数:把一个字符串13579先变成Array——[1, 3, 5, 7, 9],再利用reduce()就可以写出一个把字符串转换为Number的函数
'use strict'; function string2int(s) { return s.split("").map( function( x ){ return x * 1 }).reduce( function( x , y ){ return x * 10 + y });
- 把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']。
function normalize(arr) { return arr.map((item) => item.charAt(0).toUpperCase() + item.slice(1).toLowerCase());}
filter()
- 定义:用于把Array的某些元素过滤掉,然后返回剩下的元素。Array的filter()也接收一个回调函数,把传入的函数依次作用于每个元素,然后根据返回值true保留元素,返回值false丢弃该元素。
- 回调函数的多参数:通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身
var arr = ['A', 'B', 'C']; var r = arr.filter(function (element, index, self) { console.log(element); // 依次打印'A', 'B', 'C' console.log(index); // 依次打印0, 1, 2 console.log(self); // self就是变量arr return true; }); - 删掉偶数保留奇数:
arr.filter(function (x) {return x % 2 !== 0;}); - 删掉空字符串:
arr.filter(function (s) {return s && s.trim();}); - 去除重复元素:
r = arr.filter(function (element, index, self) {return self.indexOf(element) === index;});
sort()
- 默认排序规则:
arr.sort();
- 字符串根据ASCII码由小到大排序:大写字母>小写字母
- 默认把所有元素先转换为String再排序:1>10>2>20,无法进行数字排序 - 自定义排序:
- 语法:arr.sort(function (x, y) {});,对于两个元素x和y,根据对比函数,返回-1的放前面,返回1的放后面
every()
- 作用:判断数组的所有元素是否满足测试条件,满足返回true,不满足返回false
- 存在字符串:
console.log(arr.every(function (s) { return s.length > 0; })); - 元素全部是小写:
console.log(arr.every(function (s) { return s.toLowerCase() === s; }));
find()
- 作用:用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
- 寻找全部小写的元素:
console.log(arr.find(function (s) { return s.toLowerCase() === s; }));
- findIndex()
- 查找符合条件的第一个元素,和find()不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1
- 寻找全部小写的元素的索引:
console.log(arr.findIndex(function (s) { return s.toLowerCase() === s; }));
- foreach()
- 作用:把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组,因此,传入的函数不需要返回值
- 依次打印数组元素:
arr.forEach(console.log);
2-5 闭包
- 闭包
- 定义:将函数作为返回值的函数为闭包,返回的内部函数可以保存、引用外部函数的参数和局部变量
- 语法:
- 新建:
function return_func(arr) { var func = function () { return arr;} return func; }
- 调用:var f = return_func(arr);,arr保存在f中,每次调用都会返回一个新的函数,即使传入相同的参数
- 运行:f();
2-6 箭头函数
- 箭头函数:
- 定义:ES6标准新增了Arrow Function(箭头函数),定义用的就是一个箭头。箭头函数相当于匿名函数(但不是,区别在this),并且简化了函数定义。
- 语法:
- 基本:
const func = (parameters) => { // 函数体 } - 参数:
- 无参数:
() => 3.14 - 单参数:
parameter => expression;,省略() - 可变参数:
(x, y, ...rest) => {...} - 多参数:
(x, y) => x * x + y * y,用括号()括起来
- 无参数:
- 函数体:
- 单语句:
parameter => expression;,省略{...}和return,如x => x * x; - 多条语句:
x => { if (x > 0) { return x * x; } },不能省略{ ... }和return
- 单语句:
- 注意:返回一个对象的单表达式要加括号,如
x => ({ foo: x })
- 基本:
- 箭头函数中的this变量
- 定义:箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this。
- 理解:箭头函数没有自己的
this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变。 - 特点:
- .call()、.apply()、.bind()无法改变箭头函数中this的指向:.call()、.apply()、.bind()方法可以用来动态修改函数执行时this的指向,但由于箭头函数的this定义时就已经确定且永远不会改变
- 箭头函数不能作为构造函数使用:
- 用new构造函数:JS内部首先会先生成一个对象;再把函数中的this指向该对象;然后执行构造函数中的语句;最终返回该对象实例。
- 因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错
- 箭头函数没有自己的arguments对象:在箭头函数中访问arguments实际上获得的是外层(函数)执行环境中的arguments对象,如果没有就会报错,可以在箭头函数中使用rest参数代替
- 箭头函数没有原型prototype,不能用作Generator函数,不能使用yeild关键字
- this整理 => this指向及其实际场景【面试】 & this的指向【笔试】
- 指向问题
- 普通函数:this指向词法作用域,即指向其函数的外层直接调用者obj
- 箭头函数:this指向其定义环境,任何方法都改变不了其指向,如call()、bind()等
- 构造函数:如果不使用new,则this指向window,如果使用new创建了一个实例,则this指向该实例
- window内置函数:如setInterval,setTimeout等,其内部的this指向Window。
- 匿名函数:this指向Window
- 使用场景:
- 全局中的this:当this不在任何函数内部时,this始终是指向全局对象,如浏览器中的window、Node.js中的Global;
- 普通函数中的this:始终指向全局对象,因为函数调用
func()相当于window.func()相当于window调用的,所以函数中的所有this都指向window - 对象方法中的this【调用者】:始终是指向调用该方法的对象;因为
对象.方法相当于用对象调用的 - 构造函数中的this:始终指向new出来的实例对象
- 箭头函数中的this【上下文】:继承它所在上下文父级的this
var a='w'
let obj={
a:'o',
print: {
console.log(this.a);
},
print2: () => {
console.log(this.a);
}
}
let p = obj.print; //
let p2 = obj.print2;
obj.print(); //调用的是对象方法中this,指向调用者,输出'o'
obj.print2(); //调用的是对象方法中箭头函数的this,指向上下文(全局作用下window),输出'w'
p(); //p是全局作用域的,p指向print普通函数的地址,在全局下调用,所以this指向window,输出'w'
p2(); //p2是全局作用域的,p2指向print2箭头函数的地址,所以this指向print2定义的上下文,输出'w'
- 箭头函数和普通函数的区别 => es6的箭头函数和普通函数的区别【面经】
- this:
- 普通函数中的this总是指向调用它的那个对象;
- 箭头函数没有自己的this,他的this永远指向其定义环境,任何方法都改变不了其指向,如call()、bind()、apply()。(正是因为它没有this,所以也就不能用作构造函数,也没有原型对象)
- 构造函数&原型对象:
- 普通函数可以当做构造函数,用new命令;
- 因为箭头函数没有this,所以不能当作构造函数,会报错;不能使用yield命令,因此箭头函数不能用作genertor函数;箭头函数没有原型属性
- argument对象:箭头函数不能使用argument对象,该对象在函数体内不存在,可以用rest参数代替
- 变量提升:由于js的内存机制,函数的级别最高,会把所有申明的变量“提升”到函数顶部;箭头函数用var定义的变量不能得到变量提升,所以定义要先于调用;
2-7 标签函数
- 标签函数
- 定义:模板字符串除了方便引用变量构造字符串外,还可以使用标签函数;标签函数和普通函数的定义区别仅仅在于参数
function update(strings, ...exps) {
let sql = strings.join('?');
}
- 语法:
- 定义:
- strings:字符串数组,即除去
${xxx}剩下的字符组成的数组 - ...exps:可变参数,它接收的也是一个数组,但数组的内容是由模板字符串里所有的
${xxx}的实际值组成
- strings:字符串数组,即除去
- 调用:
updateUPDATE users SET age={score} WHERE id=${id};- update为标签函数
UPDATE users SET age= ..., score=... WHERE id=为字符串${age}、${score}、${id}为变量
- 定义:
2-8 generator
- generator
- 定义:generator(生成器)是ES6标准引入的新的数据类型,类似函数,但是可以返回多次。
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
- 语法:
- 新建generator:
- 创建generator对象:
var f = foo(5); - 调用:
- 调用generator对象的next()方法:
f.next();,当执行到done为true时,不再继续调用next()了 - 用for ... of循环迭代:
- 调用generator对象的next()方法:
3-标准对象
3-1 Date
- 时间戳:表示从1970年1月1日零时整的GMT时区开始的那一刻,到现在的毫秒数。假设浏览器所在电脑的时间是准确的,那么世界上无论哪个时区的电脑,它们此刻产生的时间戳数字都是一样的,所以,时间戳可以精确地表示一个时刻,并且与时区无关。
- Date:
- 定义:Date对象用来表示日期和时间;
- 语法:
- 获取系统本地时间:var d = new Date();
- 创建指定日期时间:
-var d = new Date(2015, 5, 19, 20, 15, 30, 123);:2015年6月19让你20点15分30秒123毫秒,返回Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
-var d = Date.parse('2015-06-24T19:49:22.875+08:00');:返回时间戳1435146562875;使用Date.parse()时传入的字符串使用实际月份01-12
- 时间戳转data:var d = new Date(1435146562875);,转换为Date对象后getMonth()获取的月份值为0~11 - 方法:
- 获取年份getFullYear:now.getFullYear(),返回年份数字,如2024
- 获取月份getMonth:now.getMonth();,返回月份数字,范围是0-11
- 获取日期getDate:now.getDate();,返回日期数字,如31
- 获取周几getDay:now.getDay();,返回周几数字,范围1-7,如7
- 获取小时getHours:now.getHours();,返回小时数字,范围1-24,如19
- 获取分钟getMinutes:now.getMinutes();,返回分钟数字,范围1-60,如59
- 获取秒数getSeconds:now.getSeconds();,返回秒钟数字,范围1-60,如59
- 获取毫秒数getMilliseconds:now.getMilliseconds();,返回毫秒数,范围1-1000
- 获取时间戳getTime:now.getTime();,返回数字形式的时间戳,如1435146562875
- 显示本地时区时间toLocaleString:now.toLocaleString();,返回2015/6/24 下午7:49:22,本地时间为北京时区+8:00,显示的字符串与操作系统设定的格式有关
- 显示调整后的UTC时间:now.toUTCString();,返回Wed, 24 Jun 2015 11:49:22 GMT,UTC时间与本地时间相差8小时
3-2 RegExp
- RegExp
-
定义:正则表达式用来匹配字符串,用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
-
用字符描述字符
- 精准匹配:直接给出字符
- 非精准匹配:
- 字符类型:\d可以匹配一个数字,\w可以匹配一个字母或数字,.可以匹配任意字符,\s可以匹配一个空格(也包括Tab等空白符)
- 字符长度:*表示任意个字符(包括0个),+表示至少一个字符,?表示0个或1个字符,{n}表示n个字符,{n,m}表示n-m个字符
- 进阶:
- [0-9a-zA-Z_]可以匹配一个数字、字母或者下划线
- [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串
- [a-zA-Z_$][0-9a-zA-Z_$]*可以匹配由字母或下划线、组成的字符串
- [a-zA-Z_$][0-9a-zA-Z_$]{0, 19}更精确地限制了变量的长度是1-20个字符
- A|B可以匹配A或B,^表示行的开头,^\d表示必须以数字开头,表示必须以数字结束。 -
语法:
- 新建:var re1 = /ABC\-001/;,直接通过/正则表达式/写出来;var re2 = new RegExp('ABC\\-001');,通过new RegExp('正则表达式')创建一个RegExp对象
- 检测匹配:re.test('010-12345');,RegExp对象的test()方法用于测试给定的字符串是否符合条件。 -
操作:
3-3 JSON
- JSON
- 定义:JavaScript Object Notation的缩写,它是一种超轻量级的数据交换格式,是JavaScript的一个子集。
- 理解:
- 发送:把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。
- 接收:如果我们收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象,就可以在JavaScript中直接使用这个对象了。
- 序列化:对象Object
- 语法:var s = JSON.stringify(obj);
- 换行缩进:JSON.stringify(obj, null, ' ');,每个属性结束会自动换行
- 指定输出属性:JSON.stringify(obj, ['name', 'age'], ' ');,只输出name和age属性
- 处理键值对后输出:JSON.stringify(obj, convert, ' ');,convert函数把属性值转化为大写形式
function convert(key, value) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
- 精准序列化:在对象内定义toJSON()的方法,直接返回JSON应该序列化的数据,JSON.stringify(obj);
var obj = {
name: 'zhf',
toJSON: function () {
return { // 只输出name和age,并且改变了key:
'Name': this.name,
'Age': this.age
};
}
};
- 反序列化
- 语法:拿到一个JSON格式的字符串,直接用JSON.parse()把它变成一个JavaScript对象
4-面向对象编程
- 面向对象:
- 类:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生;
- 实例:实例是根据类创建的对象,例如,根据Student类可以创建出xiaoming、xiaohong等多个实例,每个实例表示一个具体的学生,全都属于Student类型。 - JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。JavaScript的原型链和Java的Class区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。
4-1 创建对象
- 原型链
- 定义:JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。
- 访问过程:用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。
- 数组arr的原型链:arr ----> Array.prototype ----> Object.prototype ----> null
- Array.prototype定义了indexOf()、shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。 - 函数func的原型链:func ----> Function.prototype ----> Object.prototype ----> null
- Function.prototype定义了apply()等方法,因此,所有函数都可以调用apply()方法
- 构造函数
- 定义:除了直接用{ ... }创建一个对象外,还可以用构造函数来创建对象
- 语法:
- 定义构造函数:
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
- 关键词new调用函数:
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
- 注意:
- 不写new,这就是一个普通函数,它返回undefined;写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;。
- 构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new。
- 原型链:xiaoming ----> Student.prototype ----> Object.prototype ----> null
-
理解:
- 红色箭头是原型链。
- Student.prototype指向的对象就是xiaoming、xiaohong的原型对象,这个原型对象自己还有个属性constructor,指向Student函数本身。
- 函数Student恰好有个属性prototype指向xiaoming、xiaohong的原型对象,但是xiaoming、xiaohong这些对象可没有prototype这个属性,不过可以用__proto__这个非标准用法来查看。 -
共享函数:
- 由来:构造函数中定义了函数,通过new创建的多个对象对应的方法是不同的
- 语法:
- 定义构造函数:
function Student(name) { this.name = name; }
- 把函数定义在对象共同的原型上:
Student.prototype.func = function () { alert('Hello, ' + this.name + '!'); };
- 用new创建对象
4-2 原型继承
- 原型继承
- 定义:基于类的语言中,继承的本质是扩展一个已有的Class,并生成新的Subclass。
4-3 class继承
- 类class
- 定义:从ES6开始class正式被引入到JavaScript中
- 语法:
- 定义:
class Student{...} - 创建:
var xiaoming = new Student('小明'); - 调用:
xiaoming.hello();
- 定义:
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
- constructor()方法
- getter取值函数:当类中一个属性只有get()方法而无set()方法时,该属性是无法进行赋值的,连构造方法中的初始化都不行,因此,当对象的price属性在构造方法中进行初始化,会抛出异常
- setter存值函数
5-浏览器
5-1 浏览器对象
- 浏览器对象
- 定义:JavaScript可以获取浏览器提供的很多对象,并进行操作。
- 常见浏览器对象:window、navigator、screen、location、document、history
- window窗口
- 定义:window对象不但充当全局作用域,而且表示浏览器窗口。
- 常见属性:
- 内部宽度innerWidth、内部高度innerHeight:
window.innerWidth,获取浏览器窗口的内部宽度和高度,指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽、高,所以放大、缩小页面都会影响内宽高 - 外宽outerWidth、外高outerHeight:
window.outWidth,获取浏览器窗口的整个宽高
- 内部宽度innerWidth、内部高度innerHeight:
- 常见方法:
- 打开窗口open:
window.open(URL,name),用于打开一个新的浏览器窗口或查找一个已命名的窗口,参数1(URL)指定打开的页面URL,如果没有指定URL,则打开一个新的空白窗口,参数2(name)指定窗口的名称
- 打开窗口open:
- navigator导航
- 定义:navigator对象表示浏览器的信息
- 常见属性:
- 浏览器名appName:navigator.appName,返回浏览器名称,如Netscape
- 浏览器版本appVersion:navigator.appVersion,返回浏览器版本
- 浏览器语言language:navigator.language,返回浏览器设置的语言,如zh-CN
- 浏览器系统platform:navigator.platform,返回操作系统类型,如Win32
- 浏览器用户userAgent:navigator.userAgent,返回浏览器设定的User-Agent字符串。
- screen屏幕
- 定义:screen对象表示屏幕的信息
- 常见属性:
- 屏幕宽度width:screen.width,返回屏幕宽度,以像素为单位,如1920
- 屏幕高度height:screen.height,返回屏幕高度,以像素为单位,如1080
- 颜色位数colorDepth:screen.colorDepth,返回颜色位数,如8、16、24。
- location地址
- 定义:location对象表示当前页面的URL信息,如www.example.com:8080/path/index.…
- 常见属性:
- href:location.href,获取完整的URL信息
- protocol:location.protocol
-location.host,返回
- port:
- pathname
- search
- hash - 常见方法:
- 加载新页面assign():location.assign('url');
- 重载当前页面reload():location.reload();
6. document
- 定义:document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。要查找DOM树的某个节点,需要从document对象开始查找。
- 常见属性:
- 标题title:document.title,返回标题,是从HTML文档中的xxx读取的,可以动态改变
- 登录信息cookie:document.cookie,获取当前页面的Cookie
- 子节点:document.children,数组形式获取所有子节点,索引从0开始 - 常见方法:
- ID查找DOM节点getElementById():document.getElementById('idname');
- Tag Name查找DOM节点getElementByTagName():document.getElementByTagName('dt'); - Cookie:
- 定义:Cookie是由服务器发送的key-value标示
- 区分用户:因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。当一个用户成功登录后,服务器发送一个Cookie给浏览器,例如user=ABC123XYZ(加密的字符串)...,此后,浏览器访问该网站时,会在请求头附上这个Cookie,服务器根据Cookie即可区分出用户。
- 存储信息:存储网站的一些设置,例如,页面显示的语言等等。
- 安全隐患:引入的第三方js代码如果存在恶意代码,可以直接获取网站的用户登录信息。服务器在设置Cookie时可以使用httpOnly,设定了httpOnly的Cookie将不能被JavaScript读取。
- history
- 定义:history对象保存了浏览器的历史记录,但是任何情况都不应该使用history这个对象了
- 常见方法:
- 后退back():history.back();,相当于用户点击了浏览器的“后退”或“前进”按钮。
- 前进forward():history.forward();
5-2 操作DOM
- 操作DOM
- 定义:由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。始终记住DOM是一个树形结构。
- 获取节点 => 获取DOM节点【笔试】
- 定义:在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。
- 常见方法:
- id属性getElementById:
var id = document.getElementById('id'),仅返回一个匹配元素(id="id"),因为ID在HTML文档中是唯一的,用ID可以直接定位唯一的一个DOM节点;注意括号内不用加#,因为此方法决定了括号中的值是一个元素的id值 - class属性getElementsByClassName:
document.getElementsByClassName("box");,返回匹配的一组元素(class="box");注意括号内不用加.,因为此方法决定了括号中的值是一个元素的class值; - 标签名getElementsByTagName:
document.getElementByTagName('input')[0],返回匹配的一组元素(<input ...>) - name属性getElementsByName:
document.getElementsByName("user");,返回匹配的一组元素(name="user"),只有含有name属性的元素(表单元素)才能通过name属性获取 - html方法document.documentElement:
document.documentElement - body方法document.body:
document.body - css选择器:
- 获取第一个元素querySelector:
document.querySelector("#demo");,返回匹配指定选择器(id=“demo”)的第一个元素 - 获取一组元素querySelectorAll:
document.querySelector(".demo");,获取css选择器匹配(class=“demo”)的所有元素,document.querySelectorAll('#file')[0]返回匹配的所有元素(id=“file”)的第一个 - 注意:返回的是元素的css选择器,所以需要加
#或.区别是id选择器和class选择器
- 获取第一个元素querySelector:
- 大概定位:返回一组DOM节点,先定位父节点,再从父节点开始选择,以缩小范围
document.getElementsByTagName():- CSS选择器
document.getElementsByClassName():
- 注意:返回一组元素时,不能直接给集合绑定事件,需要获取到集合中的某一个元素,然后再为元素绑定事件;获取一组元素的某个元素时,索引从0开始;
- id属性getElementById:
- 常见操作:
- 更新DOM:相当于更新了该DOM节点表示的HTML的内容;
- 添加DOM:相当于动态增加了一个HTML节点;
- 删除DOM:相当于删掉了该DOM节点的内容以及它包含的所有子节点。
- 更新DOM
- 定义:拿到一个DOM节点后可以对它进行更新,即修改节点的文本
- 修改innerHTML属性:
- 功能:可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树;如果写入的字符串是通过网络拿到的,要注意对字符编码来避免XSS攻击。
- 语法:
p.innerHTML = 'XXX';
var p = document.getElementById('p-id'); //获取<p id="p-id">...</p>中的p
p.innerHTML = 'ABC'; // 修改为<p id="p-id">ABC</p>
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
- 修改innerText或textContext属性:
- 功能:自动对字符串进行HTML编码,保证无法设置任何HTML标签。读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本。
- 语法:
p.innerText = "xxx"
var p = document.getElementById('p-id'); // 获取<p id="p-id">...</p>dfo
p.innerText = '<script>alert("Hi")</script>'; // 无法进行HTML编码:<p id="p-id"><script>alert("Hi")</script></p>
- 修改style属性:
- 功能:DOM节点的style属性对应所有的CSS,可以直接获取、设置和修改CSS。因为CSS允许font-size这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize。
- 语法:
p.style.color = '#ff0000'; - 属性(驼峰式命名):color、fontSize 、paddingTop、fontWeight
var p = document.getElementById('p-id'); // 获取<p id="p-id">...</p>
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';
- 插入DOM
- 定义:获得了某个DOM节点,在这个DOM节点内插入新的DOM
- 空DOM节点:
innerHTML = '<span>child</span>',修改DOM节点的内容,相当于“插入”了新的DOM节点,修改innerHTML会直接替换掉原来的所有子节点 - 非空DOM节点:
- appendChild:父节点的末尾插入子节点
- 功能:把一个子节点添加为父节点的最后一个子节点,即并列子节点的最后。
- 语法:
sub.appendChild(new);,这个节点首先会从原先的位置删除,再插入到新的位置。
- insertBefore:
- 功能:把子节点插入到指定的位置
- 语法:
parentElement.insertBefore(newElement, referenceElement);,子节点会插入到referenceElement之前,重点是要拿到一个“参考子节点”的引用。 - 补充:迭代children属性,实现循环一个父节点的所有子节点
- appendChild:父节点的末尾插入子节点
var d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);
var
i, c,
list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
c = list.children[i]; // 拿到第i个子节点
}
- 作业未完成
- 删除DOM
- 定义:要获得该节点本身以及它的父节点,再调用父节点的removeChild把自己删掉
- 语法:
var removed = parent.removeChild(self);,或parent.removeChild(parent.children[0]);,删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置 - 注意:children属性是一个只读属性,并且它在子节点变化(删除)时会实时更新(索引-1);
5-4 操作表单
- 操作表单
- 定义:类似操作DOM,因为表单本身也是DOM树。用JavaScript来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。
- HTML表单的输入控件:
- 文本框,对应的<input type="text">,用于输入文本;
- 口令框,对应的<input type="password">,用于输入口令;
- 单选框,对应的<input type="radio">,用于选择一项;
- 复选框,对应的<input type="checkbox">,用于选择多项;
- 下拉框,对应的<select>,用于选择一项;
- 隐藏文本,对应的<input type="hidden">,用户不可见,但表单提交时会把隐藏文本发送到服务器。
- 获取数值:
- 定义:获得了一个<input>节点的引用,就可以直接调用value获得对应的用户输入值
- 语法:
<input type="text" id="email">- 获取input节点:
var input = document.getElementById('email'); - 拿到输入数值:
input.value; - 注意:单选框和复选框用value获得的是预设的值,需要用checked判断
- 获取input节点:
// <label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
// <label><input type="radio" name="weekday" id="tuesday" value="2"> Tuesday</label>
var mon = document.getElementById('monday');
var tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或者false
tue.checked; // true或者false
- 设置值:
- 直接设置value:
input.value = 'test@example.com'; - 单选和复选要设置checked:
mon.checked = true;
- 直接设置value:
- HTML5控件:
- 常用:date、datetime、datetime-local、color等都使用input标签
- date:
<input type="date" value="2021-12-02">,是一个可输入的日历 - datetime-local:
<input type="datetime-local" value="2021-12-02T20:21:12">,是一个可输入的日历和时间 - color:
<input type="color" value="#ff0000">,是一个可输入的颜色 - 获取数值时input.value是一个标准格式的字符串,如YYYY-MM-DD
- 提交表单:
- 方法:①通过元素的submit()方法提交一个表单;②响应本身的onsubmit事件,在提交form时作修改;
- 方法1:响应一个的click事件,在JavaScript代码中提交表单
- 缺点:扰乱了浏览器对form的正常提交,因为浏览器默认点击<button type="submit">时提交表单,或者用户在最后一个输入框按回车键
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
- 方法2:响应本身的onsubmit事件,在提交form时作修改
- 注意:return true来告诉浏览器继续提交,如果return false,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 继续下一步:
return true;
}
</script>
- 保密:
<script>
function checkForm() {
var pwd = document.getElementById('password');
// 把用户输入的明文变为MD5:
pwd.value = toMD5(pwd.value);
// 继续下一步:
return true;
}
</script>
5-5 操作文件
5-6 AJAX
- 异步AJAX
- 定义:Asynchronous JavaScript and XML,即用JavaScript执行异步网络请求
- 作用:通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。而传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。
- 机制:js是单线程的,是浏览器实现了异步操作;由于js是事件驱动的,每个事件都会绑定回调函数,所以Ajax异步执行调用是基于Event和callback的 => Ajax执行调用机制【笔试】
- 工作原理:
- XMLHttpRequest对象:用于在后台与服务器交换数据,有,有 的属性,有。
- send()和open()方法
- responseText、responseXML属性:不同响应类型
- onreadystatechange、readyState、status属性
- onreadystatechange:存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。
- readyState:存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。0: 请求未初始化;1: 服务器连接已建立;2: 请求已接收;3: 请求处理中;4: 请求已完成,且响应已就绪
- status:200: "OK";404: 未找到页面;
- XMLHttpRequest对象:用于在后台与服务器交换数据,有,有 的属性,有。
- 语法1-创建XMLHttpRequest对象:
- 现代浏览器:
var xmlhttp=new XMLHttpRequest(); - 低版本的IE:
var request = new ActiveXObject('Microsoft.XMLHTTP'); - 合并写法:
- 现代浏览器:
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else {
request = new ActiveXObject('Microsoft.XMLHTTP');
}
- 语法2-向服务器发送请求:
- 21-规定请求的内容:
xmlhttp.open("GET","ajax_info.txt",true);,类型为GET或POST,url为文件在服务器的位置,异步处理请求为true(异步)或者false(同步) - 22-发送请求:
xmlhttp.send();- GET:
- 获取信息:
xmlhttp.open("GET","/try/ajax/demo_get.php",true); - 添加一个唯一的 ID:
xmlhttp.open("GET","/try/ajax/demo_get.php?t=" + Math.random(),true); - 在url上添加信息:
xmlhttp.open("GET","/try/ajax/demo_get2.php?fname=Henry&lname=Ford",true);
- 获取信息:
- POST:
- POST请求:
xmlhttp.open("POST","/try/ajax/demo_post.php",true); - POST数据:用 setRequestHeader() 来添加 HTTP 头,在 send() 方法中规定要发送的数据
- POST请求:
- GET:
- 21-规定请求的内容:
xmlhttp.open("POST","/try/ajax/demo_post2.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Henry&lname=Ford");
- 语法3-获取服务器响应
- text文本:
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;,responseText获得字符串形式的响应数据\ - XML文件:
xmlDoc=xmlhttp.responseXML;,responseXML获得 XML 形式的响应数据 - 基于响应的任务:当 readyState 等于 4 且状态为 200 时,表示响应已就绪
- text文本:
xmlhttp.onreadystatechange = function(){
if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
- 优雅版:
$(document).ready(function(){
$("button").click(function(){
$.get("/try/ajax/demo_test.php",function(data,status){
alert("数据: " + data + "\n状态: " + status);
});
});
});
- 前后端数据交互 => 前后端数据如何交互【面试】
- 工作流程:前端通过表单传递参数并提交给后端,后端根据需求接收、处理、返回数据,前端根据返回的数据再次渲染页面
- form表单提交:
- 语法:
<form action="demo.do" method="post"/>,action为地址,method提交方式 - 缺点:提交时,页面会发生跳转,也无法得知服务器返回的状态,响应慢
- 语法:
- ajax提交:
- 优点:不重新加载整个页面的情况下与服务器交换数据并更新部分网页内容,实现局部刷新,大大降低了资源的浪费
- GET:
url: "TestJsonServlet?id="+id+"&gender="+"男", type: "get",提交url字符串拼接数据 - POST:提交数据data
function leftmenu(parentid, parentpath,moduleindex){
var leftcontent="";
$.ajax({
type: "POST", //数据传输的方法
dataType: "JSON", //规定返回的数据类型
url : "<%=path%>/resource/usermenus", //传递参数的目标位置
data : {parentid:parentid,parentpath:parentpath}, //前端传递给后端的数据
success : function(data){ //服务器返回数据成功的时候,前端需要如何操作,data为服务器返回的数据 }
error: function (errorMsg) { //请求失败之后进入该方法,errorMsg为失败后返回的错误信息 }
});
}
- 后端返回的数据:JSON文件
5-7 Promise(未完)
- 异步编程
- 定义:同步按你的代码顺序执行,异步不按照代码顺序执行
- 同步Synchronous, sync:传统单线程编程中,程序的运行是同步的,同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行
- 异步Asynchronous, async:异步则是不保证同步,一个异步过程的执行将不再与原有的序列有顺序关系,异步就是从主线程发射一个子线程来完成任务,异步的执行效率更高
- 线程
- 线程&进程:
- 进程:CPU分配【资源】的最小单位,即能拥有资源和独立运行的最小单位,如公司
- 线程:CPU调度的最小单位,在进程的基础上的一次程序运行单位,一个进程中可以有多个线程,如员工
- 单线程&多线程:
- 单线程:一个进程里只有一个线程。
- 多线程:一个进程里同时运行多个线程完成不同的工作
- 主线程&子线程:
- 主线程:在处理一些简短、快速的操作时,在主线程中就可以完成,但是不能同时接受多方面的请求,必须要等到一个事件结束界面才能处理其他请求。
- 子线程:用子线程完成一些可能消耗时间足够长以至于被用户察觉的事情,如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的,所以JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。
- 浏览器& Js:
- 浏览器是多进程:每打开一个tab页面,其实就是新开了一个进程,每个进程有ui渲染线程、js引擎线程、http请求线程等
- Js是单线程:同一个时刻,因为不能同时执行多个DOM操作,所以设计JavaScript只能执行一个任务,其他任务只能等待
- 线程&进程:
- 任务
- 同步任务&异步任务:
- 由来:在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。如果一个任务的处理耗时很久的话,如网络请求、定时器、等待鼠标点击等,后面的任务也就会被阻塞
- 同步任务:不需要进行等待可立即看到执行结果,如console
- 异步任务:需要等待一定的时候才能看到结果,如setTimeout、网络请求
- 执行顺序:先把所有的同步代码添加到任务队列,排在前面,然后把异步代码排到同步代码的后面。如果此时好几个异步代码都有等待时间,那么谁的等待时间短就会被先放进任务队列中
- 同步任务&异步任务:
- 浏览器事件循环机制 => 消息队列、宏任务和微任务的理解【面试】
- 消息队列:用于存储待处理的事件和消息,遵循先进先出(FIFO)原则。事件包括用户交互事件、网络请求完成、定时器到期、异步回调等
- 事件循环:用户交互,定时器,网络请求等等浏览器事件产生对应的任务要在任务队列中排队,事件循环不断地循环调用栈和消息队列,将消息队列中的事件一个个推送到调用栈中执行。
- 微任务&宏任务:
- 由来:因为有些任务优先级更高,js允许插队优先执行,所以异步任务又分为宏任务和微任务
- 微任务:由js引擎自身提供的是微任务,如Promise,基本上平时接触到的除了Promise都是宏任务
- 宏任务:是消息队列中的任务,每个宏任务中都包含了一个微任务队列
- 执行顺序:先执行宏任务,等宏任务执行完了,再执行微任务(如果没有微任务,就不需要执行),结束后继续执行下一个宏任务
- 异步函数
- setTimeout函数:
- 语法1:
setTimeout(func, 3000);,func为回调函数,3000为毫秒数,函数执行之后会产生一个子线程,子线程会等待 3 秒,然后执行回调函数 - 语法2:
- 语法1:
setTimeout(function () {
document.getElementById("demo").innerHTML="RUNOOB!";
}, 3000);
- setInterval函数:
- Promise
- 定义:承诺将来会执行的对象,用于处理异步操作
- 语法:
new Promise(function (resolve, reject) { });- Promise内置的构造函数:返回一个 Promise 对象,接受一个函数作为参数,该起始函数是同步的并且会被立即执行
- 起始函数: resolve 和 reject参数分别表示 Promise 成功和失败的状态
const p1 = new Promise((resolve, reject) => {
resolve("成功");
});
const p = Promise.resolve("成功"); //用Promise.resolve创建
const p = Promise.reject("失败"); //用Promise.reject创建
- 三大状态:
- pending初始状态:调用promise时,一开始就呈现出等待状态,遇到resolve或者reject之前,都处于这个状态,且可以改变
- fulfilled成功状态:在执行了resolve后,promise则会从pedding变成fulfilled
- rejected失败状态:在执行了reject后,promise状态会变成rejected
- 九个方法 => 介绍Promise的各种方法【面试】
Promise.resolve():创建一个成功状态的Promise对象,可以在.then的成功回调中获取resolve的值Promise.reject():创建一个失败状态的Promise对象,可以在.then的失败回调中获取reject的值,也可以在.catch中获取Promise.then():用于接收请求接口返回的数据,用于接收promise对应状态的数据,用于处理 Promise 成功状态和失败状态的回调函数,两个参数为用于解决时的回调函数(必选)和用于拒绝状态的回调函数(可选)Promise.catch():用于处理 Promise 失败状态的回调函数,来捕获代码异常或者出错.then和.catch本质上是没有区别的,一般异常用.catch,拒绝状态用.then;.then中产生异常能在.catch或者下一个.then中捕获
Promise.all():- finally:无论 Promise 是成功还是失败,都会执行的回调函数。
const p = new Promise((resolve, reject) => { resolve("成功"); });
const p = new Promise((resolve, reject) => { reject("拒绝"); });
p.then(result => console.log(result)); //解决
p.then(
(res) => { console.log("p:", res); }, //p:成功
(rej) => { console.log("p:", rej); } //不执行
);
p.catch((error) => {console.log(error);});
- 串行执行异步任务:
new Promise(test).then(function (result) { ... }).catch(function (reason) { ... });
new Promise(function (resolve, reject) {
var a = 0;
var b = 1;
if (b == 0) reject("Divide zero"); //调用 resolve 代表一切正常
else resolve(a / b); //reject 是出现异常时所调用的
}).then(function (value) {
console.log("a / b = " + value);
}).catch(function (err) {
console.log(err);
}).finally(function () {
console.log("End");
});
- 并行执行异步任务:
Promise.all([p1, p2]).then(function (results) {}); - 多异步任务单返回:
Promise.race([p1, p2]).then(function (result) {});
5-8 async函数
5-9 Canvas
6-错误处理
7-jQuery
7-1
7-5 ajax
Javascript强化
4-性能
4-4 页面生命周期
- 页面生命周期
- 定义:页面生命周期定义了页面从加载到卸载的整个过程,包括各种事件和阶段,四个关键事件为DOMContentLoaded、load、beforeunload 和 unload。
- 生命周期:
- 1-加载页面的 HTML 和 DOM 树:完成后触发DOMContentLoaded事件,适用于执行与 DOM 相关的初始化操作。
- 2-加载整个页面及其所有外部资源(如图像、样式表、脚本等):完成后触发load事件,适用于执行与页面渲染和交互相关的操作。
- 3-页面被卸载(关闭、刷新、导航到其他页面等):
- 之前触发beforeunload 事件,适用于询问用户是否确定离开页面或执行一些清理操作;
- 之后触发unload事件,适用于执行最后的清理操作。
- 浏览器加载顺序:script executed->readyState : interactive->【DOMContentLoaded】->image onload->iframe onload->readyState : complete->【window onload】
- 生命周期事件
- DOMContentLoaded
- 定义:DOMContentLoaded 事件在页面的 HTML 和 DOM 树加载完成后触发,但在所有外部资源(如图像、样式表、脚本等)加载完成之前
- 场景:在 DOM 加载完成后执行一些操作,例如初始化页面元素、注册事件监听器、发送初始的 AJAX 请求、执行一些初始的 JavaScript 逻辑等。
- 属性:
- type类型:值为DOMContentLoaded
- bubbles冒泡:默认false
- cancelable可取消:默认false
- target目标对象:即触发事件的元素
- 方法:
EventTarget.addEventListener():注册事件监听器,以便在 DOMContentLoaded 事件触发时执行相应的处理函数
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded event triggered'); // DOMContentLoaded 事件触发后执行的逻辑
});
- load
- 定义:load 事件在整个页面及其所有外部资源(如图像、样式表、脚本等)加载完成后触发
- 场景:此时页面的所有内容已经可用,并且可以执行与页面渲染和交互相关的操作,如执行一些需要页面完全加载后才能进行的操作、初始化和配置第三方库和插件、启动动画或其他视觉效果等
- 属性:同上,区别是类型为load
- 方法:
EventTarget.addEventListener(),用于注册事件监听器,以便在 load 事件触发时执行相应的处理函数。
window.addEventListener('load', function() {
console.log('load event triggered'); // load 事件触发后执行的逻辑
});
- beforeunload
- 定义:beforeunload 事件在页面即将被卸载(关闭、刷新、导航到其他页面等)之前触发
- 场景:用于提示用户保存未保存的数据或离开前的确认提示,并可以在事件处理函数中执行一些清理操作,如取消未完成的 AJAX 请求、释放资源等
- 事件:同上,区别是类型为beforeunload、cancelable默认true
- 方法:
EventTarget.addEventListener(),用于注册事件监听器,以便在 beforeunload 事件触发时执行相应的处理函数。
window.addEventListener('beforeunload', function(event) { // beforeunload 事件触发时执行的逻辑
event.preventDefault(); // 阻止默认的 beforeunload 行为
event.returnValue = ''; // Chrome 需要设置 returnValue 属性
});
- unload
- 定义:unload 事件在页面即将被卸载(关闭、刷新、导航到其他页面等)时触发
- 场景:用于执行一些清理操作,如释放页面所占用的资源(清除定时器、取消事件监听器、取消未完成的请求)、发送最后的统计数据或日志等。
- 方法:
EventTarget.addEventListener(),用于注册事件监听器,以便在 unload 事件触发时执行相应的处理函数。
window.addEventListener('unload', function() { // unload 事件触发后执行的逻辑
console.log('unload event triggered');
});