let const和作用域
简单来说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.JS的作用域是考函数来形成的,一个函数的变量在函数外不可以访问
全局作用域
- 函数外部定义的变量拥有全局作用域
- 未经过定义而直接赋值的变量
- window对象的属性拥有全局作用
局部作用域
局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以在一些地方会把这种作用域成为函数作用域
function fn () {
var a = 1
return a
}
console.log(fn())
console.log(a) //报错
a是函数内部声明并赋值,拥有局部作用域,只能在函数fn内部使用,在fn外部使用就会报错,这就是局部作用域的特性,外部无法访问
块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,会带来下面问题
- 变量提升导致内层变量可能会覆盖外层变量
var i = 5
function func () {
console.log(i)
if(true) {
var i = 6
}
}
func() //undefined
- 用来计数的循环变量泄露为全局变量
for (var i = 0; i< 10; i++) {
console.log(i)
}
console.log(i) //10
作用域链
通俗地讲,当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链
- 当执行函数时,总是先从函数内部找寻局部变量
- 如果内部找不到()函数的局部作用域没有),则会向创建函数的作用域(声明函数的作用域)寻找,依次向上
let
跟 var 一样是声明变量的关键词,以下是跟 var 不同的地方
- 在同个作用域内不可重复定义
- 不会导致变量提升
- 有块级作用域
const
在 let 的基础上多了个只读特性,即在声明后就不允许修改,所以在声明的同时就必须要赋值.要注意的是,不允许修改指的是不能修改 this 指向的内存地址,不代表不能修改属性成员
解构赋值
数组解构
let/const/var 后面跟上一对用中括号[]包裹的变量列表,变量的值为对应位置上的数组元素的值
let arr = [1,2,3]
let [a,b,c] = arr
console.log(a,b,c) // 123
如果不想要数组前面的数据,而是只想要中间或者最后的数据,则加上对应数量的逗号即可
let arr = [1,2,3]
let [,b] = arr
console.log(b) //2
let [,,c] = arr
console.log(c) //3
结合剩余参数使用,则把数组里剩余的元素放进一个新的数组里,需要注意的是剩余参数只能放在解构位置的后面
let arr = [1,2,3]
let [a,...b] = arr
console.log(a,b) //1,[2,3]
解构成员的数组长度小于被解构的数组长度,则按从前到后的顺序提取,大于的话则多出来的部分赋值undefined
let arr = [1,2,3]
let [a] = arr
console.log(a) //1
let [b,c,d,e] = arr
console.log(e) //undefined
对象解构 (醒醒,你没有对象)
根据属性名进行结构,而不是根据顺序,其他特点跟数组解构一致,比如找不到的属性名赋值undefined,可以给予默认值
let obj = {name: 'zce', age: 18}
let {name} = obj
console.log(name) //zce
因为被解构的变量名必须跟对象的属性名一致,所以在解构时可能会发生变量名冲突的情况,所以这时候可以使用别名来区分
let name = 'tom'
let obj = {name: 'zce', age: 18}
let {name: objName} = obj
console.log(objName) //zce
模板字符串
通过反引号 ` 包裹的字符串,跟用单引号或双引号包裹的字符串没什么区别.有些新特性很好用
- 支持直接换行.单双引号的字符串要通过 /n 换行符来表示换行,而反引号的字符串直接回车就行
- 可使用差值表达式直接插入变量
字符串扩展方法
startWith 是否以传入参数作为开头
endWith 是否已传入参数作为结尾
includes 是否包含传入参数
对象
对象字面量
//ES5写法
const name = "yuan";
const age = "21";
const sex = "man";
const obj = {
name: name,
age: age,
sex: sex,
getName: function(){
console.log("my name is " + name);
}
}
//ES6写法
const name = "yuan";
const age = "21";
const sex = "man";
const obj = {
name,
age,
sex,
getName(){
console.log("my name is " + name);
},
//计算属性名
[expression]: 123
}
对象扩展方法
Object.assign 将多个源对象中的属性赋值到一个目标对象中,如果对象中有相同的属性,目标对象的属性不变
const source1 = {
a: 123,
b: 123
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1)
console.log(target) //{a:123,b:123,c:456}
console.log(target === result) //true
用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
//展示基础用法
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
get (target, property) {
return property ion target ? target[property] : 'default'
},
set (target, property, value) {
if(property === 'age') {
if(!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
与Object.defineProperty对比
denfineProperty 只能监视到属性的读写, Proxy能够监视到更多对象操作,例如 delete, 对对象当中的方法的调用等等
Proxy 更好的支持数组对象的监视
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法.这些方法与 proxy handler 的方法相同.Reflect不是一个函数对象,而是一个静态类,因此它是不可构造的.
class
// ES5写法定义类
function Person (name) {
this.name = name
}
//类的通用方法
Person.prototype.say = function () {
console.log(`hi, my name is ${this.name}`)
}
//ES6写法
class Person {
construtor (name) {
this.name = name
}
say () {
console.log(`hi,my name is ${this.name}`)
}
}
let person = new Person('Artoria')
person.say()
static
在类的方法中,一般分为实例方法和静态方法,实例方法就是要通过这个类型构造的实例对象去调用,静态方法则是直接通过类型本身去调用,ES6 通过 static 关键字去定义静态方法
extend
通过继承来抽象相似类型之间相同的地方
class Person {
construtor (name) {
this.name = name
}
say () {
console.log(`hi,my name is ${this.name}`)
}
}
class Student extends Person {
construtor (name, number) {
//super 语法糖 调用这个相当于调用父类的构造函数
super(name)
}
}
Set
结构与数组类似,但是 Set 里的元素不允许重复
Symbol
用 Symbol 定义出来的任何字段都是唯一的,可用于对象的唯一标识或者杜绝实例的自定义的属性或方法覆盖原型上的属性或方法
function Foo(name) {
this.name = name
}
Foo.prototype.say = function() {
return this.name
}
var f = new Foo('f')
var say = Symbol('say')
f[say] = function() {
return 'other name'
}
f.say() // 'f'
f[say]() // 'other name'
for … of
以后将用这种方式作为遍历所有数据结构的统一方式,可用 break 跳出循环.同时也可以遍历像 arguments 这样的伪数组
let arr = [100,200,300,400]
for (let item of arr){
if(item > 100) break;
}
arr.forEach() // 除了return不能跳出循环
let s = new Set([1,2])
for(let item of s){//可遍历其他数据结构
console.log(item)
}
let m = new Map()
m.set('foo', '123')
m.set('bar', '546')
for (let [key, value] of m) {
console.log(key, value)
}
//无法遍历 对象
let obj = {foo: 123, bar: 456}
for (let item of obj) {
console.log(item)//报错
}
可迭代接口
实现 iterable 接口就是 for…of 的前提
//实现可迭代接口 (Iterable),内部必须有一个返回迭代器的 Iterator 方法
let obj = {
store: ['foo', 'bar', 'baz']
[Symbol.iterator]: function () {
// 实现了迭代器接口(iterator),内部必须要有一个可迭代的 next 方法
let index = 0
let self = this
return {
next: function () {
//实现迭代接口结果(IterationResult)
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for (const item of obj) {
console.log(item)
}
//使用 Generator 函数实现 iterator 方法
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶']
[Symbol.iterator]: function * () {
let all = [...this.life, ...this.learn, ...this.work]
for (let item of all) {
yield item
}
}
}
for (let item of todos) {
console.log(item)
}
迭代器模式的核心就是对外提供一个统一的遍历的接口,让外部不再关心这个内部的数据结构是怎样的
ES2016
ArrayObject.includes 查找数组中是否包含某个元素
** 用于代替Math.pow,作用跟其一致
ES2017
Object.values-----------------------返回对象当中所有的值组成的数组
Object.entries-----------------------返回对象当中所有的键值对组成的数组
Object.getOwnPropertyDescriptors------------------搭配getter和setter, Object.assign 无法复制对象的getter和setter方法,就用这个来复制
String.prototype.padStart-------------------------给字符串(i) 的开头填充指定长度的字符串(b),直到该字符串(i) 达到指定长度
String.prototype.padEnd-------------------------给字符串(i) 的结尾填充指定长度的字符串(b),直到该字符串(i) 达到指定长度
let a = '5'
let b = '10'
let c = '105'
console.log(a.padStart(2,'0')) // 05
console.log(b.padStart(2,'0')) // 10
console.log(c.padStart(2,'0')) // 105