ECMAScript:简称ES,是由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)按照ECMA-262和ISO/IEC 16262标准制定的一种脚本语言规范;
JavaScript:运行在浏览器端的语言,该语言是使用ES标准。 ES+webapi=JavaScript;
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现;
NodeJs:运行在服务器端的语言,该语言使用ES标准。 ES + node api = NodeJs;
ES3.0: 1999——成为 JavaScript 的通行标准,得到了广泛支持;
ES5.0: 2009——该版本成为了 ISO 国际标准(ISO/IEC 16262:2011);
ES6.0: 2015——并且更名为“ECMAScript 2015”,TC39委员会决定每年发布一个ECMAScript 的版本;
ES7.0: 2016
等等…
1.1 允许重复的变量声明:导致数据被覆盖
1.2 变量提升:怪异的数据访问、闭包问题
1.3 全局变量挂载到全局对象:全局对象成员污染问题
var div = document.getElementById("divBtn");
for (var i = 1; i <= 10; i++) {
var btn = document.createElement("button");
btn.innerHTML = "按钮" + i;
div.appendChild(btn);
btn.onclick = function () {
console.log(i); //每次点击都会输出11
}
}
ES6不仅引入let关键字用于解决变量声明的问题,同时引入了块级作用域的概念;
块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域;
1.1 let声明的变量不会挂载到全局对象;
1.2 let声明的变量,不允许当前作用域范围内重复声明,在块级作用域中用let定义的变量,在作用域外不能访问;
1.3 使用let不会有变量提升,因此,不能在定义let变量之前使用它;
底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access ‘a’ before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)
在循环中使用let声明的循环变量,在循环结束后会销毁;
let div = document.getElementById("divBtn");
for (let i = 1; i <= 10; i++) {
let button = document.createElement("button");
button.innerHTML = "按钮" + i;
div.appendChild(button)
button.onclick = function () {
console.log(i) //使用的是当前作用域中的i
}
}
const和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。
实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:
注意的细节:
- 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
- 常量的命名
2.1. 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割
2.2. 普通的常量:使用和之前一样的命名即可- 在for循环中,循环变量不可以使用常量
早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字。我们将一个16位的二进制编码叫做一个码元(Code Unit)。
后来,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)。
ES6为了解决这个困扰,为字符串提供了方法:codePointAt,根据字符串码元的位置得到其码点。
同时,ES6为正则表达式添加了一个flag: u,如果添加了该配置,则匹配时,使用码点匹配。
const text = ""; //占用了两个码元(32位)
console.log("字符串长度:", text.length);
console.log("使用正则测试:", /^.$/u.test(text));
console.log("得到第一个码元:", text.charCodeAt(0));
console.log("得到第二个码元:", text.charCodeAt(1));
//:\ud842\udfb7
console.log("得到第一个码点:", text.codePointAt(0));
console.log("得到第二个码点:", text.codePointAt(1));
/**
* 判断字符串char,是32位,还是16位
* @param {*} char
*/
function is32bit(char, i) {
//如果码点大于了16位二进制的最大值,则其是32位的
return char.codePointAt(i) > 0xffff;
}
/**
* 得到一个字符串码点的真实长度
* @param {*} str
*/
function getLengthOfCodePoint(str) {
var len = 0;
for (let i = 0; i < str.length; i++) {
//i在索引码元
if (is32bit(str, i)) {
//当前字符串,在i这个位置,占用了两个码元
i++;
}
len++;
}
return len;
}
console.log("是否是32位的:", is32bit("", 0))
console.log("abab的码点长度:", getLengthOfCodePoint("abab"))
includes
判断字符串中是否包含指定的子字符串
startsWith
判断字符串中是否以指定的字符串开始
endsWith
判断字符串中是否以指定的字符串结尾
repeat
将字符串重复指定的次数,然后返回一个新字符串。
const text = "他可是个狠人";
console.log("是否包含“狠”:", text.includes("狠"));
console.log("是否以“他”开头:", text.startsWith("他"));
console.log("是否以“狠人”结尾:", text.endsWith("狠人"));
console.log("重复4次:", text.repeat(4));
标记名:y
含义:匹配时,完全按照正则对象中的lastIndex位置开始匹配,并且匹配的位置必须在lastIndex位置。
const text = "Hello World!!!";
const reg = /W\w+/y;
reg.lastIndex = 3; //可以动态修改lastIndex的值
console.log("reg.lastIndex:", reg.lastIndex)
console.log(reg.test(text))
在ES6中,提供了模板字符串的书写,可以非常方便的换行和拼接,要做的,仅仅是将字符串的开始或结尾改为 ` 符号;
如果要在字符串中拼接js表达式,只需要在模板字符串中使用${JS表达式}
;
表达式可以是任何有意义的数据${1 + 3 * 2 / 0.5}
;
表达式也是可以嵌套的:${`表达式中的模板字符串${love1 + love2}`}
;
var love1 = "秋葵";
var love2 = "香菜";
var text = `老邓头喜欢${love1},也喜欢${love2}
\n\n
在模板字符串中也能使用\${JS表达式}可以进行插值`;
console.log(text);
var text = String.raw`abc\t\nbcd`; // String.raw 获取一个模板字符串的原始字符串的
console.log(text); // "abc\t\nbcd"
在模板字符串书写之前,可以加上标记:
标记名`模板字符串`
标记是一个函数,函数参数如下:
Html结构:
<p>
<textarea id="txt">textarea>
<button id="btn">设置div的内容button>
p>
<div id="container">div>
JS代码:
const container = document.getElementById("container");
const txt = document.getElementById("txt");
const btn = document.getElementById("btn");
btn.onclick = function(){
container.innerHTML = safe`
${txt.value}
${txt.value}
`;
}
function safe(parts){
const values = Array.prototype.slice.apply(arguments).slice(1);
let str = "";
for (let i = 0; i < values.length; i++) {
const v = values[i].replace(/, "<").replace(/>/g, ">");
str += parts[i] + v;
if (i === values.length - 1) {
str += parts[i + 1];
}
}
return str;
}
在书写形参时,直接给形参赋值,赋的值即为默认值
这样一来,当调用函数时,如果没有给对应的参数赋值(给它的值是undefined),则会自动使用默认值。
function sum(a, b = 1, c = 2) {
return a + b + c;
}
console.log(sum(10, undefined, undefined)) //13
console.log(sum(11)) //14
console.log(sum(1, undefined, 5)) //7
只要给函数加上参数默认值,该函数会自动变成严格模式下的规则:arguments和形参脱离
形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区。
function test(a, b = 1) {
console.log("arugments", arguments[0], arguments[1]); //arugments 1 2
console.log("a:", a, "b:", b); //a: 1 b: 2
a = 3;
console.log("arugments", arguments[0], arguments[1]); //arugments 1 2
console.log("a:", a, "b:", b); //a: 3 b: 2
}
test(1, 2);
arguments的缺陷:
ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。
语法:
function (...形参名){
}
细节:
function sum(...args) {
//args收集了所有的参数,形成的一个数组
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
console.log(sum()) //0
console.log(sum(1)) //1
console.log(sum(1, 2)) //3
console.log(sum(1, 2, 3)) //6
console.log(sum(1, 2, 3, 4)) //10
使用方式:...要展开的东西
MDN文档参考
/**
* 对所有数字求和
* @param {...any} args
*/
function sum(...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
/**
* 获取一个指定长度的随机数组成的数组
* @param {*} length
*/
function getRandomNumbers(length) {
const arr = [];
for (let i = 0; i < length; i++) {
arr.push(Math.random());
}
return arr;
}
const numbers = getRandomNumbers(10);
//将数组的每一项展开,依次作为参数传递,而不是把整个数组作为一个参数传递
// sum(numbers)
console.log(sum(...numbers))//相当于传递了10个参数
console.log(sum(1, 3, ...numbers, 3, 5))
const arr1 = [3, 67, 8, 5];
//数组拷贝(copy)
const arr2 = [...arr1]; //[3, 67, 8, 5]
const arr2 = [0, ...arr1, 1]; //[0, 3, 67, 8, 5, 1]
const obj1 = {
name: "成哥",
age: 18,
love: "邓嫂",
address: {
country: "中国",
province: "黑龙江",
city: "哈尔滨"
}
}
// 浅克隆到obj2
const obj2 = {
...obj1,
name: "邓哥"
};
console.log(obj2)
console.log(obj1.address === obj2.address) //true 浅克隆,不会深度拷贝
const obj1 = {
name: "成哥",
age: 18,
loves: ["邓嫂", "成嫂1", "成嫂2"],
address: {
country: "中国",
province: "黑龙江",
city: "哈尔滨"
}
}
// 深克隆到obj2 此情况是在知道obj1数据前提下进行拷贝
const obj2 = {
...obj1,
name: "邓哥",
address: {
...obj1.address //对address进行深度克隆
},
loves: [...obj1.loves, "成嫂3"] //对loves进行深度克隆
};
console.log(obj2)
console.log(obj1.loves === obj2.loves) //false
4-4.1
function test(a, b, c) {
console.log(a, b, c);
}
test(2, 6, 7); //2 6 7
const arr = ["asf", "Gfh", "111"];
test(...arr); // 把arr数组拆开作为参数传入; asf Gfh 111
4-4.2
<div>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
<p><input type="number" name="" id="" value="0">p>
div>
<p>
<button>计算button>
p>
<p>
<h2>最大值:<span id="spanmax">span>h2>
<h2>最小值:<span id="spanmin">span>h2>
p>
function getValues() { //获取input框value值,并放入数组之中;
const numbers = [];
const inps = document.querySelectorAll("input")
for (let i = 0; i < inps.length; i++) {
numbers.push(+inps[i].value)
}
return numbers;
}
const btn = document.querySelector("button")
btn.onclick = function () { //点击按钮计算input框中value值的大小
const numbers = getValues(); //得到文本框中的所有数字形成的数组
spanmax.innerText = Math.max(...numbers)
spanmin.innerText = Math.min(...numbers)
}
4-4.3
// curry: 柯里化 用与固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接受剩余的参数;
function cal(a,b,c,d){
return a + b * c + d;
}
function curry(func,...args){ //柯里化函数
return function(...args1){
const newArr = [...args,...args1];
if(newArr.length >= func.length){
// 参数够了
return func(...newArr);
}else{
//参数不够,继续固定
return curry(func,...newArr);
}
}
}
const newCal = curry(cal,2,3);
console.log(newCal(4,5)); // 2+3*4+5 19
console.log(newCal(5,5)); // 2+3*5+5 22
console.log(newCal(6,5)); // 2+3*6+5 25
console.log(newCal(7,5)); // 2+3*7+5 28
const newCal1 = newCal(8);
console.log(newCal1(5)); // 2+3*8+5 31
ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用;
new.target
该表达式,得到的是:如果没有使用new来调用函数,则返回undefined ,如果使用new调用函数,则得到的是new关键字后面的函数本身。
function Person(firstName, lastName) {
//判断是否是使用new的方式来调用的函数
// //过去的判断方式
// if (!(this instanceof Person)) {
// throw new Error("该函数没有使用new来调用")
// }
if (new.target === undefined) { //调用者不用new关键字调用则会抛出错误
throw new Error("该函数没有使用new来调用")
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName} ${lastName}`;
}
const p1 = new Person("胖", "虎");
console.log(p1) //正常执行打印
const p2 = Person("胖", "虎");
console.log(p2); //会抛出错误
const p3 = Person.call(p1, "胖", "虎")
console.log(p3); //会抛出错误
回顾:this指向
箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数;
完整语法:
(参数1, 参数2, ...)=>{
//函数体
}
如果参数只有一个,可以省略小括号
参数 => {
}
如果箭头函数只有一条返回语句,可以省略大括号,和return关键字
参数 => 返回值
const obj = {
count: 0,
start: function () {
setInterval(() => {
this.count++;
console.log(this.count);
}, 1000)
},
regEvent: function () {
window.onclick = () => {
console.log(this.count);
}
},
print: function () {
console.log(this) //指向obj
console.log(this.count) // 输出count的值;
}
}
// obj.start(); //每隔一秒输出count的值;
// obj.regEvent(); //当点击的时候输出count的值;
obj.print();
//判断一个数是不是奇数
// const isOdd = function (num) {
// return num % 2 !== 0;
// }
// const isOdd = (num) => {
// return num % 2 !== 0;
// }
const isOdd = num => num % 2 !== 0;
console.log(isOdd(3))
console.log(isOdd(4))
const sum = (a, b) => ({
a: a,
b: b,
sum: a + b
});
console.log(sum(3, 5)); // {a: 3, b: 5, sum: 8}
const obj = {
method: function(){
const func = () => {
console.log(this) //obj
console.log(arguments) // 234
}
func()
}
}
obj.method(234);
const numbers = [3, 7, 78, 3, 5, 345];
const result = numbers.filter(num => num % 2 !== 0)
.map(num => num * 2)
.reduce((a, b) => a + b)
console.log(result); // 先按照条件过滤(保留奇数),然后映射(将过滤后的数组*2),最后求和, 726