ES6后,变量通过let关键字声明
let i
let sum
let i, sum
let message = 'hello'
let i = 0, j = 1
let 语句中 不为变量指定初始值,变量也会被声明, 但在被赋值之前它的值是undefined
要声明常量而非变量,则要使用const而非let,const与let类似,区别在于const必须在声明时初始化常量
const AU = 1.496E8 // 天文单位
常量是不能改变的,重新赋值会报错 TypeError
何时使用const
for(let datum of data) console.log(datum)
上述也可以使用const来声明这些循环变量,只要保证在循环体内不给它重新赋值即可,此时const声明只是一次循环迭代期间的常量值
for(const datum of data) console.log(datum)
变量与常量作用域
重复声明
const x = 1
if(x === 1) {
let x = 2
console.log(x)
}
console.log(x) // 1 回到全局变量
let x = 3 // 报错,重复声明
声明与类型
let i = 10
i = 'len' // 给一个变量赋一个数值,然后再给它赋一个字符串是合法的
// var变量不会将这些变量的作用域限定在函数体内,每次循环都会重新声明和重新初始化同一个变量
for(var i = 0; ....) {}
④ var声明的变量,最不寻常的一个特性是,作用域提升(在使用var声明变量时,该声明会被提高到包含函数的顶部)
因此对使用var声明的变量,可以在包含函数内部的任何地方使用而不会报错。如果初始化的代码尚未运行,则变量的值可能是undefined
/*
解构赋值:
- ES6允许按照一定模式从数组和对象中提取值,对变量进行赋值,
- 1、数组的解构
- 2、对象的解构
*/
// 1、数组的解构 []
const F4 = ['小沈阳', '刘能', '赵四', '宋小宝']
let [xiao, liu, zhao, song] = F4
console.log(xiao) // 小沈阳
// 2、对象的解构 {}
const zao = {
nam:'赵本山',
age:'不详',
xiaopin:function() {
console.log('我可以演小品')
}
}
let {nam, age, xiaopin} = zao // 此处用name关键字问题等 对对象里面的值进行一一对应
console.log(nam) // 赵本山
解构赋值让使用返回数组的函数变得异常便捷 - 使得返回数组的函数使用
// 将[x, y] 坐标转换为[r, theta] 极坐标
function toPolar(x, y) {
return [Math.sqrt(x*x, y*y), Math.atan2(y, x)]
}
let [r, theta] = toPolar(1.0, 1.0) // r == Math.sqrt(2) theta == Math.PI/4
可以在JavaScript的各种for循环中声明变量和常量,同样也可以在这个上下文中使用变量解构赋值
let o = {x:1, y:2}
for(const [name, value] of Object.entries(o)) {
console.log(name, value) // 'x 1' 和 'y 2'
}
解构赋值左侧变量的个数不一定与右侧数组中的元素个数相同,左侧多余的变量会被设置为undefined,而右侧多余的值会被忽略 左侧的变量列表可以包含额外的逗号,以跳过右侧的某些值
let [x, y] = [1] // x == 1, y == undefined
[x, y] = [1, 2, 3] // x == 1, y == 2
[, x, , y] // x == 2, y == 4
在解构赋值的时候,如果想把所有未使用或剩余的值收集到一个变量中, 可以在左侧最后一个变量名前面加上3个点 (…)
let [x, ...y] = [1, 2, 3, 4] // y == [2, 3, 4]
解构赋值可用于嵌套函数,此时赋值的左侧看起来也应该像一个嵌套的数组字面量
let [a, [b, c]] = [1, [2, 2.5], 3] // a == 1, b == 2, c == 2.5
const firstName = (user && user.firstName) || 'default'
const firstName = user?.firstName || 'default'
// 解释
上述代码使用 ?. 运算符,直接在链式调用的时候判断,左侧对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined
使用 ?. 运算符的注意点
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
// 上述代码中,如果a是undefined或者null, 那么x不会进行递增运算,也就是说链判断运算符一旦为真,右侧表达式就不再求值
const text = data.text || 'Hello, world!'
const text = data.text ?? 'Hello, world!'
关键点
const animationDuration = settings?.animationDuration ?? 300
上面代码中,settings 如果是 null 或 undefined,就会返回默认值300。
有时候, 区分直接定义在对象上的属性和那些从原型对象上继承的属性很重要。JS使用术语 “自有属性” 指代非继承属性。
除了名字和值之外,每个属性还有3个属性特征:
① writable(可写)特性指定是否可以设置属性的值
② enumerable(可枚举)特性指定是否可以在 for/in 循环中返回属性的名字
③ configurable (可配置)特性指定是否可以删除属性,以及是否可修改其特性
创建对象的方式(3种)
6.2.1 对象字面量
对象字面量的最简单形式是包含在一对花括号中的一组逗号分隔开来的 “名 : 值”对形式
let empty = {} // 没有属性的对象
// 包含两个数值的属性
let point = {
x : 1,
y : 0,
}
6.10.1 简写属性
// 之前写法
let x = 1, y = 2
let o = {
x : x,
y : y,
}
// ES6之后写法
let x = 1, y = 2
let o = { x, y }
o.x + o.y = 3
6.2.2 使用new创建对象
let o = new Object() // 创建一个空对象 {}
let a = new Array() // 创建一个空数组 []
let d = new Date() // 创建一个表示当前时间的日期对象
let r = new Map() // 创建一个映射对象
除了内置构造函数,我们经常需要定义自己的构造函数来初始化新创建的对象
6.2.3 原型
let a = {
kang:''
}
let b = {
kang:'kang'
}
console.log(Object.assign(a,b)) // { kang: 'kang' }
let o1 = Object.create({x:1, y:2}) // o1继承属性x和y
o1.x + o1.y // 3
let o2 = Object.create(null) // o2不继承任何属性或方法
let o3 = Object.create(Object.prototype) // o3 与 {} 或 new Object() 类似
let o = {x :"don't change this value"}
library.function(Object.create(o)) // 防止意外修改
let author = book.author // 取得book的'author'属性
let name = author.surname // 取得author的'surname' 属性
let title = book['main title'] // 取得book的'main title'属性
book.edition = 7 // 为book创建一个'edition'属性
book['main title'] = 'ECMAScript' // 修改"main title"属性
使用方括号时,其中的表达式必须求值为一个字符串,更准确的说法是,该表达式必须求值为一个字符串或一个可以转换为字符串或符号的值 - 在下一章,我们看到方括号中使用数字也是很常见的
基于浏览器F12测试
let digits = [...'abc']
digits
(3) ['a', 'b', 'c']
集合对象是可迭代的,因此要去除数组中的重复元素,一种便捷方式就是先把数组转换为集合,再使用扩展操作符将这个集合转换成数组
let letters = [...'hello world']
[...new Set(letters)]
(8) ['h', 'e', 'l', 'o', ' ', 'w', 'r', 'd']
Array() 构造函数
let a = new Array()
let a = new Array(10)
let a = new Array(5, 4, 3, 2, 1, 'testing')
Array.of()
Array.of(1)
[1]
Array.of(10)
[10]
Array.of(1,2,3,'hello')
(4) [1, 2, 3, 'hello']
Array.from()
let origin = [1, 2, 3]
let copy = Array.from(origin)
copy
(3) [1, 2, 3]
let trueArray = Array.from(arrayLike)
let a = [true, false]
a[2]
undefined
a[-1]
undefined
b = [1, 2, 3, 4, 5]
(5) [1, 2, 3, 4, 5]
b.length = 3
3
b
(3) [1, 2, 3]
b.length = 0 // 删除所有元素 b是[]
0
b.length = 5 // 长度为5,但没有元素,类似 new Array(5)
5
b
(5) [空属性 × 5]
let a = []
a[0] = 'zero'
let a = []
a.push('zero')
a.push('one', 'two')
可以使用delete操作符删除数组元素
到ES6为止,遍历一个数组的最简单方式就是使用for/of 循环
let letters = [..."Hello world"]
let string = ""
for(let letter of letters){
// string += letter
console.log(letter)
}
// console.log(string)
对于稀疏数组,这个循环没有特殊行为,凡是不存在的元素都返回undefined
如果要对数组使用for/of循环,并且想知道每个数组元素的索引,可以使用数组的entries() 方法和解构赋值
let letters = [..."Hello world"]
for(let [index, letter] of letters.entries()) {
console.log('@', index) // 索引从0开始
console.log('#', letter)
}
forEach()
let letters = [..."Hello world"]
let uppercase = ''
letters.forEach(letter => {
uppercase += letter.toUpperCase()
});
console.log(uppercase) // HELLO WORLD
当然使用老式的for循环也可以遍历数组
for(let i = 0; i < letters.length; i++){
console.log(letters[i])
}
当数组是稀疏的,我们想要跳过未定义或不存在的元素时
for(let i = 0; i < letters.length; i++){
if(letters[i] === undefined) continue
}
let table = new Array(10)
for(let i = 0; i < table.length; i++) {
table[i] = new Array(10)
}
console.log(table)
二维数组用的较少,访问二维数组的时候使用matrix[row][col]
<script>
let data = [1, 2, 3, 4, 5], sum = 0
// 计算数组元素之和 - 只关心数组元素的值,可以把函数写成只接收一个参数
data.forEach(value => {
sum += value
})
console.log(sum) // 15
/*
forEach() 第一个参数是函数
该函数有三个参数
- 1、数组元素的值
- 2、数组元素的索引
- 3、数组本身
*/
// 递增每个元素的值
data.forEach(function(v, i, a){
a[i] = v + 1
})
console.log(data) // [2, 3, 4, 5, 6]
</script>
<script>
let a = [1, 2, 3]
b = a.map( x => x * x)
console.log(a) // [1, 2, 3]
console.log(b) // [1, 4, 9]
</script>
<script>
let a = [5, 4, 3, 2, 1]
b = a.filter( x => x < 3)
console.log(a) // [5, 4, 3, 2, 1]
console.log(b) // [2, 1]
</script>
let dense = sparse.filter(() => true)
let a = [5, 4, , , 1]
let b = a.filter(() => true) // [5, 4, 1]
console.log(b)
a = a.filter( x => x !== undefined && x!== null)
let a = [1, 2, 3, 4, 5]
console.log(a.findIndex(x => x === 3)) // 2
console.log(a.find(x => x % 5 === 0)) // 5
every
let a = [1, 2, 3, 4, 5]
console.log(a.every(x => x < 10)) // true
console.log(a.every(x => x % 2 === 0)) // false 并非所有值都是偶数
some
let a = [1, 2, 3, 4, 5]
a.some(x => x % 2 === 0) // true
注意:
reduce() 接收两个参数
let a = [1, 2, 3, 4, 5]
console.log(a.reduce((x, y) => x + y, 0)) // 15
console.log(a.reduce((x, y) => x * y, 1)) // 120
console.log(a.reduce((x, y) => (x > y) ? x : y)) // 5
doneTotal() {
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
},
在此处函数里面的第二个参数又是一个对象
reduceRight()
flat()
[1, [2, 3]].flat() // [1, 2, 3]
let a = [1, [2, [3, [4]]]]
a.flat(2) // [1, 2, 3, [4]]
flatMap()
let phrases = ["hello world", "my love"]
let words = phrases.flatMap((item) => item.split(""))
console.log(words)
结果
[
'h', 'e', 'l', 'l', 'o',
' ', 'w', 'o', 'r', 'l',
'd', 'm', 'y', ' ', 'l',
'o', 'v', 'e'
]
let a = [1, 2, 3]
console.log(a.concat(4, 5)) // [ 1, 2, 3, 4, 5 ]
console.log(a.concat([4, 5], [6, 7]))
console.log(a.concat(4, [5, [6, 7]])) // [ 1, 2, 3, 4, 5, [ 6, 7 ] ]
console.log(a) // [1, 2, 3]
说白了,就是只能打平一级的数组
push() & pop()
let stack = []
stack.push(1, 2)
console.log(stack) // [1, 2]
stack.pop()
console.log(stack) // [1]
a.push(...value)
unshift() & shift()
let q = []
let len = q.push(1, 2)
console.log(len) // 2
q.shift()
console.log(q) // [2]
let a = []
a.unshift(1)
a.unshift(2) // [2, 1]
a = []
a.unshift(1, 2) // [1, 2]
以上是数组定义的处理连续区域(数组切片)的方法
1、slice()
let a = [1, 2, 3, 4, 5]
a.slice(0, 3) // [1, 2, 3]
a.slice(3) // [4, 5]
a.slice(1, -1) // [2, 3, 4]
console.log(a.slice(-3, -2)) // [3]
2、splice()
删除操作:
let a = [1, 2, 3, 4, 5, 6, 7, 8]
console.log(a.splice(4)) // [5, 6, 7, 8]
console.log(a) // [1, 2, 3, 4]
a.splice(1, 2) // [2, 3] , a现在是[1, 4]
插入操作
let a = [1, 2, 3, 4, 5]
a.splice(2, 0, 'a', 'b') // [], a现在是[1, 2, 'a', 'b', 3, 4, 5]
a.splice(2, 2, [1, 2], 3) // ['a', 'b'], a现在是[1, 2, [1, 2], 3, 3, 4, 5]
3、fill()
let a = new Array(5) // 创建一个长度为5的没有元素的数组
a.fill(0) // [0, 0, 0, 0, 0] 用0来填充数组
a.fill(9, 1) // [0, 9, 9, 9, 9]
a.fill(8, 2, -1) // [0, 9, 8, 8, 9]
**4、copyWithin()**用的较少
let a = [1, 2, 3, 4, 5]
a.copyWithin(1) // [1, 1, 2, 3, 4] 把数组元素复制到索引及之后
a.copyWithin(2, 3, 5) // [1, 1, 3, 4, 4] 把最后两个元素复制到索引2
a.copyWithin(0, -2) // [4, 4, 3, 4, 4] 负偏移也可以
1、indexOf() 和 lastIndexOf()
let a = [0, 1, 2, 1, 0]
a.indexOf(1) // 1
a.lastIndexOf(1) // 3
a.indexOf(3) // -1
注意: indexOf()和lastIndexOf() 都接收第二个可选的参数,指定从哪个位置开始搜索,如果省略这个参数,indexOf() 从头开始搜索,lastIndexOf() 从尾开始搜索
let a = [1, 2, 3, 4, 1, 2]
console.log(a.indexOf(1, 2)) // 4
/*
从数组a中找到所有值x,返回匹配索引的数组
*/
function findAll(a, x) {
let result = []
len = a.length
pos = 0
while(pos < len) {
pos = a.indexOf(x, pos)
if(pos === -1) break // 如果没有找到,结束即可
result.push(pos) // 把索引保存在数组中
pos += 1
}
return result
}
console.log(findAll([1, 2, 3, 1, 1, 2], 1)) // [0, 3, 4]
字符串也有indexOf() 和 lastIndexOf() 方法,和这两个数组方法类似,区别在于第二个参数如果是负数会被当成是 0
**2、includes() **
let a = [1, true, 3, NaN]
a.includes(true) // true
a.includes(2) // false
a.includes(NaN) // true
a.indexOf(NaN) // -1 indexOf无法找到NaN的值
3、sort()
let a = ['banana', 'apple']
a.sort() // a == ['apple', 'banana']
let a = [33, 4, 1111, 222]
a.sort()
a.sort(function(a, b) { // 传入一个比较函数
return a - b // 取决于顺序,返回 <0、0、>0
}) // a == [4, 33, 222, 1111]
a.sort((a, b) => b - a) // a == [1111, 222, 33, 4]
4、reverse()
let a = [1, 2, 3]
a.reverse() // a == [3, 2, 1]
Array类定义了3个把数组转换为字符串的方法,通常可以用在记录日志或错误信息的时候
**join() **
let a = [1, 2, 3]
a.join() // '1,2,3'
a.join(" ") // '1 2 3'
a.join("") // '123'
let b = new Array(10)
b.join("-") // '---------'
String.split()
join() 方法执行的是String.split() 的反向操作,后者通过把字符串分割为多个片段来创建数组 split() 方法中需要放入的是字符串形式的
let res = 'hello world'
res.split("") // (11) ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
"2:3:4".split(':') // (3) ['2', '3', '4']
toString() - 对于数组而言,该方法的逻辑和没有参数的join方法是一致的
[1, 2, 3].toString()
'1,2,3'
['a', 'b', 'c'].toString()
'a,b,c'
[1, [2, 'c']].toString()
'1,2,c'
Array类也定义了3个静态函数,可以通过Array构造函数而非数组调用 Array.of() 和 Array.from() 是创建新数组的工厂方法,之前介绍过,另一个静态数组函数是Array.isArray() 用于确定一个未知值是不是数组
Array.isArray([]) // true
Array.isArray({}) // false
JavaScript数组具有一些其他对象不具备的特殊特性
以上特性让JavaScript数组与常规对象有了明显区别,但这些特性并非定义数组的本质特性 事实上只要对象有一个数值属性length,而且有相应的非负整数属性,就完全可以视同为数组
下面的代码会为一个常规对象添加属性,让它成为一个类数组对象,然后再遍历得到伪数组的“元素”
let a = {} // 创建一个常规的空对象
// 添加属性让它变成“类数组”对象
let i = 0
while(i < 10) {
a[i] = i * i
i++
}
/*
只要对象有一个数值属性length,而且有相应的非负整数属性
那就可以完全可以视同为数组
*/
a.length = i
// 像遍历真正的数组一样遍历这个对象
let total = 0
for(let j = 0; j < a.length; j++) {
total += a[j]
}
console.log(total) // 285
注意:
/*
确定对象o是不是类数组对象
*/
function isArrayLike(o) {
if (o
&& typeof o === 'object' &&
Number.isFinite(o.length) &&
o.length >= 0 &&
Number.isInteger(o.length) &&
o.length < 4294967295) {
return true
} else {
return false
}
}
多数数组方法有意设计成了泛型方法,因此除了真正的数组,同样可以用于类数组对象,但由于类数组对象不会继承Array.prototype,所以无法直接在他们身上调用数组方法,因此可以使用Function.call() 方法
let a = {'0':'a', '1':'b', '2':'c', length:3} // 类数组对象
Array.prototype.join.call(a, '+') // 'a+b+c'
Array.prototype.slice.call(a, 0) // ['a', 'b', 'c']
Array.from(a) // ['a', 'b', 'c'] 更容易的数组复制
倒数第二行代码在类数组对象调用了Array的slice方法,把该对象的元素复制到一个真正的数组对象中,在很多遗留demo中这是常见的习惯做法, 但是现在使用Array.from() 会更容易
let s = 'test'
s.charAt(0) // 't'
s[1] // 'e'
当然对字符串而言,typeof操作符仍然返回’string’, 把字符串传给Array.isArray()方法仍然返回false
重要:
Array.prototype.join.call('JavaScript', ' ')
'J a v a S c r i p t'
function factorial(x) {
if(x <= 1) return 1
return x * factorial(x-1)
/* let stack = []
stack.push(1, 2)
s
console.log(stack) // [1, 2]
stack.pop()
console.log(stack) // [1] */
let q = []
let len = q.push(1, 2)
console.log(len) // 2
q.shift()
console.log(q) // [2]
let a = []
a.unshift(1)
a.unshift(2) // [2, 1]
a = []
a.unshift(1, 2) // [1, 2]
// 定义一个对参数求平方的函数,并将其赋值给一个变量
const square = function(x){ return x * x}
// 函数表达式也可以包含名字,这对递归有用
const f = function fact(x) { if (x <= 1) return 1; else return x * fact(x - 1)}
// 函数表达式也可以用作其他函数的参数
[3, 2, 1].sort(function(a, b){
return a-b
})
// 函数表达式也可以定义完以后立即调用
let tensquared = (function(x) { return x * x}(10))
// tensquared() // 报错
tensquared // 100 tensquared代表的就是整个函数
使用函数声明定义函数f() 与 创建一个函数表达式再将其赋值给变量f有一个重要的区别
const sum = (x, y) => { return x + y; }
const sum = (x, y) => x + y
const polynomial = x => x*x + 2*x + 3
const constantFunc = () => 42
重要
const f = x => {return { value : x;}} // 正 f() 返回一个对象
const g = x = > ({value : x}) // 正 g() 返回一个对象
箭头函数的简洁语法让他们非常适合作为值传给其他函数,在使用map() 、filter()和reduce() 等数组方法时非常常见
// 得到一个过滤掉null 元素的数组
let filtered = [1, null, 2, 3].filter(x => x !== null) // filtered == [1, 2, 3]
// 求数值的平方
let squares = [1, 2, 3, 4].map(x => x*x) // squares == [1, 4, 9, 16]
与其他方式定义的函数的区别
嵌套函数
function hypotenuse(a, b) {
function square(x) { return x * x;}
return Math.sqrt(square(a) + square(b));
}
5种方式来进行调用
1、函数调用
let total = distance(0, 0, 2, 1) + distance(2, 1, 3, 5)
2、方法调用
o.m = f
o.m()
o.m(x, y)
方法调用与函数调用有一个重要区别
/* let stack = []
stack.push(1, 2)
console.log(stack) // [1, 2]
stack.pop()
console.log(stack) // [1] */
// let q = []
// let len = q.push(1, 2)
// console.log(len) // 2
// q.shift()
// console.log(q) // [2]
// let a = []
// a.unshift(1)
// a.unshift(2) // [2, 1]
// a = []
// a.unshift(1, 2) // [1, 2]
// // 定义一个对参数求平方的函数,并将其赋值给一个变量
// const square = function(x){ return x * x}
// // 函数表达式也可以包含名字,这对递归有用
// const f = function fact(x) { if (x <= 1) return 1; else return x * fact(x - 1)}
// // 函数表达式也可以用作其他函数的参数
// [3, 2, 1].sort(function(a, b){
// return a-b
// })
// // 函数表达式也可以定义完以后立即调用
// let tensquared = (function(x) { return x * x}(10))
// // tensquared() // 报错
// tensquared // 100 tensquared代表的就是整个函数
/* let a = function square(x) {
return x * x
} */
/* function hypotenuse(a, b) {
function square(x) { return x * x;}
return Math.sqrt(square(a) + square(b));
} */
let calculator = {
operand1 : 1,
operand2 : 1,
add() {
// 此处的this就是 包含了calculator等内容
/*
{ operand1: 1, operand2: 1, add: [Function: add] }
*/
console.log(this) // 其中此处的this就是函数调用的上下文
this.result = this.operand1 + this.operand2
console.log(this.result)
},
}
console.log(calculator.add())
console.log(calculator.result)
// 显示结果 非常关键
{ operand1: 1, operand2: 1, add: [Function: add] }
2
undefined
2