一、基本语法
1.1、变量
-
规则:
-
不能用关键字(
var、let、const、for、if); -
关键字:有特殊含义的字符,
javascript内置的一些英语词汇,如:let、var、if、for等; -
只能用下划线、字母、数字、$组成,且不能以数字开头;
-
字母严格区分大小写,如
Age和age是不同的变量。
-
-
规范
-
起名要有意义;
-
遵守小驼峰命名法。
-
1.2、关键字
-
var-
在ES5中,顶层对象的属性和全局变量是等价的,用
var声明的变量既是全局变量,也是顶层变量;注意:顶层对象,在浏览器环境指的是
window对象,在Node指的是global对象。 -
使用
var声明的变量存在变量提升的情况; -
使用
var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明; -
在函数中使用使用
var声明变量时候,该变量是局部的,而如果在函数内不使用var,该变量是全局的。
-
-
let-
用法类似于
var,但是所声明的变量,只在let命令所在的代码块内有效; -
不存在变量提升,只要块级作用域内存在
let命令,这个区域就不再受外部影响; -
使用
let声明变量前,该变量都不可用,也就是大家常说的”暂时性死区“; -
let不允许在相同作用域中重复声明。
-
-
const-
const声明一个只读的常量,一旦声明,常量的值就不能改变,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值; -
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动; -
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量;
-
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,
const只能保证这个指针是固定的,并不能确保改变量的结构不变。
-
-
三者区别
-
变量提升;
-
暂时性死区;
-
重复声明;
-
修改声明的变量;
-
块级作用域(作用域链,经典
for循环,闭包)。
-
1.3、数据类型
1、基本数据类型
-
Number-
数值最常见的整数类型格式则为十进制,还可以设置八进制(零开头)、十六进制(
0x开头); -
浮点类型则在数值汇总必须包含小数点,还可通过科学计数法表示;
-
在数值类型中,存在一个特殊数值
NaN,意为“不是数值”,用于表示本来要返回数值的操作失败了(而不是抛出错误)。
-
-
String-
字符串可以使用双引号(")、单引号(')或反引号(`)标示;
-
字符串是不可变的,意思是一旦创建,它们的值就不能变了。
-
-
Boolean数据类型 转换为 true 的值 转换为 false 的值 String 非空字符串 “” Number 非零数值(包括无穷值) 0、NaN Object 任意对象 null Undefined N/A(不存在) undefined -
Undefined-
Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值; -
包含
undefined值的变量跟未声明变量是有区别的,未声明的变量会报错。
-
-
Null-
Null类型同样只有一个值,即特殊值null; -
逻辑上讲,
null值表示一个空对象指针,这也是给typeof传一个null会返回"object"的原因; -
undefined值是由null值派生而来,console.log(null == undefined); // true
-
-
Symbol-
Symbol是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
-
2、复杂数据类型
-
Object -
Function -
Array -
Date -
RegExp -
Map -
Set
3、区别
-
声明变量时不同的内存地址分配
-
简单类型的值存放在栈中,在栈中存放的是对应的值;
-
引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址。
-
-
不同的类型数据导致赋值变量时的不同
-
简单类型赋值,是生成相同的值,两个对象对应不同的地址;
-
复杂类型赋值,是将保存对象的内存地址赋值给另一个变量,也就是两个变量指向堆内存中同一个对象。
-
1.4、深浅拷贝
1、浅拷贝
-
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝;
-
如果属性是基本类型,拷贝的就是基本类型的值;
-
如果属性是引用类型,拷贝的就是内存地址。
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址。
-
-
Object.assign(target, source1, source2, ...)const target = { a: 1, b: 2 }; const source = { b: 4, c: 5 }; const returnedTarget = Object.assign(target, source); console.log(target); // { a: 1, b: 4, c: 5 } console.log(returnedTarget === target); // true -
Array.prototype.slice()const fxArr = ["One", "Two", "Three"] const fxArrs = fxArr.slice(0) fxArrs[1] = "love"; console.log(fxArr) // ["One", "Two", "Three"] console.log(fxArrs) // ["One", "love", "Three"] -
Array.prototype.concat()const fxArr = ["One", "Two", "Three"] const fxArrs = fxArr.concat() fxArrs[1] = "love"; console.log(fxArr) // ["One", "Two", "Three"] console.log(fxArrs) // ["One", "love", "Three"] -
使用拓展运算符实现的复制
const fxArr = ["One", "Two", "Three"] const fxArrs = [...fxArr] fxArrs[1] = "love"; console.log(fxArr) // ["One", "Two", "Three"] console.log(fxArrs) // ["One", "love", "Three"]
2、深拷贝
-
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
-
_.cloneDeep()const _ = require('lodash'); const obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; const obj2 = _.cloneDeep(obj1); console.log(obj1.b.f === obj2.b.f); // false -
jQuery.extend()const $ = require('jquery'); const obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; const obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f); // false -
JSON.stringify()const obj2 = JSON.parse(JSON.stringify(obj1));但是这种方式存在弊端,会忽略
undefined、symbol和函数const obj = { name: 'A', name1: undefined, name3: function() {}, name4: Symbol('A') } const obj2 = JSON.parse(JSON.stringify(obj)); console.log(obj2); // {name: "A"} -
手写循环递归
function deepClone(obj, hash = new WeakMap()) { if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作 if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝 if (typeof obj !== "object") return obj; // 是对象的话就要进行深拷贝 if (hash.get(obj)) return hash.get(obj); let cloneObj = new obj.constructor(); // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身 hash.set(obj, cloneObj); for (let key in obj) { if (obj.hasOwnProperty(key)) { // 实现一个递归拷贝 cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj; }
3、区别
-
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址;
-
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址。
1.5、类型转换机制
1、显示转换
-
Number()-
Number()转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为NaN原始值 转换结果 undefined NaN null 0 true 1 false 0 String 根据语法和转换规则来转换 Symbol 抛出类型错误异常 Object 先调用toPrimitive,再调用toNumber
-
-
parseInt()-
parseInt()相比Number(),就没那么严格了,parseInt()函数逐个解析字符,遇到不能转换的字符就停下来。
-
-
String()-
可以将任意类型的值转化成字符串
原始值 转换结果 undefined “undefined” Boolean “true”或“false” Number 对应数字的字符串 String String Symbol 抛出类型错误异常 Object 先调用toPrimitive,再调用toNumber
-
-
Boolean()-
可以将任意类型的值转为布尔值,转换规则如下
数据类型 转换为true的值 转换为false的值 Boolean true false String 非空字符串 “”(空字符串) Number 非零数值(包括无穷值) 0、NaN Object 任意对象 null undefined N/A(不存在) undefined
-
2、隐式转换
我们这里可以归纳为两种情况发生隐式转换的场景
-
比较运算(
==、!=、>、<)、if、while需要布尔值地方; -
算术运算(
+、-、*、/、%)。
1、自动转换为布尔值
-
在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用
Boolean函数; -
可以得出个小结:
-
undefined -
null -
false -
+0 -
-0 -
NaN -
""
-
-
除了上面几种会被转化成
false,其他都换被转化成true。
2、自动转换成字符串
-
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串;
-
具体规则是:先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串;
-
常发生在
+运算中,一旦存在字符串,则会进行字符串拼接操作。'5' + 1 // '51' '5' + true // "5true" '5' + false // "5false" '5' + {} // "5[object Object]" '5' + [] // "5" '5' + function (){} // "5function (){}" '5' + undefined // "5undefined" '5' + null // "5null"
3、自动转换为数值
-
除了
+有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值;'5' - '2' // 3 '5' * '2' // 10 true - 1 // 0 false - 1 // -1 '1' - 1 // 0 '5' * [] // 0 false / '5' // 0 'abc' - 1 // NaN null + 1 // 1 undefined + 1 // NaN -
null转为数值时,值为0。undefined转为数值时,值为NaN。
3、==和===
1、==
-
两个都为简单类型,字符串和布尔值都会转换成数值,再比较;
-
简单类型与引用类型比较,对象转化成其原始类型的值,再比较;
-
两个都为引用类型,则比较它们是否指向同一个对象;
-
null和undefined相等; -
存在
NaN则返回false。
2、===
-
全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回
true,即类型相同,值也需相同; -
undefined和null与自身严格相等。
二、字符串
2.1、操作方法
1、增
-
除了常用
+以及${}进行字符串拼接之外,还可通过concat; -
concat用于将一个或者多个字符串拼接成一个新的字符串
let stringValue = "hello "; let result = stringValue.concat("world"); console.log(result); // "hello world" console.log(stringValue); // "hello"
2、删
-
slice() -
substr() -
substring()这三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。
let stringValue = "hello world"; console.log(stringValue.slice(3)); // "lo world" console.log(stringValue.substring(3)); // "lo world" console.log(stringValue.substr(3)); // "lo world" console.log(stringValue.slice(3, 7)); // "lo w" console.log(stringValue.substring(3,7)); // "lo w" console.log(stringValue.substr(3, 7)); // "lo worl"
3、改
-
trim()、trimLeft()、trimRight()-
删除前、后或前后所有空格符,再返回新的字符串;
let stringValue = " hello world "; let trimmedStringValue = stringValue.trim(); console.log(stringValue); // " hello world " console.log(trimmedStringValue); // "hello world"
-
-
repeat()-
接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果;
let stringValue = "na "; let copyResult = stringValue.repeat(2) // na na
-
-
padStart()、padEnd()-
复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件;
let stringValue = "foo"; console.log(stringValue.padStart(6)); // " foo" console.log(stringValue.padStart(9, ".")); // "......foo"
-
-
toLowerCase()、toUpperCase()-
大小写转化;
let stringValue = "hello world"; console.log(stringValue.toUpperCase()); // "HELLO WORLD" console.log(stringValue.toLowerCase()); // "hello world"
-
4、查
-
chatAt()-
返回给定索引位置的字符,由传给方法的整数参数指定;
let message = "abcde"; console.log(message.charAt(2)); // "c"
-
-
indexOf()-
从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 );
let stringValue = "hello world"; console.log(stringValue.indexOf("o")); // 4
-
-
startWith()、includes()-
从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值;
let message = "foobarbaz"; console.log(message.startsWith("foo")); // true console.log(message.startsWith("bar")); // false console.log(message.includes("bar")); // true console.log(message.includes("qux")); // false
-
2.2、转换方法
-
split把字符串按照指定的分割符,拆分成数组中的每一项;
let str = "12+23+34" let arr = str.split("+") // [12,23,34]
2.3、模板匹配方法
针对正则表达式,字符串设计了几个方法:
-
match()-
接收一个参数,可以是一个正则表达式字符串,也可以是一个
RegExp对象,返回数组;let text = "cat, bat, sat, fat"; let pattern = /.at/; let matches = text.match(pattern); console.log(matches[0]); // "cat"
-
-
search()-
接收一个参数,可以是一个正则表达式字符串,也可以是一个
RegExp对象,找到则返回匹配索引,否则返回 -1;let text = "cat, bat, sat, fat"; let pos = text.search(/at/); console.log(pos); // 1
-
-
replace()-
接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数);
let text = "cat, bat, sat, fat"; let result = text.replace("at", "ond"); console.log(result); // "cond, bat, sat, fat"
-
2.4、正则表达式
1、创建
-
字面量创建,其由包含在斜杠之间的模式组成;
-
调用
RegExp对象的构造函数;const re = /\d+/g; const re = new RegExp("\\d+", "g"); const rul = "\\d+" const re1 = new RegExp(rul, "g"); -
使用构建函数创建,第一个参数可以是一个变量,遇到特殊字符
\需要使用\\进行转义。
2、匹配规则
| 规则 | 描述 |
|---|---|
| \ | 转义 |
| 匹配输入的开始 | |
| $ | 匹配输入的结束 |
| * | 匹配前一个表达式 0 次或多次 |
| . | 默认匹配除换行符之外的任何单个字符 |
| \b | 匹配一个词的边界,例如在字母和空格之间 |
| \B | 匹配一个非单词边界 |
| \d | 匹配一个数字 |
| \D | 匹配一个非数字字符 |
| \n | 匹配一个换行符 |
| \r | 匹配一个回车符 |
| \s | 匹配一个空白字符,包括空格、制表符、换页符和换行符 |
| \S | 匹配一个非空白字符 |
| \w | 匹配一个单字字符(字母、数字或者下划线) |
| \W | 匹配一个非单字字符 |
3、匹配方法
| 方法 | 描述 |
|---|---|
| exec | 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。 |
| test | 一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。 |
| match | 一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。 |
| matchAll | 一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。 |
| search | 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。 |
| replace | 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。 |
| split | 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。 |
三、数组
3.1、操作方法
1、增
-
push()-
push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。let colors = []; // 创建一个数组 let count = colors.push("red", "green"); // 推入两项 console.log(count) // 2
-
-
unshift()-
unshift()在数组开头添加任意多个值,然后返回新的数组长度。let colors = new Array(); // 创建一个数组 let count = colors.unshift("red", "green"); // 从数组开头推入两项 alert(count); // 2
-
-
splice()-
传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组。
let colors = ["red", "green", "blue"]; let removed = colors.splice(1, 0, "yellow", "orange") console.log(colors) // red,yellow,orange,green,blue console.log(removed) // []
-
-
concat()-
首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组。
let colors = ["red", "green", "blue"]; let colors2 = colors.concat("yellow", ["black", "brown"]); console.log(colors); // ["red", "green","blue"] console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
-
2、删
-
pop()-
pop()方法用于删除数组的最后一项,同时减少数组的length值,返回被删除的项。let colors = ["red", "green"] let item = colors.pop(); // 取得最后一项 console.log(item) // green console.log(colors.length) // 1
-
-
shift()-
shift()方法用于删除数组的第一项,同时减少数组的length值,返回被删除的项。let colors = ["red", "green"] let item = colors.shift(); // 取得第一项 console.log(item) // red console.log(colors.length) // 1
-
-
splice()-
传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组。
let colors = ["red", "green", "blue"]; let removed = colors.splice(0,1); // 删除第一项 console.log(colors); // green,blue console.log(removed); // red,只有一个元素的数组
-
-
slice()-
slice()用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组。let colors = ["red", "green", "blue", "yellow", "purple"]; let colors2 = colors.slice(1); let colors3 = colors.slice(1, 4); console.log(colors) // red,green,blue,yellow,purple concole.log(colors2); // green,blue,yellow,purple concole.log(colors3); // green,blue,yellow
-
3、改
-
splice()-
传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响。
let colors = ["red", "green", "blue"]; let removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素 console.log(colors); // red,red,purple,blue console.log(removed); // green,只有一个元素的数组
-
4、查
-
indexOf()-
返回要查找的元素在数组中的位置,如果没找到则返回 -1。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; numbers.indexOf(4) // 3
-
-
includes()-
返回要查找的元素在数组中的位置,找到返回
true,否则false。let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; numbers.includes(4) // true
-
-
find()-
返回第一个匹配的元素。
const people = [ { name: "Matt", age: 27 }, { name: "Nicholas", age: 29 } ]; people.find((element, index, array) => element.age < 28) // // {name: "Matt", age: 27}
-
3.2、ES6新增
1、扩展运算符
-
可以将数组转换为用逗号分隔的参数序列,主要用于函数调用的时候,将一个数组变为参数序列;
const arr = [1, 2, 3]; fn(...arr) -
可以将某些数据结构转为数组;
[...document.querySelectorAll('div')] -
能够更简单实现数组复制;
const a1 = [1, 2]; const [...a2] = a1; -
数组快速合并;
const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]通过扩展运算符实现的是浅拷贝,修改了引用指向的值,会同步反映到新数组。
-
扩展运算符可以与解构赋值结合起来,用于生成数组;
const [first, ...rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5] const [first, ...rest] = []; first // undefined rest // [] const [first, ...rest] = ["foo"]; first // "foo" rest // [] -
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错;
const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错 const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错 -
可以将字符串转为真正的数组;
[...'hello'] // [ "h", "e", "l", "l", "o" ] -
定义了遍历器接口的对象,都可以用扩展运算符转为真正的数组,如果对没有
Iterator接口的对象,使用扩展运算符,将会报错。const obj = {a: 1, b: 2}; let arr = [...obj]; // TypeError: Cannot spread non-iterable object
2、构造函数新增方法
-
Array.from()-
将两类对象转为真正的数组:类似数组的对象和可遍历的对象(包括 ES6 新增的数据结构
Set和Map);let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] -
还可以接受第二个参数,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
-
-
Array.of()-
用于将一组值,转换为数组;
Array.of(3, 11, 8) // [3,11,8] -
没有参数的时候,返回一个空数组;
Array() // [] -
当参数只有一个的时候,实际上是指定数组的长度;
Array(3) // [, , ,] -
参数个数不少于 2 个时,
Array()才会返回由参数组成的新数组。Array(3, 11, 8) // [3, 11, 8]
-
3、实例对象新增方法
-
copyWithin()-
将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组;
-
参数如下:
-
target(必需):从该位置开始替换数据。如果为负值,表示倒数; -
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算; -
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。// 将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2 [1, 2, 3, 4, 5].copyWithin(0, 3); // [4, 5, 3, 4, 5]
-
-
-
find()、findIndex()-
find()用于找出第一个符合条件的数组成员,参数是一个回调函数,接受三个参数依次为当前的值、当前的位置和原数组;[1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10 -
findIndex()返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1;[1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2 -
这两个方法都可以接受第二个参数,用来绑定回调函数的
this对象。function f(v){ return v > this.age; } let person = {name: 'John', age: 20}; [10, 12, 26, 15].find(f, person); // 26
-
-
fill()-
使用给定值,填充一个数组,还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
['a', 'b', 'c'].fill(7) // [7, 7, 7] new Array(3).fill(7) // [7, 7, 7] ['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']如果填充的类型为对象,则是浅拷贝
-
-
entries(),keys(),values()-
keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历;for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a"
-
-
includes()-
用于判断数组是否包含给定的值;
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true -
方法的第二个参数表示搜索的起始位置,默认为
0,参数为负数则表示倒数的位置。[1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true
-
-
flat(),flatMap()-
将数组扁平化处理,返回一个新数组,对原数据没有影响;
[1, 2, [3, 4]].flat() // [1, 2, 3, 4] -
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1;[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]] [1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5] -
flatMap()方法对原数组的每个成员执行一个函数相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组// 相当于 [[2, 4], [3, 6], [4, 8]].flat() [2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8]flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this
-
-
sort()-
接受一个函数,函数返回值>1,<1,=0(升序)
[1, 5, 7, 3, 4].sort((a, b) => a - b ) // [1, 3, 4, 5, 7]
-
四、函数
4.1、ES6新增
1、参数
-
ES6允许为函数的参数设置默认值;function log(x, y = 'World') { console.log(x, y); } console.log('Hello') // Hello World console.log('Hello', 'China') // Hello China console.log('Hello', '') // Hello -
函数的形参是默认声明的,不能使用
let或const再次声明;function foo(x = 5) { let x = 1; // error const x = 2; // error } -
参数默认值可以与解构赋值的默认值结合起来使用;
function foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined 5 foo({x: 1}) // 1 5 foo({x: 1, y: 2}) // 1 2 foo() // TypeError: Cannot read property 'x' of undefined上面的
foo函数,当参数为对象的时候才能进行解构,如果没有提供参数的时候,变量x和y就不会生成,从而报错,这里设置默认值避免。function foo({x, y = 5} = {}) { console.log(x, y); } foo() // undefined 5 -
参数默认值应该是函数的尾参数,如果不是非尾部的参数设置默认值,实际上这个参数是没法省略的。
function f(x = 1, y) { return [x, y]; } f() // [1, undefined] f(2) // [2, undefined] f(, 1) // 报错 f(undefined, 1) // [1, 1]
2、属性
-
函数的
length属性;-
length将返回没有指定默认值的参数个数,rest参数也不会计入length属性;(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2 (function(...args) {}).length // 0 -
如果设置了默认值的参数不是尾参数,那么
length属性也不再计入后面的参数了。(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1
-
-
name属性。-
返回该函数的函数名;
var f = function () {}; // ES5 f.name // "" // ES6 f.name // "f" -
如果将一个具名函数赋值给一个变量,则
name属性都返回这个具名函数原本的名字;const bar = function baz() {}; bar.name // "baz" -
Function构造函数返回的函数实例,name属性的值为anonymous;(new Function).name // "anonymous" -
bind返回的函数,name属性值会加上bound前缀。function foo() {}; foo.bind({}).name // "bound foo" (function(){}).bind({}).name // "bound "
-
3、作用域
-
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
下面例子中,
y=x会形成一个单独作用域,x没有被定义,所以指向全局变量x。let x = 1; function f(y = x) { // 等同于 let y = x let x = 2; console.log(y); } f() // 1
4、严格模式
-
只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错 function doSomething(a, b = a) { 'use strict'; // code } // 报错 const doSomething = function ({a, b}) { 'use strict'; // code }; // 报错 const doSomething = (...a) => { 'use strict'; // code }; const obj = { // 报错 doSomething({a, b}) { 'use strict'; // code } };
5、箭头函数
-
使用“箭头”(
=>)定义函数;var f = v => v; // 等同于 var f = function (v) { return v; }; -
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分;
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; }; -
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用
return语句返回;var sum = (num1, num2) => { return num1 + num2; }如果返回对象,需要加括号将对象包裹
let getTempItem = id => ({ id: id, name: "Temp" });
1、函数体内的
this对象,就是定义时所在的对象,而不是使用时所在的对象;2、不可以当作构造函数,也就是说,不可以使用
new命令,否则会抛出一个错误;3、不可以使用
arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替;4、不可以使用
yield命令,因此箭头函数不能用作Generator函数。
4.2、闭包
闭包、函数柯里化、节流防抖 - 掘金 (juejin.cn)
4.3、函数式编程
1、函数式编程概念
-
函数式编程是一种编程范式、一种编写程序的方法论;
-
主要的编程范式有三种:命令式编程,声明式编程和函数式编程;
-
相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程;
-
简单来讲,就是要把过程逻辑写成函数,定义好输入参数,只关心它的输出结果,即是一种描述集合和集合之间的转换关系,输入通过函数都会返回有且只有一个输出值;
-
可以看到,函数实际上是一个关系,或者说是一种映射,而这种映射关系是可以组合的,一旦我们知道一个函数的输出类型可以匹配另一个函数的输入,那他们就可以进行组合。
2、实现
-
纯函数
-
纯函数是对给定的输入返还相同输出的函数,并且要求你所有的数据都是不可变的,即纯函数=无状态+数据不可变;
-
特性:
-
函数内部传入指定的值,就会返回确定唯一的值;
-
不会造成超出作用域的变化,例如修改全局变量或引用传递的参数。
-
-
优势
-
使用纯函数,我们可以产生可测试的代码;
-
不依赖外部环境计算,不会产生副作用,提高函数的复用性;
-
可读性更强 ,函数不管是否是纯函数都会有一个语义化的名称,更便于阅读;
-
可以组装成复杂任务的可能性,符合模块化概念及单一职责原则。
-
-
-
高阶函数
-
高级函数,就是以函数作为输入或者输出的函数被称为高阶函数;
通过高阶函数抽象过程,注重结果;
const forEach = function(arr,fn) { for(let i=0; i<arr.length; i++) { fn(arr[i]); } } let arr = [1,2,3]; forEach(arr, (item) => { console.log(item); })上面通过高阶函数
forEach来抽象循环如何做的逻辑,直接关注做了什么。 -
高阶函数存在缓存的特性,主要是利用闭包作用。
const once = (fn) => { let done = false; return function() { if(!done) { fn.apply(this, fn); } else { console.log("该函数已经执行"); } done = true; } }
-
-
柯里化
-
柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程;
一个二元函数如下:
let fn = (x,y) => x + y;转化成柯里化函数如下:
const curry = function(fn){ return function(x){ return function(y){ return fn(x,y); } } } let myfn = curry(fn); console.log( myfn(1)(2) ); -
上面的
curry函数只能处理二元情况,下面再来实现一个实现多参数的情况;// 多参数柯里化; const curry = function(fn) { return function curriedFn(...args) { if(args.length < fn.length) { return function() { return curriedFn(...args.concat([...arguments])); } } return fn(...args); } } const fn = (x, y, z, a) => x + y + z + a; const myfn = curry(fn); console.log(myfn(1)(2)(3)(1)); -
关于柯里化函数的意义如下:
-
让纯函数更纯,每次接受一个参数,松散解耦;
-
惰性执行。
-
-
-
组合与管道
-
组合函数,目的是将多个函数组合成一个函数;
function afn(a) { return a * 2; } function bfn(b) { return b * 3; } const compose = (a, b) => c => a(b(c)); let myfn = compose(afn, bfn); console.log( myfn(2));可以看到
compose实现一个简单的功能:形成了一个新的函数,而这个函数就是一条从bfn -> afn的流水线; -
管道函数,目的实现一个多函数组合;
const compose = (...fns) => { return val => { return fns.reverse().reduce((acc, fn) => { return fn(acc); }, val); } }compose执行是从右到左的。而管道函数,执行顺序是从左到右执行的;const pipe = (...fns) => { return val => { return fns.reduce((acc, fn) => { return fn(acc); }, val) } } -
组合函数与管道函数的意义在于:可以把很多小函数组合起来完成更复杂的逻辑。
-
3、优点
-
更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况;
-
更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响;
-
更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性;
-
隐性好处:减少代码量,提高维护性。
4、缺点
-
性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销;
-
资源占用:在 JavaScript 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式;
-
递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作。
4.4、函数缓存
-
函数缓存,就是将函数运算过的结果进行缓存,本质上就是用空间(缓存存储)换时间(计算过程),常用于缓存数据计算结果和缓存对象,缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理。
const add = (a,b) => a+b; const calc = memoize(add); // 函数缓存 calc(10,20);// 30 calc(10,20);// 30 缓存 -
实现函数缓存主要依靠闭包、柯里化、高阶函数。
// 定义 const memoize = function (func, content) { let cache = Object.create(null) content = content || this return (...key) => { if (!cache[key]) { cache[key] = func.apply(content, key) } return cache[key] } } // 调用 const calc = memoize(add); const num1 = calc(100,200) const num2 = calc(100,200) // 缓存得到的结果1、在当前函数作用域定义了一个空对象,用于缓存运行结果;
2、运用柯里化返回一个函数,返回的函数由于闭包特性,可以访问到
cache;3、然后判断输入参数是不是在
cache的中。如果已经存在,直接返回cache的内容,如果没有存在,使用函数func对输入参数求值,然后把结果存储在cache中。 -
虽然使用缓存效率是非常高的,但并不是所有场景都适用,因此千万不要极端的将所有函数都添加缓存。
以下几种情况下,适合使用缓存:
-
对于昂贵的函数调用,执行复杂计算的函数;
-
对于具有有限且高度重复输入范围的函数;
-
对于具有重复输入值的递归函数;
-
对于纯函数,即每次使用特定输入调用时返回相同输出的函数。
-
五、原型链
原型链、继承、Class、this指向改变方法 - 掘金 (juejin.cn)
5.1、new操作符
-
在
JavaScript中,new操作符用于创建一个给定构造函数的实例对象。function Person(name, age){ this.name = name; this.age = age; } Person.prototype.sayName = function () { console.log(this.name) } const person1 = new Person('Tom', 20) console.log(person1) // Person {name: "Tom", age: 20} t.sayName() // 'Tom'从上面可以看到:
1、
new通过构造函数Person创建出来的实例可以访问到构造函数中的属性;2、
new通过构造函数Person创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)。若在构建函数中显式加上返回值,并且这个返回值是一个原始类型,但这个值并没有什么作用;
function Test(name) { this.name = name return 1 } const t = new Test('xxx') console.log(t.name) // 'xxx'若在构造函数如果返回值为一个对象,那么这个返回值会被正常使用。
function Test(name) { this.name = name console.log(this) // Test { name: 'xxx' } return { age: 26 } } const t = new Test('xxx') console.log(t) // { age: 26 } console.log(t.name) // 'undefined' -
new关键字主要做了以下的工作:-
创建一个新的对象
obj; -
将对象与构建函数通过原型链连接起来;
-
将构建函数中的
this绑定到新建的对象obj上; -
根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理。
-
-
手写模拟
new操作符function mynew(Func, ...args) { // 1.创建一个新对象 const obj = {} // 2.新对象原型指向构造函数原型对象 obj.__proto__ = Func.prototype // 3.将构建函数的this指向新对象 let result = Func.apply(obj, args) // 4.根据返回值判断 return result instanceof Object ? result : obj }
5.2、this对象
函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别,在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。
1、
this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象;2、
this在函数执行过程中,this一旦被确定了,就不可以再更改。
1、绑定规则
-
默认绑定
-
全局环境中定义
person函数,内部使用this关键字;var name = 'Jenny'; function person() { return this.name; } console.log(person()); //Jenny1、上述代码输出
Jenny,原因是调用函数的对象在游览器中位window,因此this指向window,所以输出Jenny;2、严格模式下,不能将全局对象用于默认绑定,this会绑定到
undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。
-
-
隐式绑定
-
函数还可以作为某个对象的方法调用,这时
this就指这个上级对象;function test() { console.log(this.x); } var obj = {}; obj.x = 1; obj.m = test; obj.m(); // 1 -
这个对象中包含多个对象,尽管这个函数是被最外层的对象所调用,
this指向的也只是它上一级的对象;var o = { a:10, b:{ fn:function(){ console.log(this.a); //undefined } } } o.b.fn();上述代码中,
this的上一级对象为b,b内部并没有a变量的定义,所以输出undefined。 -
特殊情况
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //undefined console.log(this); //window } } } var j = o.b.fn; j();此时
this指向的是window,这里需要记住,this永远指向的是最后调用它的对象,虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window。
-
-
new绑定 -
显示绑定
2、箭头函数
在 ES6 的语法中还提供了箭头函语法,让我们在代码书写时就能确定 this 的指向(编译时绑定)。
const obj = {
sayThis: () => {
console.log(this);
}
};
// window 因为 JavaScript 没有块作用域,所以在定义 sayThis 的时候,里面的 this 就绑到 window 上去了
obj.sayThis();
const globalSay = obj.sayThis;
globalSay(); // window 浏览器中的 global 对象
虽然箭头函数的 this 能够在编译的时候就确定了 this 的指向,但也需要注意一些潜在的坑。
-
绑定事件监听;
const button = document.getElementById('mngb'); button.addEventListener('click', ()=> { console.log(this === window) // true this.innerHTML = 'clicked button' })上述可以看到,我们其实是想要
this为点击的button,但此时this指向了window -
在原型上添加方法时候,此时
this指向window;Cat.prototype.sayName = () => { console.log(this === window) //true return this.name } const cat = new Cat('mm'); cat.sayName() -
箭头函数不能作为构建函数。
3、优先级
new 绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
六、DOM
文档对象模型(DOM)是 HTML 和 XML 文档的编程接口,它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。
任何 HTML 或 XML 文档都可以用 DOM 表示为一个由节点构成的层级结构,节点分很多类型,每种类型对应着文档中不同的信息和(或)标记,也都有自己不同的特性、数据和方法,而且与其他类型有某种关系。
6.1、DOM常见操作
1、创建节点
-
createElement创建新元素,接受一个参数,即要创建元素的标签名。
const divEl = document.createElement("div"); -
createTextNode创建一个文本节点。
const textEl = document.createTextNode("content"); -
createDocumentFragment用来创建一个文档碎片,它表示一种轻量级的文档,主要是用来存储临时节点,然后把文档碎片的内容一次性添加到 DOM 中。
const fragment = document.createDocumentFragment();当请求把一个
DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点。 -
createAttribute创建属性节点,可以是自定义属性。
const dataAttribute = document.createAttribute('custom'); consle.log(dataAttribute);
2、查询节点
-
querySelector传入任何有效的
css选择器,即可选中单个DOM元素(首个):document.querySelector('.element') document.querySelector('#element') document.querySelector('div') document.querySelector('[name="username"]') document.querySelector('div + p > span')如果页面上没有指定的元素时,返回
null。 -
querySelectorAll返回一个包含节点子树内所有与之相匹配的
Element节点列表,如果没有相匹配的,则返回一个空节点列表。const nodeList = document.querySelectorAll("p");需要注意的是,该方法返回的是一个
NodeList的静态实例,它是一个静态的“快照”,而非“实时”的查询document.getElementById('id属性值'); // 返回拥有指定id的对象的引用 document.getElementsByClassName('class属性值'); // 返回拥有指定class的对象集合 document.getElementsByTagName('标签名'); // 返回拥有指定标签名的对象集合 document.getElementsByName('name属性值'); // 返回拥有指定名称的对象结合 document/element.querySelector('CSS选择器'); // 仅返回第一个匹配的元素 document/element.querySelectorAll('CSS选择器'); // 返回所有匹配的元素 document.documentElement; // 获取页面中的HTML标签 document.body; // 获取页面中的BODY标签 document.all['']; // 获取页面中的所有元素节点的对象集合型除此之外,每个 DOM 元素还有
parentNode、childNodes、firstChild、lastChild、nextSibling、previousSibling属性。
3、更新节点
-
innerHTML不但可以修改一个 DOM 节点的文本内容,还可以直接通过 HTML 片段修改 DOM 节点内部的子树。
// 获取<p id="p">...</p > var p = document.getElementById('p'); // 设置文本为abc: p.innerHTML = 'ABC'; // <p id="p">ABC</p > // 设置HTML: p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ'; // <p>...</p >的内部结构已修改 -
innerText、textContent自动对字符串进行
HTML编码,保证无法设置任何 HTML 标签。// 获取<p id="p-id">...</p > var p = document.getElementById('p-id'); // 设置文本: p.innerText = '<script>alert("Hi")</script>'; // HTML被自动编码,无法设置一个<script>节点: // <p id="p-id"><script>alert("Hi")</script></p >两者的区别在于读取属性时,
innerText不返回隐藏元素的文本,而textContent返回所有文本。 -
styleDOM 节点的 style 属性对应所有的 CSS ,可以直接获取或设置。遇到
-需要转化为驼峰命名。// 获取<p id="p-id">...</p > const p = document.getElementById('p-id'); // 设置CSS: p.style.color = '#ff0000'; p.style.fontSize = '20px'; // 驼峰命名 p.style.paddingTop = '2em';
4、添加节点
-
innerHTML如果这个 DOM 节点是空的,例如,
<div></div>,那么,直接使用innerHTML = '<span>child</span>'就可以修改 DOM 节点的内容,相当于添加了新的 DOM 节点。如果这个DOM节点不是空的,那就不能这么做,因为
innerHTML会直接替换掉原来的所有子节点。 -
appendChild把一个子节点添加到父节点的最后一个子节点。通常是先新增一个节点然后。
-
insertBefore把子节点插入到指定的位置,使用方法如下:
parentElement.insertBefore(newElement, referenceElement)子节点会插入到
referenceElement之前。 -
setAttribute在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值。
const div = document.getElementById('id') div.setAttribute('class', 'white');//第一个参数属性名,第二个参数属性值。
5、删除节点
-
删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的
removeChild把自己删掉。// 拿到待删除节点: const self = document.getElementById('to-be-removed'); // 拿到父节点: const parent = self.parentElement; // 删除: const removed = parent.removeChild(self); removed === self; // true删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
6.2、事件模型
1、原始事件模型(DOM0级)
事件绑定监听函数
-
HTML代码中直接绑定;
<input type="button" onclick="fun()"> -
通过 JS 代码绑定。
var btn = document.getElementById('.btn'); btn.onclick = fun;
特性
-
绑定速度快;
DOM0 级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行。
-
只支持冒泡,不支持捕获;
-
同一个类型的事件只能绑定一次;
-
删除 DOM0 级事件处理程序只要将对应事件属性置为 null 即可。
2、标准事件模型(DOM2级)
在该事件模型中,一次事件共有三个过程
-
事件捕获阶段:事件从
document一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行; -
事件处理阶段:事件到达目标元素, 触发目标元素的监听函数;
-
事件冒泡阶段:事件从目标元素冒泡到
document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
事件绑定与移除
addEventListener(eventType, handler, useCapture) // 事件绑定
removeEventListener(eventType, handler, useCapture) // 事件移除
-
eventType指定事件类型(不要加on); -
handler是事件处理函数; -
useCapture是一个boolean用于指定是否在捕获阶段进行处理,一般设置为false与IE浏览器保持一致。
var btn = document.getElementById('.btn');
btn.addEventListener(‘click’, showMessage, false);
btn.removeEventListener(‘click’, showMessage, false);
特性
-
可以在一个 DOM 元素上绑定多个事件处理器,各自并不会冲突;
btn.addEventListener(‘click’, showMessage1, false); btn.addEventListener(‘click’, showMessage2, false); btn.addEventListener(‘click’, showMessage3, false); -
执行时机,当第三个参数设置为
true就在捕获过程中执行,反之在冒泡过程中执行处理函数。
3、IE事件模型(基本不用)
IE事件模型两个过程
-
事件处理阶段:事件到达目标元素, 触发目标元素的监听函数;
-
事件冒泡阶段:事件从目标元素冒泡到
document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
事件绑定与移除
attachEvent(eventType, handler) // 事件绑定
detachEvent(eventType, handler) // 事件移除
6.3、事件代理
事件代理,俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素,事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成;
事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素,当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
应用场景
如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件,如果给每个列表项都绑定一个函数,那对于内存消耗是非常大的,这时候就可以事件委托,把点击事件绑定在父级元素 ul 上面,然后执行事件的时候再去匹配目标元素。
适合事件委托的事件有:click,mousedown,mouseup,keydown,keyup,keypress。
事件委托优点
-
减少整个页面所需的内存,提升整体性能;
-
动态绑定,减少重复工作。
事件委托缺点
-
focus、blur这些事件没有事件冒泡机制,所以无法进行委托绑定事件; -
mousemove、mouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。
如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件。
七、BOM
BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象。
其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率。
浏览器的全部内容可以看成 DOM,整个浏览器可以看成 BOM。区别如下:
- DOM
-
文档对象模型;
-
DOM 就是把文档当作一个对象来看待;
-
DOM 的顶级对象是 DOCUMENT;
-
DOM 主要是操作页面元素;
-
DOM 是 W3C 标准规范。
-
- BOM
-
浏览器对象模型;
-
把浏览器当作一个对象来看待;
-
BOM 的顶级对象是 WINDOW;
-
BOM 主要是与浏览器窗口进行交互;
-
BOM 是浏览器厂商在各自浏览器上定义的,兼容性较差。
-
7.1、BOM对象
1、window
BOM 的核心对象是 window,它表示浏览器的一个实例。在浏览器中,window 对象有双重角色,即是浏览器窗口的一个接口,又是全局对象,因此所有在全局作用域中声明的变量、函数都会变成 window 对象的属性和方法。
窗口控制方法:
-
moveBy(x, y):从当前位置水平移动窗体x个像素,垂直移动窗体y个像素,x为负数,将向左移动窗体,y为负数,将向上移动窗体; -
moveTo(x, y):移动窗体左上角到相对于屏幕左上角的(x, y)点; -
resizeBy(w, h):相对窗体当前的大小,宽度调整w个像素,高度调整h个像素。如果参数为负值,将缩小窗体,反之扩大窗体; -
resizeTo(w, h):把窗体宽度调整为w个像素,高度调整为h个像素; -
scrollTo(x, y):如果有滚动条,将横向滚动条移动到相对于窗体宽度为x个像素的位置,将纵向滚动条移动到相对于窗体高度为y个像素的位置; -
scrollBy(x, y): 如果有滚动条,将横向滚动条向左移动x个像素,将纵向滚动条向下移动y个像素。
window.open() 既可以导航到一个特定的 url,也可以打开一个新的浏览器窗口。
-
如果
window.open()传递了第二个参数,且该参数是已有窗口或者框架的名称,那么就会在目标窗口加载第一个参数指定的 URL; -
window.open()会返回新窗口的引用,也就是新窗口的window对象; -
window.close()仅用于通过window.open()打开的窗口,新创建的window对象有一个opener属性,该属性指向打开他的原始窗口对象。
2、location
http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents
| 属性名 | 例子 | 说明 |
|---|---|---|
| hash | #contents | url中#后面的字符,没有则返回空串 |
| host | www.wrox.com:80 | 服务器名称和端口号 |
| hostname | www.wrox.com | 域名,不带端口号 |
| href | http://www.wrox.com:80/WileyCDA/?q=javascript#contents | 完整url |
| pathname | /WileyCDA/ | 服务器下面的文件路径 |
| port | 80 | url的端口号,没有则为空 |
| protocol | http: | 使用的协议 |
| search | ?q=javascript | url的查询字符串,通常为?后面的内容 |
除了 hash 之外,只要修改 location 的一个属性,就会导致页面重新加载新 URL;
location.reload(),此方法可以重新刷新当前页面。这个方法会根据最有效的方式刷新页面,如果页面自上一次请求以来没有改变过,页面就会从浏览器缓存中重新加载,如果要强制从服务器中重新加载,传递一个参数 true 即可。
3、navigator
navigator 对象主要用来获取浏览器的属性,区分浏览器类型。属性较多,且兼容性比较复杂。
4、screen
保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。
5、history
history 对象主要用来操作浏览器 URL 的历史记录,可以通过参数向前,向后,或者向指定 URL 跳转。
常用的属性如下:
-
history.go()-
接收一个整数数字或者字符串参数:向最近的一个记录中包含指定字符串的页面跳转,当参数为整数数字的时候,正数表示向前跳转指定的页面,负数为向后跳转指定的页面。
history.go('maixaofei.com'); history.go(3); //向前跳转三个记录 history.go(-1); //向后跳转一个记录
-
-
history.forward():向前跳转一个页面; -
history.back():向后跳转一个页面; -
history.length:获取历史记录数。
7.2、本地存储
1、cookie
-
Cookie类型为小型文本文件,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 HTTP 无状态导致的问题; -
作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制
cookie有效期、安全性、使用范围的可选属性组成; -
但是
cookie在每次请求中都会被发送,如果不使用 HTTPS 并对其加密,其保存的信息很容易被窃取,导致安全风险。举个例子,在一些使用cookie保持登录态的网站上,如果cookie被窃取,他人很容易利用你的cookie来假扮成你登录网站。
cookie 常用属性
-
Expires用于设置Cookie的过期时间;Expires=Wed, 21 Oct 2015 07:28:00 GMT -
Max-Age用于设置在Cookie失效之前需要经过的秒数(优先级比Expires高);Max-Age=604800 -
Domain指定了Cookie可以送达的主机名; -
Path指定了一个URL路径,这个路径必须出现在要请求的资源的路径中才可以发送Cookie首部;Path=/docs # /docs/Web/ 下的资源会带 Cookie 首部 -
标记为
Secure的Cookie只应通过被 HTTPS 协议加密过的请求发送给服务端。
我们可以看到
cookie一开始的作用并不是为了缓存而设计出来,只是借用了cookie的特性实现缓存。
cookie 的使用
-
document.cookie = '名字=值'; -
关于
cookie的修改,首先要确定domain和path属性都是相同的才可以,其中有一个不同得时候都会创建出一个新的cookie;Set-Cookie:name=aa; domain=aa.net; path=/ # 服务端设置 document.cookie =name=bb; domain=aa.net; path=/ # 客户端设置 -
最后
cookie的删除,最常用的方法就是给cookie设置一个过期的事件,这样cookie过期后会被浏览器删除。
2、localStorage
HTML5 新方法,IE8 及以上浏览器都兼容。
特点
-
生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的;
-
存储的信息在同一域中是共享的;
-
当本页操作(新增、修改、删除)了
localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件; -
大小:5M(跟浏览器厂商有关系);
-
localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡; -
受同源策略的限制。
使用
// 设置
localStorage.setItem('username','cfangxu');
// 获取
localStorage.getItem('username');
// 获取第一个键名
localStorage.key(0);
// 删除
localStorage.removeItem('username');
// 一次性清除所有存储
localStorage.clear();
缺点
-
无法像
Cookie一样设置过期时间; -
只能存入字符串,无法直接存对象。
localStorage.setItem('key', {name: 'value'}); console.log(localStorage.getItem('key')); // '[object, Object]'
3、sessionStorage
sessionStorage 和 localStorage 使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage 将会删除数据。
4、indexedDB
indexedDB 是一种低级 API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该 API 使用索引来实现对该数据的高性能搜索,虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB 提供了一个解决方案。
优点
-
储存量理论上没有上限;
-
所有操作都是异步的,相比
LocalStorage同步操作性能更高,尤其是数据量较大时; -
原生支持储存 JS 的对象;
-
是个正经的数据库,意味着数据库能干的事它都能干。
缺点
-
操作非常繁琐;
-
本身有一定门槛;
关于 indexedDB 的使用基本使用步骤如下:
-
打开数据库并且开始一个事务;
-
创建一个
object store; -
构建一个请求来执行一些数据库操作,像增加或提取数据等;
-
通过监听正确类型的
DOM事件以等待操作完成; -
在操作结果上进行一些操作(可以在
request对象中找到)。