三天精通Vue--ES6的常用语法
es5中使用var来声明全局变量
es5中我们学习了使用var来声明变量,但是使用var声明变量,会存在变量提升的问题。即变量可以在声明之前使用,值为undefined
。
栗子1:
console.log(a); // var 的结果:undefined-->变量提升 { var a = 2; console.log(a); // 2 } console.log(a); // var 的结果:2-->变量提升 //在js中一个{},称为作用域,使用var声明变量,会存在变量提升的问题。 //什么是变量提升呢?当解析脚本的时候,当在脚本中遇到var声明的变量,会将 var a;提到最上面去声明。从而导致此问题。由此也会使,a是一个全局的变量 //上面的在局部作用域声明并赋值给a=2,相当于这样将a声明为一个全局作用域的变量,然后在局部作用域对a赋值为2. var a; //声明全局变量 console.log(a); // var 的结果:undefined { a = 2; // 在局部作用域对全局变量的a赋值 console.log(a); } console.log(a); // var 的结果:2
栗子2:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10 //上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。 //每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i), //里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i, //导致运行时输出的是最后一轮的i的值,也就是 10。
ES6 新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。与使用var来声明变量相比, 使用let声明变量有以下几个特点:
1.不存在变量提升
2.不允许重复声明
3.块级作用域
1.不存在变量提升
栗子1:
console.log(a); // let 的结果:Uncaught ReferenceError: a is not defined { let a = 2; console.log(a); //2 } console.log(a); // let 的结果:Uncaught ReferenceError: a is not defined
使用let
,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6 //上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量, //所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值, //从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时, //就在上一轮循环的基础上进行计算。
另外,for
循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc //上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
let
不允许在相同作用域内,重复声明同一个变量。
// 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; }
因此,不能在函数内部重新声明参数。
function func(arg) {//函数的参数和函数内部是同一个作用域,这点和for循环不同 let arg; } func() // 报错 function func(arg) { { let arg; } } func() // 不报错
3.块级作用域
为什么需要块级作用域?
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var num=1; console.log(num); //1 { var num = 11; console.log(num) //11 } console.log(num); //11-->其实还是变量提升
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5 //上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
栗子
let num=1; console.log(num); //1 { let num = 11; //内层作用域重复定义全局变量,只在当前作用域有效 console.log(num) //11 } console.log(num); //1 //注意区分: let num=1; console.log(num); //1 { num = 11; //在内层作用域内修改全局变量 console.log(num) //11 } console.log(num); //11 //ES6 允许块级作用域的任意嵌套。内层作用域可以重复定义、可以覆盖(通过赋值的方式)、可以直接使用 //外层作用域的同名变量,而外层作用域可以重复定义、可以覆盖(通过赋值的方式)、不能直接使用内层定义的变量。 {{{{ {let insane = 'Hello World'} console.log(insane); // 报错,外层不能直接使用内层的变量 }}}}; //上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。 {{{{ let insane = 'Hello World'; {let insane = 'Hello World'} //内层作用域可以定义外层作用域的同名变量 }}}};
1.不存在变量提升
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
2.不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。
注意: for循环括号中设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。 而函数的形参和函数内部是同一个作用域。
3.块级作用域
实际上还是利用了let
命令不存在变量提升,从而方便作用域的任意嵌套。
内层作用域可以重复定义、可以覆盖(通过赋值的方式)、可以直接使用外层作用域的同名变量,而外层作用域可以重复定义、可以覆盖(通过赋值的方式)、不能直接使用内层定义的变量。
注意:for循环的花括号不是作用域,只有函数的花括号才是作用域.
const
声明一个常量
const声明的变量跟let类似,同样拥有上面的三个特性,但是const声明的是常量。
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable. //上面代码表明改变常量的值会报错。
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo; // SyntaxError: Missing initializer in const declaration //上面代码表示,对于const来说,只声明不赋值,就会报错。
注意:
const
命令声明的常量也是不提升,只能在声明的位置后面使用,否则报错。
const
声明的常量,也与let
一样不可重复声明(包括不能与var/let声明的变量重复)。
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它(模板字符串)可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量(插入变量使用${})。
// 普通字符串 `In JavaScript '\n' is a line-feed.` // 多行字符串 `In JavaScript this is not legal.` console.log(`string text line 1 string text line 2`); // 字符串中嵌入变量 let name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?`
es5的普通函数中使用function关键字来声明函数.
//普通函数:es5需要这样来定义函数 function add() { return 5; } add() // 函数表达式:即匿名函数的函数名声明在等号左边(如果不声明函数名则会报错) let add2 = function(){ return 5; }; add2() // 自执行函数:在匿名函数的基础上 (function(x,y){ return x+y; })(1,2); // 闭包函数:这里就不写了,以免你懵逼...
.......
//语法规则 //箭头函数 等价于 匿名函数 (形参)=>{函数体} ================> function(形参){函数体} //栗子 let add3 = (a,b)=>{ let c = a+b; return a+b+c; }; console.log(add3(3,4)); //等价于 let add3 = function(a,b){ let c = a+b; return a+b+c; }; console.log(add3(3,4));
如果形参只有一个,可以省略圆括号;如果函数体的代码块部分只有一条语句,并且是返回值语句,则可以省略花括号。栗子:
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
// 报错 let getTempItem = id => { id: id, name: "Temp" }; // 不报错 let getTempItem = id => ({ id: id, name: "Temp" });
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn = () => void doesNotReturn();
对象(object)是 JavaScript 最重要的数据结构,(类似于Python中的对象,有属性和方法这两种成员,写法类似于Python中的字典,花括号中是键值对的形式)。ES6 对它进行了重大升级。
//es6中类的定义以及调用==>更接近Python等其他高级语言的类的定义方式 class Student{ //对象的单体模式:其实就是普通函数的简单写法,所以this的指向和普通函数是一样的,都是指向调用该方法的对象 constructor(name,age){//构建函数 this.name = name; this.age = age; } fav(){//单体模式 console.log(this.name); } } //实例化 let s1 = new Student('alex',18); s1.fav();
属性的简洁表示法
const foo = 'bar'; const baz = {foo}; baz // {foo: "bar"} // 等同于 const baz = {foo: foo};
function f(x, y) { return {x, y}; } // 等同于 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2}
除了属性简写,方法也可以简写(,这样的书写方式我们称为对象的单体模式)。
const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };
下面是一个实际的例子。
let birth = '2000/01/01'; const Person = { name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); } };
这种写法用于函数的返回值,将会非常方便。
function getPoint() { const x = 1; const y = 10; return {x, y}; } getPoint() // {x:1, y:10}
简洁写法在打印对象时也很有用。
let user = { name: 'test' }; let foo = { bar: 'baz' }; console.log(user, foo) // {name: "test"} {bar: "baz"} console.log({user, foo}) // {user: {name: "test"}, foo: {bar: "baz"}}
小结: 对象的三种声明方式
//普通函数:字面量方式创建对象,等价于fav:function() let person = { name:'超哥', age:18, fav(){ // this指的是person console.log(this.name); } }; person.fav();//person对象调用的fav方法,则fav方法中的this指的就是person对象 //箭头函数 let person2 = { name:'超哥2', age:188, fav:()=>{ // this指的是person的父类,也就是谁定义了person对象,这里是window对象 console.log(this); } }; person2.fav();//person对象调用的fav方法,则fav方法中的this指的是person对象的父类 //es6中类的定义以及调用==>更接近Python等其他高级语言的类的定义方式 class Student{ //对象的单体模式:其实就是普通函数的简单写法,所以this的指向和普通函数是一样的,都是指向调用该方法的对象 constructor(name,age){ this.name = name; this.age = age; } fav(){ console.log(this.name); } } let s1 = new Student('alex',18); s1.fav();
关于this的指向,⼤家不要看调用方法的对象是谁,要看被调用的方法此时在当前对象内部 使⽤的是普通函数和对象的单体模式写法,还是箭头函数。
1.如果是普通函数或者对象的单体模式写法,那么该this指向调用函数的对象。
//普通函数:字面量方式创建对象,等价于fav:function() let person = { name:'超哥', age:18, fav(){ // this指的是person console.log(this.name); } }; person.fav();//person对象调用的fav方法,则fav方法中的this指的就是person对象 //箭头函数 let person2 = { name:'超哥2', age:188, fav:()=>{ // this指的是person的父类,也就是谁定义了person对象,这里是window对象 console.log(this); } }; person2.fav();//person对象调用的fav方法,则fav方法中的this指的是person对象的父类 //es6中类的定义以及调用==>更接近Python等其他高级语言的类的定义方式 class Student{ //对象的单体模式:其实就是普通函数的简单写法,所以this的指向和普通函数是一样的,都是指向调用该方法的对象 constructor(name,age){ this.name = name; this.age = age; } fav(){ console.log(this.name); } } let s1 = new Student('alex',18); s1.fav();
对于箭头函数,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this