1 ECMAScrity 与 JavaScript 的区别
- ECMAScript 语言层面,规范
- javascript 实现了ecmaScript的语言的标准
- JavaScript= dom + bom + ecmaScript
- ECMAScript 2015(ES2015)
#2 作用域: 代码中的某个成员能够起作用的范围
- 全局作用域
- 函数作用域
- 块级作用域 es6中新增的块级作用域,一堆花括号之间形成的作用域 如:
if (true) {
console.log('aaa')
}
for(var i = 0; i < 10; i++) {
console.log('aaa')
}
以前块是没有独立的作用域的,导致我们在块中定义的作用域,外面也能访问到
if (true) {
var foo = 'aaa'
}
console.log(foo)
es6中可以使用 let const 建立块级作用域
for(var i = 0; i < 3; i++) {
for(var i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层是i3') // 不满如循环条件
}
只打印3次是因为用var声明的i,不是块级作用域,是全局变量,外层i声明完以后,内层在声明一次, 解决全局变量重名的问题
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = function() {
console.log(i)
}
}
elements[2].onclick() // 都会打印3
因为i是全局变量,所以自循环完以后是3,无论什么时候打印都是3
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = (function(i) {
console.log(i)
})(i)
}
elements[2].onclick() // 都会打印3
可以使用闭包来解决这个问题,闭包借助函数作用域来摆脱全局作用域所产生的的影响
var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = function() {
console.log(i)
}
}
elements[2].onclick() // 都会打印3
使用let 创建块级作用域
let 不会出现变量提升
#1、 const对象的属性可以修改吗?
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
#2、 如果new一个箭头函数的会怎么样
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。
new操作符的实现步骤如下:
- 创建一个对象
- 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
- 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
- 返回新的对象
所以,上面的第二、三步,箭头函数都是没有办法执行的。
#3、箭头函数与普通函数的区别
#(1)、箭头函数比普通函数更加简洁
- 如果没有参数,就直接写一个空括号即可
- 如果只有一个参数,可以省去参数的括号
- 如果有多个参数,用逗号分割
- 如果函数体的返回值只有一句,可以省略大括号
- 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
let fn = () => void doesNotReturn();
1
#(2)、箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
#(3)、箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
#(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
#(5) 箭头函数不能作为构造函数使用
构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
#(6) 箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
#(7)箭头函数没有prototype
#(8) 箭头函数不能用作Generator函数,不能使用yeild关键字
#4、箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
可以⽤Babel理解⼀下箭头函数:
// ES6
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
}
}
转化后:
// ES5,由 Babel 转译
var obj = {
getArrow: function getArrow() {
var _this = this;
return function () {
console.log(_this === obj);
};
}
};
#3 对象解构
const obj = { name: 'cyz', age: 18 }
const { name : objName = 'jack' } = obj // 当有重名时,objName 为新的名字
console.log(objName) ==> 'cyz'
const { log } = console
log(123)
如何提取高度嵌套的对象里的指定属性? 有时会遇到一些嵌套程度非常深的对象:
const school = {
classes: {
stu: {
name: 'Bob',
age: 24,
}
}
}
像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:
const { name } = school
1
显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:
const { classes } = school
const { stu } = classes
const { name } = stu
name // 'Bob'
但是还有一种更标准的做法,可以用一行代码来解决这个问题:
const { classes: { stu: { name } }} = school
console.log(name) // 'Bob'
可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。
#4 带标签的模板字符串
// 模板字符串 带标签的模板字符串
// console.log(123)
const name = 'tom'
const gender = true
function myTagFunc(strings, name, gender) {
// console.log(strings, name, gender)
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey,${name} is a ${gender}`
hey,tom is a man
#5 字符串的扩展方法
const message = 'Error: foo id not defined'
console.log(
message.startWith('Error') // true
message.endsWith('defined') // true
message.includes('foo') // true
)
#6 参数默认值
// 带参数默认值的参数在后面,避免使用短路运算来设置默认值
//(默认值应为参数为undefined的时候设置,规避false也使用参数默认值)
function foo (bar, enable = true) {
console.log('foo invoked - enable: ')
console.log(enable)
}
foo(false)
#7 剩余参数
// ... 展开剩余参数 只能在形参的最后一位,只能使用一次
function foo(...args) {
console.log(args) // [ 1, 2, 3, 4 ]
}
foo(1,2,3,4)
扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组
function mutiple(...args) {
let result = 1;
for (var val of args) {
result *= val;
}
return result;
}
mutiple(1, 2, 3, 4) // 24
这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组:
function mutiple(...args) {
console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]
这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。
#8 展开运算符
#(1) 对象扩展运算符
对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }
上述方法实际上等价于:
let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。
同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
let bar = {a: 1, b: 2};
let baz = {...bar, ...{a:2, b: 4}}; // {a: 2, b: 4}
利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数,reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。
需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。
#(1)数组扩展运算符
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr)
console.log(...arr) // 'foo' 'bar' 'baz'
下面是数组扩展运算符符的应用:
- 将数组转换为参数序列
function add(x, y) {
return x + y;
}
const numbers = [1, 2];
add(...numbers) // 3
- 复制数组
const arr1 = [1, 2];
const arr2 = [...arr1];
要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。
- 合并数组 如果想在数组内合并数组,可以这样:
const arr1 = ['two', 'three'];
const arr2 = ['one', ...arr1, 'four', 'five'];
// ["one", "two", "three", "four", "five"]
- 扩展运算符与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
1
2
3
需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...rest, last] = [1, 2, 3, 4, 5]; // 报错
const [first, ...rest, last] = [1, 2, 3, 4, 5]; // 报错
- 将字符串转为真正的数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
1
- 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组 比较常见的应用是可以将某些数据结构转为数组:
// arguments对象
function foo() {
const args = [...arguments];
}
用于替换es5中的Array.prototype.slice.call(arguments)写法。
- 使用Math函数获取数组中特定的值
const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
Math.max(...numbers); // 9
#9 箭头函数
不会改变this执行,始终都是当前作用域的this
const inc = n => n + 1
const person = {
name: 'tom',
say: function() {
console.log(this.name) // tom
}
ai: () => {
console.log(this.this) // undefined
}
}
#10 对象字面量
const obj = {
foo: 123,
bar,
method1() {
console.log(22)
},
method1: function() {
// 和上面的写法是等价的
},
// 计算属性名 es6中可以动态的去屑计算属性名
[bar]: 123,
[a + b]: 222,
[Math.random()]: 123,
}
obj[Math.random] = 123
#11 Object.assign()
const source1 = {
a: 123,
b: 123,
}
const target = {
a: 456,
c: 456,
}
const result = Object.assign(target, source1) // target目标对象
console.log(target) // {a: 123, b: 456, c: 343}
console.log(result === target) // true
// 将source1对象覆盖前面target对象,并返回前面的那个对象
Object.is()
console.log(Object.is(NaN, NaN))
#12 proxy
const person = {
name: 'cuy',
age: 10
}
const personProxy = new Proxy(person, {
get(target, property){
return property in target ? target[property] : 'default'
},
set(target, property, value){
if(property === 'age'){
if(!Number.isInteger(value)){
throw new TypeError(`${value} is not an int`)
}
}
console.log(target, property, value)
}
})
// get 取值时触发的方法 target: 设置的对象 property: 设置的属性值
// set 设置值时厨房的方法 target: 对象 property: 属性值 value: 设置的值
#13 Proxy vs Object.defineProperty
- defineProperty 只能监视属性的读写,特意的方式去监视对象
- Proxy能够监视到更多的对象操作,非入侵的方式去监视对象
const person = {
name: 'cuy',
age: 10
}
const personProxy = new Proxy(person, {
deleteProperty(target, property) { // 代理对象,删除的属性名
console.log('delete', property)
delete target[property]
},
})
| handler方法 | 触发方式 |
|---|---|
| get | 读取某个属性 |
| set | 写入某个属性 |
| has | in操作符 |
| deleteProperty | delete 操作符 |
| getProperty | Object.getPropertypeOf() |
| setProperty | Object.setPropertypeOf() |
| isExtensible | Object.isExtensible() |
| preventExtensions | Object.preventExtensions() |
| getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() |
| defineProperty | Object.defineProperty() |
| ownKeys | Object.keys()、 Object.getOwnPropertyNames()、Object.getOwnPropertySymbols() |
| apply | 调用一个函数 |
| construct | 用new调用一个函数 |