let var const 区别
var可以重复定义
let只可以定义一次,否则报错
const
拥有 let
的所有优点,不同的是,通过 const
声明的变量是只读的。
const
声明并不会真的保护数据不被改变。 为了确保数据不被改变,JavaScript 提供了一个函数 Object.freeze()
var被视作全局变量
如果你创建一个函数,将它存储起来,稍后在使用 i
变量的 for
循环中使用。这么做可能会出现问题。 这是因为存储的函数会总是指向更新后的全局 i
变量的值。
var printNumTwo;
for (var i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
这里控制台将显示值 3
。
可以看到,printNumTwo()
打印了 3,而不是 2。 这是因为赋值给 i
的值已经更新,printNumTwo()
返回全局的 i
,而不是在 for 循环中创建函数时 i
的值。 let
关键字就不会出现这种现象:
let printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
console.log(i);
在这里控制台将显示值 2
和一个错误提示 i is not defined
。
i
未定义,因为它没有在全局范围内声明。 它只在 for
循环语句中被声明。 printNumTwo()
返回了正确的值,因为 let
关键字在循环语句中使 i
变量产生了三个不同的值(分别为 0、1、2)。
开发者会用大写字母作为常量标识符,用小写字母或者驼峰命名作为变量(对象或数组)标识符。
对象(包括数组和函数)在使用 const
声明的时候依然是可变的。 使用 const
来声明只会保证变量不会被重新赋值。
箭头函数
ES6 提供了其他写匿名函数的方式的语法糖。 你可以使用箭头函数:
const myFunc = () => {
const myVar = "value";
return myVar;
}
当不需要函数体,只返回一个值的时候,箭头函数允许你省略 return
关键字和外面的大括号。 这样就可以将一个简单的函数简化成一个单行语句。
const myFunc = () => "value";
这段代码默认会返回字符串 value
。
和一般的函数一样,你也可以给箭头函数传递参数。
const doubler = (item) => item * 2;
doubler(4);
doubler(4)
将返回 8
。
如果箭头函数只有一个参数,则可以省略参数外面的括号。
const doubler = item => item * 2;
可以给箭头函数传递多个参数。
const multiplier = (item, multi) => item * multi;
multiplier(4, 2);
ES6 里允许给函数传入默认参数,来构建更加灵活的函数。
请看以下代码:
const greeting = (name = "Anonymous") => "Hello " + name;
console.log(greeting("John"));
console.log(greeting());
控制台将显示字符串 Hello John
和 Hello Anonymous
。
rest
rest 操作符可以用于创建有一个变量来接受多个参数的函数。 这些参数被储存在一个可以在函数内部读取的数组中。
请看以下代码:
function howMany(...args) {
return "You have passed " + args.length + " arguments.";
}
console.log(howMany(0, 1, 2));
console.log(howMany("string", null, [1, 2, 3], { }));
控制台将显示字符串 You have passed 3 arguments.
和 You have passed 4 arguments.
。
使用 rest 参数,就不需要查看 args
数组,并且允许我们在参数数组上使用 map()
、filter()
和 reduce()
。
spread
下面的 ES5 代码使用了 apply()
来计算数组的最大值:
var arr = [6, 89, 3, 45];
var maximus = Math.max.apply(null, arr);
maximus
的值为 89
。
我们必须使用 Math.max.apply(null, arr)
,因为 Math.max(arr)
返回 NaN
。 Math.max()
函数中需要传入的是一系列由逗号分隔的参数,而不是一个数组。 展开操作符可以提升代码的可读性,使代码易于维护。
const arr = [6, 89, 3, 45];
const maximus = Math.max(...arr);
maximus
的值应该是 89
。
...arr
返回一个解压的数组。 也就是说,它展开数组。 然而,展开操作符只能够在函数的参数中或者数组中使用。 下面的代码将会报错:
const spreaded = ...arr;
解构赋值
解构赋值是 ES6 引入的新语法,用来从数组和对象中提取值,并优雅地对变量进行赋值。
有如下 ES5 代码:
const user = { name: 'John Doe', age: 34 };
const name = user.name;
const age = user.age;
name
的值应该是字符串 John Doe
, age
的值应该是数字 34
。
下面是使用 ES6 解构赋值语句,实现相同效果:
const { name, age } = user;
同样,name
的值应该是字符串 John Doe
, age
的值应该是数字 34
。
从对象中分配变量
可以给解构的值赋予一个新的变量名, 通过在赋值的时候将新的变量名放在冒号后面来实现。
还是以上个例子的对象来举例:
const user = { name: 'John Doe', age: 34 };
这是指定新的变量名的例子:
const { name: userName, age: userAge } = user;
你可以这么理解这段代码:获取 user.name
的值,将它赋给一个新的变量 userName
,等等。 userName
的值将是字符串 John Doe
,userAge
的值将是数字 34
。
嵌套对象解构
使用与前面的例子中类似的对象:
const user = {
johnDoe: {
age: 34,
email: 'johnDoe@freeCodeCamp.com'
}
};
这是解构对象的属性值赋值给具有相同名字的变量:
const { johnDoe: { age, email }} = user;
这是将对象的属性值赋值给具有不同名字的变量:
const { johnDoe: { age: userAge, email: userEmail }}
分离数组变量
与数组解构不同,数组的扩展运算会将数组里的所有内容分解成一个由逗号分隔的列表。 所以,你不能选择哪个元素来给变量赋值。
而对数组进行解构却可以让我们做到这一点:
const [a, b] = [1, 2, 3, 4, 5, 6];
console.log(a, b);
控制台将显示 a
和 b
的值为 1, 2
。
数组的第一个值被赋值给变量 a
,数组的第二个值被赋值给变量 b
。 我们甚至能在数组解构中使用逗号分隔符,来获取任意一个想要的值:
const [a, b,,, c] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c);
控制台将显示 a
、b
和 c
的值为 1, 2, 5
。
函数传参
在某些情况下,你可以在函数的参数里直接解构对象。
请看以下代码:
const profileUpdate = (profileData) => {
const { name, age, nationality, location } = profileData;
}
上面的操作解构了传给函数的对象。 这样的操作也可以直接在参数里完成:
const profileUpdate = ({ name, age, nationality, location }) => {
}
当 profileData
被传递到上面的函数时,从函数参数中解构出值以在函数内使用。
模板字符串
模板字符串可以使用多行字符串和字符串插值功能。
请看以下代码:
const person = {
name: "Zodiac Hasbro",
age: 56
};
const greeting = `Hello, my name is ${person.name}!
I am ${person.age} years old.`;
console.log(greeting);
控制台将显示字符串 Hello, my name is Zodiac Hasbro!
和 I am 56 years old.
。
这里发生了许多事情。 首先,这个例子使用反引号(`
),而不是引号('
或者 "
)将字符串括起来。 其次,注意代码和输出中的字符串都是多行的。 不需要在字符串中插入 \n
。 上面使用的 ${variable}
语法是一个占位符。 这样一来,你将不再需要使用 +
运算符来连接字符串。 当需要在字符串里增加变量的时候,你只需要在变量的外面括上 ${
和 }
,并将其放在模板字符串里就可以了。 同样,你可以在字符串中包含其他表达式,例如 ${a + b}
。 这个新的方式使你可以更灵活地创建复杂的字符串。
使用简洁的字面量声明
const getMousePosition = (x, y) => ({
x: x,
y: y
});
getMousePosition
简单的函数,返回拥有两个属性的对象。 ES6 提供了一个语法糖,消除了类似 x: x
这种冗余的写法。 你可以只写一次 x
,解释器会自动将其转换成 x: x
(或效果相同的内容)。 下面是使用这种语法重写的同样的函数:
const getMousePosition = (x, y) => ({ x, y });
简洁的函数声明
const person = {
name: "Taylor",
sayHello: function() {
return `Hello! My name is ${this.name}.`;
}
};
用 ES6 的语法在对象中定义函数的时候,可以删除 function
关键词和冒号。 请看以下例子:
const person = {
name: "Taylor",
sayHello() {
return `Hello! My name is ${this.name}.`;
}
};
class类
constructor
,然后使用 new
关键字来实例化一个对象:
var SpaceShuttle = function(targetPlanet){
this.targetPlanet = targetPlanet;
}
var zeus = new SpaceShuttle('Jupiter');
class
语法只是简单地替换了构造函数 constructor
的写法:
class SpaceShuttle {
constructor(targetPlanet) {
this.targetPlanet = targetPlanet;
}
}
const zeus = new SpaceShuttle('Jupiter');
应该注意 class
关键字声明了一个新的函数,里面添加了一个构造函数。 当用 new
创建一个新的对象时,构造函数会被调用。
**注意:**首字母大写驼峰命名法 UpperCamelCase 是 ES6 class 命名的惯例,就像上面的 SpaceShuttle
。
控制对象的访问
Getter 函数的作用是可以让对象返回一个私有变量,而不需要直接去访问私有变量。
Setter 函数的作用是可以基于传进的参数来修改对象中私有变量。 这些修改可以是计算,或者是直接替换之前的值。
注意: 通常会在私有变量前添加下划线(_
)。 然而,这种做法本身并不是将变量变成私有的。
插入HTML
需要在 HTML 文档里创建一个 type
为 module
的脚本。 例子如下:
<script type="module" src="filename.js"></script>
函数导入
假设有一个文件 math_functions.js
,该文件包含了数学运算相关的一些函数。 其中一个存储在变量 add
里,该函数接受两个数字作为参数返回它们的和。 你想在几个不同的 JavaScript 文件中使用这个函数。 要实现这个目的,就需要 export
它。
export const add = (x, y) => {
return x + y;
}
上面是导出单个函数常用方法,还可以这样导出:
const add = (x, y) => {
return x + y;
}
export { add };
import { add } from './math_functions.js';
在这里,import
会在 math_functions.js
里找到 add
,只导入这个函数,忽略剩余的部分。 ./
告诉程序在当前文件的相同目录寻找 math_functions.js
文件。 用这种方式导入时,相对路径(./
)和文件扩展名(.js
)都是必需的。
下面是一个从同目录下的 math_functions.js
文件中导入所有内容的例子:
import * as myMathModule from "./math_functions.js";
export default
还需要了解另外一种被称为默认导出的 export
的语法。 在文件中只有一个值需要导出的时候,通常会使用这种语法。 它也常常用于给文件或者模块创建返回值。
下面是使用 export default
的例子:
export default function add(x, y) {
return x + y;
}
export default function(x, y) {
return x + y;
}
import default
在下面的例子里,add
是 math_functions.js
文件的默认导出。 以下是如何导入它:
import add from "./math_functions.js";
这个语法有一处特别的地方, 被导入的 add
值没有被花括号({}
)所包围。 add
只是一个变量的名字,对应 math_functions.js
文件的任何默认导出值。 在导入默认导出时,可以使用任何名字。
promise
Promise 有三个状态:pending
、fulfilled
和 rejected
。
Promise 成功时调用 resolve
,promise 执行失败时调用 reject
Promise 是异步编程的一种解决方案 - 它在未来的某时会生成一个值。 任务完成,分执行成功和执行失败两种情况。 Promise
是构造器函数,需要通过 new
关键字来创建。 构造器参数是一个函数,该函数有两个参数 - resolve
和 reject
。 通过它们来判断 promise 的执行结果。 用法如下:
const myPromise = new Promise((resolve, reject) => {
});
const myPromise = new Promise((resolve, reject) => {
if(condition here) {
resolve("Promise was fulfilled");
} else {
reject("Promise was rejected");
}
});
then
当程序需要花费未知的时间才能完成时(比如一些异步操作),一般是服务器请求,promise 很有用。 服务器请求会花费一些时间,当结束时,需要根据服务器的响应执行一些操作。 这可以用 then
方法来实现, 当 promise 完成 resolve
时会触发 then
方法。 例子如下:
myPromise.then(result => {
});
result
即传入 resolve
方法的参数。
catch
当 promise 失败时会调用 catch
方法。 当 promise 的 reject
方法执行时会直接调用。 用法如下:
myPromise.catch(error => {
});
error
是传入 reject
方法的参数。
同步异步
举个例子来说,一家餐厅吧来了5个客人,同步的意思就是说,来第一个点菜,点了个鱼,好, 厨师去捉鱼杀鱼,过了半小时鱼好了给第一位客人,开始下位一位客人,就这样一个一个来,按顺序来
相同, 异步呢,异步的意思就是来第一位客人,点什么,点鱼,给它一个牌子,让他去一边等吧,下一位客人接着点菜,点完接着点让厨师做去吧,哪个的菜先好就先端出来,
同步的优点是:同步是按照顺序一个一个来,不会乱掉,更不会出现上面代码没有执行完就执行下面的代码, 缺点:是解析的速度没有异步的快;
异步的优点是:异步是接取一个任务,直接给后台,在接下一个任务,一直一直这样,谁的先读取完先执行谁的, 缺点:没有顺序 ,谁先读取完先执行谁的 ,会出现上面的代码还没出来下面的就已经出来了,会报错;