@octopus
2020-08-06T13:50:55.000000Z
字数 15119
阅读 1326
es6
进度:2.18-2.26_52studyit.com
var a = 1b = 2window.c = 3let d = 4const e = 4
变量 a 是全局变量,全局变量不可被删除,所以 delete a 时返回 false。可以通过 window 属性去访问
b 是 window 对象的属性,因为 windows对象全局可访问,所以 b也像全局变量一样可以被访问,但其实是属性,可被删除。
c 也是 window 对象的属性
d 是块级作用域变量,不可作为属性访问,不存在变量提升,不能被重新定义
e 是常量,具有 let的所有特点,且不能被修改,初始化时必须赋值。
// 例一function test(){var a = 1if(a===1){var b = 2}console.log(a+b) // 3}
因为变量提升,上述代码实际被编译成:
function test(){var a = 1var bif(a===1){b = 2}console.log(a+b) // 3}
所以最后能取到b,打印出 a+b 的值。
// 例二function test(){var a = 1if(a===1){let b = 2}console.log(a+b) // b is not defined}
let 声名的变量是块级作用域,仅在 {}中生效,不存在变量提升,所以外层获取不到 b
// 例三const bb = 1 // Missing initializer in const declaration
常量在初始化时必须赋值
数组遍历的n种方式
//1var arr = [1,2,3,4]for(let i = 0; i<arr.length ; i++){if(arr[i]===2){continue}console.log(arr[i])}
//2var arr = [1,2,3,4]arr.forEach(function (item) { // 此方法不支持 continue 与 break,只能完全遍历console.log(item)})
//3var arr = [1,2,3,4]arr.every(function (item) {if(item !== 2){console.log(item)}return true // 只有 return true 才能继续遍历})
//4var arr = [1,2,3,4]for(let index in arr){if(arr[index]===2){continue}console.log(arr[index]);}
//5var arr = [1,2,3,4]for(let item of arr){console.log(item)}
坑1:for in 不仅能打印数组值,还能打印属性。
for in 不是为数组准备的遍历功能,是为遍历对象而生,因为数组是对象,所以也可以用 for in 遍历。
数组是对象,所以也可以为它加属性:
var arr = [1,2,3,4]arr.a = 8 // 加上自定义属性 afor(let index in arr){console.log(index, arr[index]);}输出:0 11 22 33 4a 8
坑2:for(let index in arr) 中的 index 是字符串类型
var arr = [1,2,3,4]for(let index in arr){if(index === 1){continue}console.log(arr[index]);}输出:1,2,3,4var arr = [1,2,3,4]for(let index in arr){if(index*1 === 1){ // *1强制转化为数字后即可continue}console.log(arr[index]);}
for of
es6及其以后可以自定义可遍历结构,也就是不止数组是可遍历对象,这时就不能用 for、forEach、every、for in等。
for of 就是为包括自定义的可遍历对象而生的
{0:"a",1:"b",length:2}{length:5} // 可以理解为5个索引元素的值都是空
es5: [].slice.call(arrayLike)es6: Array.from(arrayLike, mapFunc, thisArg)
集合/nodeList 都是伪数组,满足上述两种特性,看起来像数组但用不了数组的api
// 获取页面中所有的 img,返回一个 modeListdocument.querySelectorAll("img")NodeList [img]length: 10: imgalt: ""src: "http://localhost:8080/es2019.jpg"...// 转换为数组es5:let imgs = [].slice.call(document.querySelectorAll("img"))es6:let imgs = Array.from(document.querySelectorAll("img"))
es5的老办法:
let arr = Array(n);for(let i = 0; i<arr.length; i++){arr[i] = 1}
es6:
let arr = Array.from({length:n}, function () {return 1})let arr = Array(n).fill(1)
es5:
var arr = Array()var arr = []
es6:
let arr = Array.of(1,2); // [1,2]let arr = Array(5).fill(1)
Array(7); // [ , , , , , , ]Array.of(7); // [7]Array.of(1, 2, 3); // [1, 2, 3]Array(1, 2, 3); // [1, 2, 3]
可以填充数组,也可以批量改变数组的值。
// 填充数组let arr = Array(5).fill(1)// 批量改变数组值let arr = [1,2,3,4,5,6]let arr2 = arr.fill(8,1,3) // 将第1-3(包含头不包含尾)的元素值改成8console.log(arr2) // [1, 8, 8, 4, 5, 6]
es5:
// 将 return true 的所有元素以数组格式返回,缺点是,如果只想知道是否存在一个元素,filter找到后依然会遍历所有元素,这并不高效Array.filter(function(){})
es6:
// 将 return true 的第一个值返回,关注的是有或没有,找到了就返回值,没找到就返回undefinedArray.find(function(){})// 返回第一个 return true 的元素位置Array.findIndex(function(){})
举个例子:
// filterlet arr = [1,2,3,4,5,4]let find = arr.filter(function (item) {return item === 4})console.log(find) // [4, 4]// findlet arr = [1,2,3,4,5,4]let find = arr.find(function (item) {return item === 4})console.log(find) // 4// findIndexlet arr = [1,2,3,4,5,4]let find = arr.findIndex(function (item) {return item === 4})console.log(find) // 3
es5:
let Animal = function (type) {this.type = typethis.eat = function () {console.log(this.type+"正在吃...")}}let dog = new Animal("dog");let cat = new Animal("cat");
1. 隐式地创建一个空对象 obj = {}
2. Animal.call(obj,type), 将 this 指向的作用域变成 obj,并初始化,则 obj 有了 Animal 初始化后的的所有属性和方法。(值拷贝)
3. obj.__proto__= Animal.prototype,将原型链指向函数,存的是引用地址。(prototype属性,可以返回对象的 原型对象 的引用。)
4. 返回obj对象
这就导致,每生成一个类,都完全复制了一份父类,使占用的空间变的很大,且 eat 本是想作为一个父类的公共方法,new 以后却变成了每个子类的"私有"方法。
对象就像是一个封装好的盲盒,盲盒里有图纸,和一张说明。图纸谁拿到了都可以修改,但是说明只有盲盒本盒才有权改(盲盒可读可写,其他人只读)。
A = function(){ // 这是盲盒 Athis.type = "flower" // 图纸,可以生产花}A.prototype // 这是盲盒 A 里的说明A.prototype.warning = "不可以生产蓝花" // 在说明里新增一条注意事项
说明上都记录着什么呢?
console.log(A.prototype)// 结果:warning: "不可以生产蓝花"constructor: ƒ ()length: 0name: "A"arguments: nullcaller: nullprototype: {constructor: ƒ}__proto__: ƒ ()[[FunctionLocation]]: VM64:1[[Scopes]]: Scopes[2]__proto__:constructor: ƒ Object()__defineGetter__: ƒ __defineGetter__()__defineSetter__: ƒ __defineSetter__()hasOwnProperty: ƒ hasOwnProperty()__lookupGetter__: ƒ __lookupGetter__()__lookupSetter__: ƒ __lookupSetter__()isPrototypeOf: ƒ isPrototypeOf()propertyIsEnumerable: ƒ propertyIsEnumerable()toString: ƒ toString()valueOf: ƒ valueOf()toLocaleString: ƒ toLocaleString()get __proto__: ƒ __proto__()set __proto__: ƒ __proto__()
记录着涂鸦(warning),盲盒本身(指针指向外层)(constructor) ,盲盒来源的说明的地址(__proto__)
有了盲盒,就有了可以使用盲盒生产东西的工厂,开工厂就一个条件,要有使用的盲盒说明的地址。
现在有一个工厂 a 想用图纸 A 生产花,生产红色的花,于是 a 打开了盲盒,复印并修改了盲盒图纸,同时按照要求挂上了使用的盲盒说明的地址。
let a = new A()a.type = "red flower"consloe.log(a);// 打印结果:type: "red flower" // 工厂生产的东西__proto__: // 盲盒的说明warning: "不允许狗进入"constructor: ƒ () // 盲盒本身length: 0name: "A"arguments: nullcaller: nullprototype: {warning: "不允许狗进入", constructor: ƒ} // 盲盒的说明__proto__: ƒ ()[[FunctionLocation]]: test.js:1[[Scopes]]: Scopes[2]__proto__: // 盲盒的来源的说明...
所以假如 a 不禁想修改图纸,还想修改说明上的涂鸦,让所有工厂都改变这个涂鸦,就要联系盲盒本盒:
a.constructor.prototype.warning = "允许狗进入"
因为当获取一个属性,而本身找不到时,就会自动去来源处寻找,所以 a 本身并没有constructor方法,程序会自动去 __proto__ 中找到,父级依然没有的话,会一直向上找。
了解了原型链后,该如何解决公共方法的问题?—— 将共有属性和方法,放在原型链中,实例化时不会拷贝,调用时会顺着 __proto__ 指向的地址去找
let Animal = function (type) {this.type = type}Animal.prototype.eat = function () {console.log(this.type+"正在吃...")}let dog = Animal("dog");let cat = Animal("cat");
es6:
class A {constructor (type) {this.type = type}eat(){console.log(this.type+"正在吃...")}}let dog = new A("dog");let cat = new A("cat");console.log(dog);A {type: "dog"}type: "dog"__proto__:eat: ƒ eat()constructor: class A__proto__: Object
打印 dog 以后会发现,结果和上面讲的原型链一模一样,所以其实 es6 的 class 就是 es5 原型链写法的语法糖,自动帮我们实现了挂载prototype的操作而已。
在es5中设置私有属性几乎是不可能实现的,外部很容易就对私有属性进行了修改:
let A = function (type) {this.type = type}let a = new A("dog")console.log(a.type) // doga.type = "cat"console.log(a.type) // cat
es6中设置私有属性:
class A {get number(){return 1}}let a = new A()console.log(a.number) // 1a.number = 2console.log(a.number) // 1
get 后的函数名,是类的私有变量,返回值不会在外部被改变。同理,还有 set 可以在内部改变私有属性
let _number = 1class A {get number(){return _number}set number(val){if(val<10){_number = val}}}let a = new A()console.log(a.number) // 1a.number = 2console.log(a.number) // 2a.number = 11console.log(a.number) // 1console.log(a._number) // undefined
注意,在 set 中,不可以写 this.number = xxx 这样就形成了死循环,调用了 set ,set中再调用set...
所以只能用一个别名变量来存储私有变量的值,并且别名变量要在 class 外部,这样就不属于类的属性,实例不会获取到。
静态方法是类方法,实例不可以调用,要用类名调用。
es5:
let A = function () {}A.eat = function () {console.log("eating...")}let a = new A();a.eat(); // x 报错:a.eat is not a functionA.eat(); // 正确
es6:
class A {constructor (type) {this.type = type // constructor声明的是私有属性和方法}eat(){ // 原型链上的父类方法console.log(this.type+"正在吃...")}static talk(){ // static 的是静态方法console.log("正在说话...")}}A.talk()
es5:
let A = function (type) {this.type = typethis.walk = function () {console.log("walking")}}A.prototype.eat = function () {console.log("eating")}let B = function () {A.call(this, "B") // 先执行父类的构造函数,把父类的东西挪到自己身上}B.prototype = A.prototype // 和父类公用一个原型链let b = new B();console.log(b) // 成功继承
es6:
// es6 的继承很简单,需要关键字 extendsclass A {constructor (type) {this.type = type}eat(){console.log(this.type+"正在吃...")}static talk(){console.log("正在说话...")}}class B extends A{ // 继承 A}let b = new B("b");b.eat() // 即可调用父类方法B.talk() // 父类静态方法
当子类需要实现自己的构造函数时,需要显示调用父类构造方法。
class B extends A{constructor (type, voice) {super(type); // 显示调用父类构造方法this.voice = voice}}let b = new B("b","wangwang");b.eat()B.talk()
es5:
function test (a, b, c) {if (b === undefined){b = 1}if (c === undefined){c = 2}return a + b + c}console.log(test(0)) // 3
es6:
function test (a, b=1, c=2) {return a + b + c}console.log(test(0)) // 3
问题:如果只想传 a , c ,还是让 b 保持默认值怎么办呢?
用 undefined 解决,回头看 es5 的方法,就会理解,当传入undefined,函数会认为是缺省,则会触发缺省值
function test (a, b=1, c=2) {return a + b + c}console.log(test(0, undefined, 10)) // 11
es5: 在函数内部通过 arguments 获取传进来的参数 ( 但是 es6 已经被禁止使用 )
function test (a=0, b=1, c=2) {console.log(arguments)return a + b + c}test(10,10)结果:// 是一个 Arguments 对象Arguments(2) [1, undefined, callee: (...), Symbol(Symbol.iterator): ƒ]callee: (...)0: 101: 10length: 2Symbol(Symbol.iterator): ƒ values()get callee: ƒ ()set callee: ƒ ()__proto__: Object
可见 Arguments 对象有长度,有索引,所以——
function test (a=0, b=1, c=2) {let arg = Array.from(arguments) // 将伪数组转换为数组console.log(arg) // [10, 10]return a + b + c}test(10,10)
所以通过 arguments 可以轻松获取到函数 传入参数 的个数和值 (不是定义的参数的个数和值)
es6:
函数名.length 可以获取到函数定义参数,且没有默认值的参数个数
function test (a=0, b=1, c=2) {console.log(test.length) // 0 因为三个全是有默认值的return a + b + c}test(10,10)
...nums, ... 表示将所有参数写到 nums 数组中
function sum (...args) {let res = 0args.forEach(function (item) {res += item})return res}console.log(sum(10,10)) // 20
同理, ... 能把 n 个参数放进一个变量数组里,也能把变量数组,拆分到n个参数里
function sum (x, y, z) {return x + y + z}let nums = [1,2,3]let res = sum(...nums) // 6
注意: ...atgs 只能在函数的参数括号中使用,用来聚合和解构数组,其他地方使用会报错
let a,b,c = ...[1,2,3] // xxxxxx 这是错的!!!
()=>{}
es5声明一个函数:
let a = function () {console.log("hello world")}a()
es6 箭头函数:
let a = () => {console.log("hello world")}a()
当没有参数,和有两个及其以上参数的时候,必须带(),当只有一个参数时,可以不加()
let a = () => {console.log("hello world")}let a = (x,y) => {console.log(x,y)}let a = x => {console.log(x)}
箭头函数的 => 后,可以是一个函数体,可也以是一句话表达式
// 一句话表达式,不用加函数体 {},也不用写 return,会自动计算返回let a = (x, y) => x + y// 等同于:let a = (x, y) => {return x + y}// 利用一句话表达式直接返回一个数组let a = (x,y) => [x,y]
如果想返回一个对象呢?
// 这是错的let a = (x, y) => { // 程序会把 {} 认为是函数体的开始和结束符号"x" : x, // 这是啥?程序会报错"y" : y}// 这是对的let a = (x,y) => ( // () 可以认为是一个表达式的孵化器,像 (x+y)*2 一样,只是给必要的表达式提供一个环境{"x" : x,"y" : y})// 最笨的办法let a = (x, y) => {return {"x" : x,"y" : y}}
es5 的普通函数:
let test = {name : "test",func : function () {console.log(this.name)}}test.func() // test
输出了 test ,因为 func 内的 this 指向的是调用者的作用域,就是 test 对象
es6 的箭头函数:
let test = {name : "test",func : () => {console.log("name:" + this.name)}}test.func() // undefined
由于简单对象(非函数)是没有执行上下文的,所以上例中只能到最外层 windows 中找name属性,结果是 undefined。
var x = 11;var obb = {x: 222,y: {x:333,obc: function f() {console.log(this)var x = 111;var obj = {x: 22,say: () => {console.log(this.x);}}obj.say()}}}obb.y.obc() // 333
此处一直向上找有上下文的this,终于在函数中找到了,而函数中的 this 指向的是调用它的对象。
传统函数:this指向调用它的对象。 链接
箭头函数:this指向被定义时的上下文中的this。
es5:
let a = 1;let b = 2;let obj = {a : a,b : b}console.log(obj) // {a: 1, b: 2}
es6:
let a = 1;let b = 2;let obj = {a,b}console.log(obj) // {a: 1, b: 2}
当变量名是 key 时,可以简写。
当key是动态生成的,或是一个变量的值,es6支持将变量/表达式作为 key进行解析
es5:
let c = "123"let obj = {a : "a",b : "2"}obj[c] = "3"console.log(obj) // {123: "3", a: "a", b: "2"}
es6:
let c = "123"let obj = {a : "a",b : "2",[c] : "3"}console.log(obj) // {123: "3", a: "a", b: "2"}// 也可以是表达式let a = 1;let b = 2;let obj = {[a+b] : "3"}console.log(obj) // {3: "3"}
es5:
let obj = {hello : function(){console.log("hello");}}obj.hello()
es6:
let obj = {hello(){console.log("hello");}}obj.hello()
set 是 es6 新增的一种数据结构,存储的成员是唯一的,不可重复。set有增删查的成员方法,没有修改方法,如果想修改建议先删除,再添加。
// 初始化let s = new Set();// 初始化并赋值,值不仅可以是数组,凡是可遍历对象都可以let s = new Set([1,2,3,4]);
向set中添加数据
let s = new Set();s.add("hello");// 可以链式操作,因为元素唯一性,最终只有一个wwws.add("www").add("aaa").add("www");console.log(s); // Set(3) {"hello", "www", "aaa"}
删除元素,返回 true/false
s.delete("hello"); // Set(2) {"www", "aaa"}
全部清空(删除)
s.clear(); // Set(0) {}
元素是否存在
let s = new Set([1,2,3,4]);let res = s.has(2); // truelet res = s.has(10); // false
元素个数
let s = new Set([1,2,3,4]);let size = s.size // 4
let s = new Set(["a","b","c","d"]);let keys = s.keys () // SetIterator {"a", "b", "c", "d"}let values = s.values() // SetIterator {"a", "b", "c", "d"}let entries = s.entries() // SetIterator {"a" => "a", "b" => "b", "c" => "c", "d" => "d"}
可以看到,本质上也是一个对象,拥有键值
keys 返回了 set 的所有key的可遍历对象(SetIterator)
values 返回了 set 的所有值的可遍历对象
entries 返回了 set 的键值对的可遍历对象
es6新增的一种数据结构,用来存储键值对。任何类型都可以作为键,比如函数。
初始化时传值为一个可遍历对象,对象的各个值要有 键和值
let m = new Map();let m = new Map([ ["a",1], ["b",2] ]); // {"a" => 1, "b" => 2}
添加/修改键值对
let m = new Map();m.set("a", 1);m.set("b", 2); // Map(2) {"a" => 1, "b" => 2}// set 不仅可以添加,还可以用来修改m.set("a", "aaa"); // Map(2) {"a" => "aaa", "b" => 2}
删除键值对,传入key,返回 true/false
m.delete("a")console.log(m) // Map(1) {"b" => 2}
全部清空(删除)
m.clear(); // Map(0) {}
元素个数
let m = new Map([ ["a",1], ["b",2] ]);let size = m.size // 2
查询 key 是否存在
let m = new Map([ ["a",1], ["b",2] ]);m.has("c") // falsem.has("a") // true
let m = new Map([ ["a",1], ["b",2] ]);m.get("c") // undefinedm.has("a") // 1
let m = new Map([ ["a",1], ["b",2] ]);let keys = m.keys () // MapIterator {"a", "b"}let values = m.values() // MapIterator {1, 2}let entries = m.entries() // MapIterator {"a" => 1, "b" => 2}
keys 返回了 Map 的所有key的可遍历对象(SetIterator)
values 返回了 Map 的所有值的可遍历对象
entries 返回了 Map 的键值对的可遍历对象
for( let [key, v] of m ){console.log(key, v);}// a 1 b 2
const target = {}const source = {"a":1,"b":2}Object.assign(target, source)console.log(target) // {"a":1,"b":2}
es5
const a = 1;const b = 2;const res = 'res is '+(a+b)+' .'; // "res is 3 ."
es6
const a = 1;const b = 2;const c = 'hello world'const res = `${c} res is ${a+b}` // "hello world res is 3"
功能实现:当a=1时,输出“单价为1”,当a=2时,输出“单价为2”
es5
function echoPrice(a){if(a===1){return "单价为1"}else if(a===2){return "单价为2"}}console.log( echoPrice() );
es6
function Price(strings, a){let temp = strings[0]return temp+a;}let res = Price`单价为${1}` // "单价为1"let res = Price`单价为${2}` // "单价为2"
函数名后接模板,会将由变量分割的字符串数组作为第一个参数传入函数,之后的参数依次是变量。
let arr = ["hello", "world", "1"];let [a, b, c] = arr;console.log(a, b, c) // "hello", "world", "1"let [a, , c] = arr; // // "hello", "1"
解构不仅可以对数组,凡是可遍历对象都可以
let [a,b] = 'hello'console.log(a, b) // h,elet [a,b] = new Set([1,2,3]);console.log(a, b) // 1,2
解构赋值还可以修改对象值
let obj = {"a":1, "b":2}; // 这里结束要加分号,与解构语句隔开[obj.a, obj.b] = [3,4]console.log(obj) // {a: 3, b: 4}
将解构用到遍历对象上
let obj = {"a":1, "b":2};for( let [key,v] of Object.entries(obj) ){console.log(key,v);}
收集其他变量
let arr = ["hello", "world", "1",2,3,4];let [a, b, ...others] = arrconsole.log(others) // ["1", 2, 3, 4]
获取对象值
解构中的变量名默认为对象key,若要新起一个变量名,要写全 key:new_name
let option = {"a" : "aa","b" : "bb","c" : "cc"};let {a:a_value, b, c} = option;console.log(a_value, b, c) // aa bb cc
获取复杂对象值
解构时要和被解构变量保持一致的结构
let res = {"code" : 200,"data" : [{"name" : "zhang","age" : "20"}]}let {code, data:[{name, age}]} = resconsole.log(code, name, age) // 200 "zhang" "20"
使用 export 可以导出任何结构,如变量,方法
// 1.jsexport const name = "hello"export let addr = "beijing"export let arr = [1,2,3]// 2.jsimport { name,addr,arr } from './1.js'console.log(name,addr,arr) // hello, beijing, [1,2,3]
多个待导出变量/方法一起导出
let a = "1";let b = "2";export{a,b}
使用 default 来默认导出,导入时不需要放到花括号中。每个模块只可以有一个默认导出。由于只有一个默认导出,所以导入时可以随便给默认变量起名字。
// 1.jsconst name = "hello"export default name;// 2.jsimport name from './1.js'import name2 from './1.js' // 也可以更换变量名-----// 1.jsconst name = "hello";let a = "1";let b = "2";export default name;export {a,b}// 2.jsimport name,{a,b} from './1.js'
如果非默认导出变量也像改名,那么可以用 as
// 1.jslet a = "1";let b = "2";export {a,b}// 2.jsimport {a as a2, b as b2} from './1.js'
// 1.jsexport function say(){console.log("hi");}export function run(){console.log("run");}export default function eat(){console.log("eat");}// 2.jsimport eat,{say,run} from './1.js'say();run();eat();
同理也可以把函数放在一起导出
const eat = () =>{console.log("eat");}const run = () =>{console.log("run");}export{eat,run}
// 这样做是错的,导出的最外层也是一个对象,要求值是 key-value形式,而里面是个对象,就会报错export {{xxx:xxxxxx:xxx}}// 解决办法1: 使用default。因为default只能导出一个,所以后面跟对象没有歧义,就是导出一个对象export default {a : 1,b : 2}import obj from './1.js' // 随便起一个变量名来引入。
如果要导出多个对象呢?
// 将两个对象套进一个对象中默认导出var obj1 = {a:1}var obj2 = {b:2}export default{obj1,obj2}// 利用解构导入import obj from './1.js'let {obj1, obj2} = obj
class A{constructor(){this.type = 1}}export {A}// 引入import {A} from './1.js'let a = new A();
导出默认类只需要 default 后直接写类名即可‘
class A{constructor(){this.type = 1}}export default A;
import * as Mod from './1.js'let a = Mod.alet res = Mod.test();let default = Mod.default // 默认导出的变量需要用 .default 获取,不能直接用名字