作用域
ES6之前只有两个变量作用域(全局作用域和局部作用域);ES6中新增了块级作用域
例如:
if(true) {
var foo = 'sfy'
}
console.log(foo)
// 这里我们很明显都知道他的结果就是 sfy
但是如果是下面的这种方式
if(true) {
let foo = 'sfy'
}
console.log(foo)
// 这次的结果就和上面的不一样了;他会出现:ReferenceError: foo is not defined 。
// 从这个例子我们就能看出来块级作用域的作用。
//那么我们可以用它来解决什么问题呢?
如果是初学者,肯定会对下面的代码有疑问
<div>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
</div>
const btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log(`点击了第${i + 1}个按钮`)
}
}
// 对于初学者来说;他的输出肯定都是点了第几个按钮;就会在控制台输出第几个按钮;但是结果并不是这样;而是不管你点击哪个按钮;控制台输出的都是第五个按钮;
// 可能很多小伙伴到这里就糊涂了,不知道为什么会这样。这里其实是js运行机制问题,如果把这个运行机制弄明白了,也就自然而然的知道原因了;这里小编就不详细解释了;我们直接说这个问题的解决方案吧。
// 对于上面的代码我们可以这样改写
const btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function (i) {
return function() {
console.log(`点击了第${i + 1}个按钮`)
}
})(i)
}
// 这里我们用到了闭包来解决这个问题
// 我们也可以用ES6中提供的块级作用域来解决这个问题
const btns = document.querySelectorAll('button');
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log(`点击了第${i + 1}个按钮`)
}
}
var / let / const相关知识点
我们都知道var有变量提升的作用;但是let有提升作用吗?
当我们执行下面的代码就知道了
console.log(bar)
let bar = 'hello'
// 结果会报:Cannot access 'bar' before initialization;
// 我们可以看出来他不是说bar没有定义;说明是有提升;但是不能在定义前面调用;原因是因为let定义的变量有提升,但是陷入暂时性死区;所以不能再定义前调用。
ES6中新增了const关键词 去声明一个只读的恒量和常量 就是在let的基础上多了只读(声明过后不允许再被修改。const声明的变量必须有初始值;并且不能够修改它所指向的内存地址。
数组解构
// 在ES6之前呢,我们想要获取数组中的元素可以通过对应的索引值来获取;但是呢;ES6中提供了新的方法。
const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz) // 100 200 300
// 这样来获取数组中的元素是不是更方便一点呢
// 如果我们只想获得最后一个元素怎么办呢?
const [, , baz1] = arr
console.log(baz1) // 结果显而易见就是300
// 但是当我们想获得从当前位置到最后的所有成员的时候就要用到剩余参数了。剩余参数在下面有介绍。
const [foo1, ...rest] = arr
console.log(rest) // 结果死一个数组 [200, 300]
// 当然了,我们也可以在数组解构的时候加上一个新的元素;并且给他附上默认值;但是它不改变原数组
const [one, tow, three, four = 400] = arr
console.log(four)
// 举个数组解构的例子:想要获取西面地址中的中间地址
const path = 'foo/bar/baz'
const [, roodir] = path.split('/')
console.log(roodir);
对象解构
const obj = { name: 'zs', age: 19, atm: 'rose' }
const { name } = obj
console.log(name); // zs
// 如果匹配没有的属性,结果就是undefined
const { hobby } = obj
console.log(hobby) // undefined
// 也可以设置默认值在对象解构的时候
const { class = '三年级8班'} = obj
console.log(class)
// 在对象解构中会存在一个问题;那就是会出现命名冲突问题。
// 例如:
const atm = 'tom'
const { atm } = obj
console.log(atm) // 这个时候就会报错
// 这个时候我们可以使用重命名方法来避免这个错误
const { atm: Aname } = obj
console.log(Aname) // rose
// 应用实例
const { log } = console
log('hello wrold') // hello world 我们可以这么赖在控制台打印
定义字符串方式
ES6中新增了``来定义字符串的方式;它可以支持多行输入,
const str = `hello world!
I am \`tom\``
console.log(str);
// 我们可以在其中嵌入值
const name = 'tom'
const msg = `hello, ${name};你今年是不是${10 + 15}岁了,----${Math.random()}`
console.log(msg);
// 带标签的模板字符串
const str = console.log`hello world` // [ 'hello world' ]
const name1 = 'tomes'
const gender = true
// 定义一个标签函数
function myTagFunc (strings, name1, gender) {
// 这里的strings 就是 [ 'hey, ', ' is a ', '' ]
const sex = gender ? 'man' : 'woman'
return strings[0] + name1 + strings[1] + sex + ' ' + gender
}
// 使用该标签函数
const result = myTagFunc`hey, ${name1} is a ${gender}`
console.log(result); // hey, tomes is a man true
// ES6中还提供了几个方法
// 查看字符串中是否包含某个字符
.includes()
// 查看是否以某个字符开头
.startsWith(目标字符)
.endsWith() 查看是否以什么字符结尾。
参数默认值、剩余参数
设置函数默认值方法
function foo (enable = true) {
// enable = enable || true
enable = enable === undefined ? true : enable
console.log('foo 设置参数默认值');
console.log(enable);
}
foo()
剩余参数…args 。
…args剩余参数只能使用一次;并且必须在参数的最后位置
function foo (...args) {
console.log(args);
}
foo(1, 2, 3, 4, 5, 6)
箭头函数
// 这个我相信大家都熟悉的不能再熟悉了
const inc = (n, m) => n + m + 1
console.log(inc(100, 1));
// 箭头函数不影响this指向
对象自变量增强、对象扩展方法
const bar = '345'
const za = '789'
const obj = {
foo: 123,
bar: bar,
za, // 如果变量名和表示值得变量名是一样的;可以直接写变量就行了
methods () {
console.log('你好');
console.log(this);//这个会指向当前这个对象
},
[Math.random()]: 123 //动态增加属性名 以前都是在外面obj[Math.random()] = 123这么增加属性名的
}
console.log(obj);
// ----------------------对象扩展方法------------------------
// assign() 将多个源对象中的属性复制到一个目标对象中 Object.assign方法
const source1 = {
a: 123,
b: 123
}
const source2 = {
ac: 123234,
bd: 23456745
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1, source2) // 用后面的属性去覆盖第一个的属性;返回的对象就是目标对象
console.log(target); // { a: 123, c: 456, b: 123, ac: 123234, bd: 23456745 }
console.log(result === target); // true
console.log(0 == false); // == 自动转换数据类型 true
console.log(0 === false); // false ===不会转换数据类型
console.log(+0 === -0); // true
console.log(NaN === NaN); // false
// 通过Object.is()就可以区分 + 0和 - 0
console.log(
Object.is(+0, -0), // false
Object.is(NaN, NaN) // true
);
// 监控对象中的属性读写 过程
// Object.defineProperty()
// es2015加入了Proxy
const person = {
name: 'sfy',
age: 20
}
const personProxy = new Proxy(person, {
get (target, property) {
console.log(target, property, '读');
return property in target ? target[property] : 'default'
},
set (target, property, value) {
console.log(target, property, value, '写');
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
console.log(personProxy.name);
personProxy.gender = true
personProxy.age = 25
console.log(person); // { name: 'sfy', age: 25, gender: true }
// Object.defineProperty 和 Proxy的区别
// Object.defineProperty 只监视属性的读写
// Proxy 可以监视更多;对对象的操作;delete;调用等
const person = {
name: 'sfy',
age: 20
}
const personProxy = new Proxy(person, {
deleteProperty (target, property) {
console.log('delete', property);
delete target[property]
}
})
delete personProxy.age
console.log(person);
Proxy更好的支持数组对象的监视
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value);
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
// Proxy 是以非入侵的方式监管了对象的读写。
Reflect
// Reflect内部封装了一系列对对象的底层操作
const obj = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(obj, {
// 如果我们没有定义get、或者set方法;就是默认创建了get;然后把get/set的参数都交给Reflect来处理
get (target, property) {
console.log('这里是自己写的监视逻辑');
return Reflect.get(target, property)
}
})
console.log(proxy.foo);
// Reflect 意义:同意提供了一套用于操作对象的API
const obj = {
name: '烛照',
age: 25,
class: '3-2'
}
console.log('name' in obj);
console.log(delete obj['name']);
console.log(Object.keys(obj)); // 获取对象的所有属性名
// Reflect提供了上述所需要实现的方法
// console.log(Reflect.has(obj, 'name')); // true
// console.log(Reflect.deleteProperty(obj, 'name'));
// console.log(obj);
// console.log(Reflect.ownKeys(obj)); // 获取对象的所有属性名
Class类
ES6中提供了更简便的定义类的方法
es6之前定义类的方法:
function Preson (name) {
this.name = name
}
Preson.prototype.say = function () {
console.log(`hi, my name is ${this.name}`);
}
ES6中新增的定义类的方法:
class Person {
// 构造函数
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`);
}
// 定义一个静态方法
static create (name) {
return new Person(name)
}
}
const p = new Preson('tom')
p.say() // 调用普通方法
// 调用静态方法
const tom = Person.create('zhuzhao')
tom.say()
// 类的继承
class Student extends Person {
constructor (name, number) {
super(name)
this.number = number
}
hello () {
super.say()
console.log(`my school number is ${this.number}`);
}
}
const s = new Student('jack', '200')
s.hello()
Set数据结构
Set内部的值不允许重复;也就是说set内部的值都是惟一的
const s = new Set()
s.add(1).add(2).add(3).add(4).add(2)
console.log(s); //Set(4) { 1, 2, 3, 4 }
console.log(s.size); // 获取集合的长度
console.log(s.has(100)); // false 查看集合中是否包含某个值
console.log(s.delete(3)); // ture 代表删除成功
console.log(s); // Set(3) { 1, 2, 4 }
// 清除当前集合中的全部内容
s.clear()
console.log(s); // Set(0) {}
应用地方:可以给数组去重
const arr = [1, 2, 3, 4, 5, 6, 90, 7, 5, 43, 3, 2, 1]
const result = new Set(arr)
console.log(result); // Set(8) { 1, 2, 3, 4, 5, 6, 90,7, 43 }
console.log(Array.from(result)); // 把set集合重新转换为一个数组
console.log([...result]);
Map数据结构
map数据结构和对象的最大的区别就是它可以以任意类型的数据作为键;而对象实际上只能用字符串
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj)); //[ '123', 'true', '[object Object]' ] 结论:当我们给的键不是字符串的时候;会自动转成字符串类型
console.log(obj[{}]); // value
console.log(obj['123']); // value
const m = new Map()
const tom = { name: 'tom' }
m.set(tom, 90)
console.log(m); // Map(1) { { name: 'tom' } => 90 }
console.log(m.get(tom)); // 90
他也有下面三个方法
// m.has()
// m.delete()
// m.clear()
Symbol
const cache = {}
cache['foo'] = Math.random()
cache['foo'] = '234'
console.log(cache);
const s = Symbol() //特点:通过Symbol创建的值是唯一的
console.log(s, typeof s);
console.log(Symbol('foo'));
console.log(Symbol('fo2'));
const obj = {
[Symbol()]: 123
}
console.log(obj);
//私有成员
// a.js文件 ============================
const name = Symbol()
const person = {
[name]: 'sfy',
say () {
console.log(this[name]);
}
}
//b.js文件调用say
person.say()
// Symbol的唯一性
console.log(Symbol() === Symbol()); // false
// 当我们想使用上一个Symbol值得时候可以赋值给一个全局变量或者用Symbol里面提供的静态方法for实现
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2); // true 维系的时候字符串和Symbol对应的关系;如果不是字符串就会把它转换成字符串
console.log(Symbol.for(true) === Symbol.for('true')); // true
const obj = {}
console.log(obj.toString()); // [object Object]
const obj1 = {
// toStringTag是Symbol内置常量
[Symbol.toStringTag]: 'Xobject' // 自定义字符串标签(就是toSting()之后打印的结果)
}
console.log(obj1.toString()); // [object Xobject]
// 但是Symbol类型的属性名没有办法通过for循环来拿到;或者Object.keys()来获取属性名也获取不到
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for (let key in obj) {
console.log(key, '999'); // 获取不到Symbol的值
}
console.log(Object.keys(obj)); // Object.keys只能获取到对象中字符串的属性名
console.log(Object.getOwnPropertySymbols(obj)); // 这个只能获取到Symbol类型的属性名
for…of…循环
const arr = [1, 2, 3, 4, 5]
for (const item of arr) {
console.log(item); // for...of拿到的直接就是元素而不是下标
}
for (const item of arr) {
console.log(item); // for...of比forEach的优点还有一个就是可以使用break终止循环
if (item > 2) {
break
}
}
// 可以遍历Set对象
const s = new Set(['foo', 'zar'])
for (const item of s) {
console.log(item);
}
// 遍历map对象
const m = new Map()
m.set('foo', '123')
m.set('bar', '122')
for (const item of m) {
console.log(item); // item的值就是 [ 'foo', '123' ]
}
for (const [key, value] of m) {
console.log(key, value); // item的值就是 foo 123
}
// 遍历普通对象
const obj = { foo: 123, bar: 456 }
for (const item of obj) {
console.log(item); // 会报错TypeError: obj is not iterable
}
是不是很多小伙伴都蒙了;为什么遍历普通对象会报错,还提示obj不是一个可迭代的对象;如果你想知道原因;请看小编的下一篇博客ES6新增的特性(下)。