ECMAScript(简称ECMA或ES)和JavaScript(简称JS)的关系是什么呢?
ES是标准,JS是实现。
变量声明
ES5中var有什么缺陷呢?
var可以重复声明变量,带来的问题是会覆盖掉别人已经声明的变量。
无法限制var声明变量的修改
变量作用域
JS的作用域只有全局作用域global scope和函数作用域function scope两种,并没有块级作用域。所以,在JS中的代码块中使用var声明的变量的作用域都是隶属于全局作用域的。
var price = 100;
var count = 5;
if(count > 3){
var discount = price * 0.5;
console.log(discount);//50
}
console.log(discount);//50
函数内声明的局部变量的作用域只在函数内有效
function fn(){
var cnt = 5;
console.log(cnt);
}
fn();//5
console.log(cnt);//Uncaught ReferenceError: cnt is not defined
ES6中有两种定义变量的方式,分别是const和let关键字。
使用关键字let声明变量
let声明的变量是可以被修改的
let声明的变量不能被重复声明
let discount = 10;//作用域在全局
let discount = 100;//Uncaught SyntaxError: Identifier 'discount' has already been declared
let声明的变量拥有块级作用域block scope
let count = 5;
let discount = 10;//作用域在全局
if(count > 3){
let discount = 100;//作用域在块级
}
console.log(discount);//10
使用关键字const声明常量
const声明的常量是不可以被修改的
const pi = 3.14;
pi = 3.1415;//Uncaught TypeError: Assignment to constant variable.
如果使用const声明的是对象(引用),那么是可以改变对象的成员属性的,不变的是引用指针所指向的地址。
const obj = {id:1, name:"alice"};
obj.id = 2;
console.log(obj);//{id: 2, name: "alice"}
如果不允许改变对象的属性值,可使用Object.freeze(object)方法来冻结。
const obj = {id:1, name:"alpha"};
const o = Object.freeze(obj);
console.log(o);//{id: 1, name: "alpha"}
obj.id = 2;
console.log(obj);//{id: 1, name: "alpha"}
o.id = 3;//Attempt to assign to const or readonly variable
console.log(o);//{id: 1, name: "alpha"}
const声明的常量不能重复声明
const声明的常量拥有块级作用域
变量与常量
ES6中使用let声明变量的值是允许修改的,使用const声明的常量的值是禁止修改的。
let i = 1;
i = 10;
console.log(i);//10
const PI = 3.14;
PI = 3.14159;//Uncaught TypeError: Assignment to constant variable.
变量重复声明
ES5中使用var声明的变量是可以被重复赋值的
var i = 1;
console.log(i);//1
var i = 10;
console.log(i);//10
ES6中使用let声明的变量禁止重复声明
let i = 1;
let i = 10;//Uncaught SyntaxError: Identifier 'i' has already been declared
ES6中使用const声明的常量禁止重复声明
const PI = 3.14;
const PI = 3.14;//es6.js:2 Uncaught SyntaxError: Identifier 'PI' has already been declared
块级作用域
ES5中没有块级作用域,只有函数作用域。
使用ES5中的var声明变量,由于缺乏块级作用域,带来了问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
window.onload = function(){
var btns = document.getElementsByTagName("button");
for(var i=0; i<btns.length; i++){
btns[i].onclick = function(){
alert(i);
}
}
}
</script>
</body>
</html>
问题:每次都会输出3
使用函数闭包的方式解决块级作用域问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
window.onload = function(){
var btns = document.getElementsByTagName("button");
for(var i=0; i<btns.length; i++){
(function(i){
btns[i].onclick = function(){
alert(i);
}
})(i);
}
}
</script>
</body>
</html>
使用ES6中的let声明的变量具有块级作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>es6</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
window.onload = function(){
let btns = document.getElementsByTagName("button");
for(let i=0; i<btns.length; i++){
btns[i].onclick = function(){
alert(i);
}
}
}
</script>
</body>
</html>
IIFE 立即执行函数表达式
使用let和const替代立即执行函数表达式(IIFE, Immediately-Invoked Function Expression),立即执行函数表达式用于生成一个私有变量。
由于ES5中使用var声明的变量,很容易就会污染全局作用域。例如JS的window对象具有一个name的属性,如果直接使用var定义一个全局变量name则会直接覆盖window对象的name属性。
console.log(name, window.name);
var name = "junchow";
console.log(name, window.name);//junchow junchow
使用立即执行函数表达式可以有效地避免污染全局变量,使变量私有化。
console.log(name, window.name);//"" ""
(function(){
var name = "junchow";
console.log(name, window.name);//junchow ""
})();
console.log(name, window.name);//"" ""
使用立即执行函数表达式,虽然使变量私有化了,但并不利用代码的可读性。
const name = "junchow";
console.log(name, window.name);//junchow ""
let name = "junchow";
console.log(name, window.name);//junchow ""
{
const name = "junchow";
console.log(name, window.name);//junchow ""
}
怪异的for循环
例如:在for循环中经常会遇到的问题
for(var i=0; i<3; i++){
console.log(i);// 0 1 2
}
console.log(i);//3
for(var i=0; i<3; i++){
console.log(i);//0 1 2
setTimeout(function(){
console.log(i);// 3 3 3
},1000);
}
console.log(i);//3
使用ES6的let声明循环变量
for(let i=0; i<3; i++){
console.log(i);//0 1 2
setTimeout(function(){
console.log(i);// 0 1 2
},1000);
}
console.log(i);//Uncaught ReferenceError: i is not defined
临时性死区
ES6中,在代码块内使用let或const声明变量前,变量都是不可用的。在语法上,称之为临时性死区(TDZ, Temporal Dead Zone)。
使用var声明的变量时存在着变量的提升
console.log(color);//undefined
var color = "red";
为什么是undefined呢?实际上上述代码相当于:
var color;
console.log(color);//undefined
color = "red";
JS中函数以及变量的声明都将被提升到函数的最顶部,JS中变量可以在使用后声明,也就是变量可以先使用再声明。
console.log(color);//Uncaught ReferenceError: Cannot access 'color' before initialization
let color = "red";
变量赋值
解构赋值
表达式左右两边结构必须一模一样
表达式右侧必须是值
声明和赋值不能分开
数组解构赋值
//ES5解构赋值
var arr = [1,2,3];
var x = arr[0];
var y = arr[1];
var z = arr[2];
console.log(x, y, z);//1 2 3
//ES6数组解构赋值
let [x, y, z] = [1, 3, 5];
console.log(x, y, z);//1 3 5
JSON解构赋值
let {id, name} = {id:1, name:"alpha"};
console.log(id, name);//1 "alpha"
字符串
ES6新增方法startsWith和endsWith
ES6新增字符串模板
String.startsWith
startsWith表示参数字符串是否在原字符串的头部,返回布尔值。
string.startsWith(substring[, searchPos]):boolean
例如:验证URL地址的合法性
let url = "http://www.baidu.com";
let bool = url.startsWith("http://");
console.log(bool);//true
startsWith第二个参数为起始位置
例如:验证身份证号码
const identify = "51030019800730366x";
//判断是否为四川省
console.log(identify.startsWith("51"));//true
//判断是否为1980年出生的,startsWith第二个参数为从第几个位置开始
console.log(identify.startsWith("1980", 6));//true
startsWith对大小写敏感
const str = "jack love mary";
console.log(str.startsWith("jack"));//true
console.log(str.startsWith("Jack"));//false
String.endsWith
endsWith表示参数字符串是否在原字符串的尾部,返回布尔值。
let url = "yourname@hotmail.com";
let bool = url.endsWith("@hotmail.com");
console.log(bool);//true
String.repeat 重复
repeat(count)方法会返回一个新字符串表示将原字符串重复count次后的结果
repeat(count)方法的参数count如果是小数则会被向下取整
repeat(count)方法的参数count如果是负数或Infinity则会报错
repeat(count)方法的参数count如果是NaN则等于0
repeat(count)方法的参数count如果是字符串则会先转换为数字
console.log(`${'='.repeat(10)}`);//==========
console.log(`${'='.repeat(10.9)}`);//==========
console.log(`${'='.repeat(0)}`);//
console.log(`${'='.repeat(NaN)}`);//
console.log(`${'='.repeat("10")}`);//==========
console.log(`${'='.repeat(-10)}`);//Uncaught RangeError: Invalid count value
console.log(`${'='.repeat(Infinity)}`);//Uncaught RangeError: Invalid count value
例如:实现字符串右对齐
//右对齐
const pad = (string, length=0)=> `${' '.repeat(Math.max(length - string.length, 0))}${string}`;
console.log(pad("hello", 20));// hello
console.log(pad("world", 20));// world
模板字符串
传统字符串拼接无法正常换行
传统字符串拼接无法友好的插入变量
传统字符串拼接无法友好的处理单引号和双引号相互嵌套的问题
ES6模板字符串使用一对反引号来定义一个模板字符串
let obj = {id:1, title:"test"};
console.log(`id = ${id} title = ${title}`);
模板字符串的应用
const User = {
id:1,
name:"junchow",
date:"2019-08-28",
todos:[
{todo:"go to store", status:false},
{todo:"watch movie", status:true},
{todo:"running", status:true}
]
};
//模板字符串可以进行嵌套
const template = `<ul>${User.todos.map(item=>`<li>${item.todo}</li>`).join("")}</ul>`;
console.log(template);
document.querySelector("#app").innerHTML = template;
const User = {
id:1,
name:"junchow",
date:"2019-08-28",
todos:[
{todo:"go to store", status:false},
{todo:"watch movie", status:true},
{todo:"running", status:true}
]
};
//模板字符串可以进行嵌套
const template = `
<ul>
${User.todos.map(item=>`
<li>
${item.todo}
${item.status===true?"done":"undo"}
</li>
`).join("")}
</ul>`;
console.log(template);
document.querySelector("#app").innerHTML = template;
标签模板字符串
function tag(template, ...args){
const html = args.map(arg=>`<strong>${arg}</strong>`);
let ret = "";
template.forEach((item, index) => ret +=`${item} ${html[index]}`);
return ret;
}
const id = 1;
const name = "junchow";
const sentence = tag`${id} ${name}`;
console.log(sentence);
数组
数组解构Array Destructuring
ES6允许按照一定模式,从数组中提取值,同时定义变量并对其赋值,这也就是解构(Destructureing)。
const result = [1, "success"];
//const [code, message, data] = result;
//console.log(code, message, data);//1 "success" undefined
//const [code, ,data] = result;
//console.log(code, data);//1 undefined
//const [code, ...args] = result;
//console.log(code, args);//1 ["success"]
//const [code=0, message="", data={}] = result;
//console.log(code, message, data);//1 "success" {}
例如:交换变量值
let x = 10, y = 20;
[x, y] = [y, x];
console.log(x, y);//20 10
Array.protype.includes() 包含
ES5数组提供Array.indexOf()方法用来查找某个元素在数组中的索引位置,若不存在则返回-1。
indexOf方法判断数组中是否包含元素是存在两点不足之处
返回-1和元素的索引位置表示是否包含不够语义化
不能判断是否含有NaN元素
const arr = ["alice", "card", 1, 2, NaN];
console.log("%s", arr.indexOf("alice"));//0
console.log("%s", arr.indexOf(0));//-1
console.log("%s", arr.indexOf(NaN));//-1
ES6提供Array.includes()方法返回布尔值可用于判断数组中是否包含指定元素,对NaN同样有效,其不足之处就是无法进行定位。
includes方法第1个参数表示查询的目标字符串
includes方法第2个参数表示判断的起始索引位置
includes方法第2个参数可以是负数,表示从右倒数第几个,但不会改变搜索方向,搜索方向仍然会从左至右。
const arr = ["alice", "card", 1, 2, NaN, "alice"];
console.log("%s", arr.indexOf("alice"));//0
console.log("%s", arr.indexOf(0));//-1
console.log("%s", arr.indexOf(NaN));//-1
console.log("%s", arr.includes("junchow"));//false
console.log("%s", arr.includes("alice"));//true
console.log("%s", arr.includes("alice", 4));//true
console.log("%s", arr.includes("alice", -5));//true
ES6中新增了四个函数,分别是映射map、汇总reduce、过滤filter、循环forEach
Array.map 映射
// 将数组中所有元素增加2倍
let arr = [11, 23, 43, 90];
let result = arr.map((x)=>x*2);
console.log(result);//[22, 46, 86, 180]
let arr = [89, 23, 31, 78];
let result = arr.map((x)=>{
if(x>=90){
return "A"
}else if(x>=80){
return "B";
}else if(x>=60){
return "C";
}else{
return "D";
}
});
console.log(result);//["B", "D", "D", "C"]
Array.prototype.reduce() 汇总
reduce方法接受一个函数作为累加器并指定初始值(可选),数组中的每个值(从左至右)开始缩减,最终计算为一个值。
array.reduce(callback, [initialValue])
reduce方法的累加器函数callback接受四个参数
function callback(preValue, curValue, index, array)
preValue初始值或上一次回调函数的返回值
curValue当前元素值
index当前索引
array调用reduce方法的数组
例如:数组求和与平均数
let arr = [100, 200, 300];
//求和 tmp为中间结果 item为每次迭代的对象 index为索引
let total = arr.reduce((tmp, item)=>{
//100 200 1
//300 300 2
//console.log(tmp, item, index);
return tmp + item;
});
//console.log(total);//600
例如:求平均值
let arr = [100, 200, 300];
//求平均数
let average = arr.reduce((tmp, item, index, arr)=>{
if(arr.length === index+1){
return (tmp+item)/index;
}else{
return tmp + item;
}
});
console.log(average);//300
例如:求阶乘
let arr = [100, 200, 300];
//求阶乘
let result = arr.reduce((preval, curval)=>{
return preval * curval;
});
console.log(result);//6000000
例如:二维数组转化为一维数组
let arr = [
[1, 3, 5],
[2, 4, 6],
[10, 20, 30]
];
let result = arr.reduce((retval, current, index, arr)=>{
return retval.concat(current);
});
console.log(result);//[1, 3, 5, 2, 4, 6, 10, 20, 30]
例如:计算数组中元素出现的次数
let arr = ["alice", "bob", "carl", "bob", "carl", "bob"];
let result = arr.reduce((retval, current, index, arr)=>{
if(current in retval){
retval[current]++;
} else{
retval[current] = 1;
}
return retval;
},{});
console.log(result);//{alice: 1, bob: 3, carl: 2}
例如:计算数组中某个元素出现的次数
let arr = ["alice", "bob", "carl", "bob", "carl", "bob"];
let times = (arr, val)=>arr.reduce((retval, current)=>{
return current===val ? retval+1 : retval+0;
},0);
console.log(times(arr, "bob"));//3
例如:获取数组对象中某个指定字段的值
let arr = [
{id:11, name:"alpha", status:1},
{id:20, name:"alice", status:0},
{id:34, name:"bob", status:1}
];
//获取数组中所有的id
let getField = (arr, field) => arr.reduce((preval, curval)=>{
preval.push(curval[field]);
return preval;
}, []);
console.log(getField(arr, "id"));//[11, 20, 34]
例如:数组去重
let arr = ["alice", "bob", "carl", "bob", "carl", "bob"];
let dedupe = (arr)=>arr.sort().reduce((retval, current)=>{
if(retval.length===0 || retval[retval.length-1]!==current){
retval.push(current);
}
return retval;
},[]);
console.log(dedupe(arr));// ["alice", "bob", "carl"]
例如:求某用户的单科成绩根据权重比例汇总后的权重值
//单科成绩
let arr = [
{subject:"math", score:89},
{subject:"chinese", score:89},
{subject:"english", score:89},
];
//权重比例
let percent = {math:0.5, chinese:0.3, english:0.2};
//求权重值
let result = arr.reduce((preval, curval)=>{
return preval + curval.score *percent[curval.subject];
}, 0);
console.log(result);//89
网友评论