《你不知道的JavaScript -- 上卷》笔记 --- 基于ES6新标准
1、let
A:let关键字:将变量绑定到所在的任意作用域
1 function process(){ 2 //do something 3 } 4 5 //在这个块中定义的内容完事就可以销毁 6 { 7 let someReallyBigData = {…………}; 8 9 process(someReallyBigData); 10 } 11 12 vra btn = document.getElementById("my_button"); 13 btn.addEventListener('click',function click(evt){ 14 console.info("click"); 15 })
B:let循环
1 for(let i = 0 ; i < 10 ; i++){ 2 console.info(i); 3 }
注意:let不仅将i绑定到for循环中,事实上将其绑定到了循环中的每一个迭代中,确保上一个循环迭代结束的时候对它的值重新进行赋值
C:可以解决闭包中的作用域问题
1 for(var i = 1 ; i <= 5 ; i++){ 2 (function(j){ 3 setTimeout(function timer(){ 4 console.info(j) 5 },j * 1000); 6 })(i); 7 } 8 9 作用相同 10 11 for(var i = 1 ; i <= 5 ; i++){ 12 let j = i ; 13 setTimeout(function timer(){ 14 console.info(j) 15 },j * 1000); 16 }
2、const
创建块作用域变量,但其值是固定的(常量),之后任何试图修改值得操做都会引起错误
3、编译器---函数声明与函数表达式
函数声明:
1 foo(); //success 2 function foo(){ 3 console.info(a); //undefined 4 var a = 2; 5 } 6 解释为 7 function foo(){ 8 var a; 9 console.info(a); //undefined 10 a = 2; 11 } 12 foo();
函数表达式:
1 foo(); //TypeError 2 bar(); //ReferenceError 3 var foo = function bar(){ 4 //.... 5 }
4、模块机制---ES6
1 bar.js 2 function hello(who){ 3 return "Let me introduce:" + who; 4 } 5 export hello; 6 7 8 foo.js 9 //仅从“bar”模块中导入hello() 10 import hello from "bar"; 11 12 var hungry = "hippo"; 13 function awesome(){ 14 console.info(hello(hungry).toUpperCase()); 15 } 16 export awesome; 17 18 19 baz.js 20 //导入完整的“foo”与“bar”模块 21 module foo from "foo"; 22 module bar from "bar"; 23 console.info(bar.hello("rhino")); //Let me introduce:rhino 24 foo.awesome(); //LET ME INTRODUCE:HIPPO
5、ES6箭头函数 -- 当做function关键字的简写以及解决关于this绑定问题
箭头函数就是function关键字的简写,用于函数声明,放弃了所有普通的this绑定原则,取而代之的是用当前的语法作用域覆盖了this本来的值,具体参考以下例子:
1 var obj = { 2 id : "awesome", 3 cool : function coolFn(){ 4 console.info(this.id); 5 } 6 } 7 8 var id = "not awesome"; 9 10 obj.cool(); //awesome 11 //cool函数丢失了同this之间的绑定 12 setTimeout(obj.cool,100); //not awesome 13 14 15 //可以通过var self = this;解决 16 var obj = { 17 count : 0, 18 cool : function coolFn(){ 19 var self = this; 20 21 if (self.count < 1) { 22 setTimeout(function timer(){ 23 self.count++; 24 console.info("awesome?"); 25 },100) 26 }; 27 } 28 } 29 obj.cool(); //awesome? 30 31 32 //这样一来代码过于冗长,通过ES6的箭头函数 33 var obj = { 34 count : 0, 35 cool : function coolFn(){ 36 if (this.count < 1) { 37 setTimeout( () => { 38 this.count++; 39 console.info("awesome?"); 40 },100) 41 }; 42 } 43 } 44 obj.cool(); //awesome? 45 46 47 //还可以通过bind()绑定 48 var obj = { 49 count : 0, 50 cool : function coolFn(){ 51 if (this.count < 1) { 52 setTimeout(function timer(){ 53 this.count++; 54 console.info("awesome?"); 55 }.bind(this),100) 56 }; 57 } 58 } 59 obj.cool(); //awesome?
6、关于指向函数自身
A:如果要是从函数对象内部引用它自身,那只使用this是不够的,一般来说,你需要通过一个指向函数对象的词法标识符(变量)来引用,例如以下例子:
1 function foo(){ 2 foo.count = 4 ; //foo指向自身 3 } 4 5 setTimeout(function(){ 6 //匿名(没有名字)的函数无法指向自身 7 },10);
第一个函数称为具名函数,在他内部可以使用foo来引用自身
第二个函数中没哟名称标识符(匿名函数),因此无法从函数内部引用自身
B:arguments.callee已经弃用,不应该再使用
C:使用foo标识符替代this引用函数对象
1 function foo(num){ 2 foo.count++; 3 } 4 foo.count = 0; 5 var i; 6 7 for(i = 0 ; i < 10 ; i++){ 8 if (i > 5) { 9 foo(i) 10 }; 11 } 12 console.info(foo.count); //4
该方法回避了this问题,并且完全依赖于变量foo的词法作用域
D:强制this指向函数对象
1 function foo(num){ 2 this.count++; 3 } 4 foo.count = 0; 5 var i; 6 7 for(i = 0 ; i < 10 ; i++){ 8 if (i > 5) { 9 foo.call(foo,i); 10 }; 11 } 12 console.info(foo.count); //4
7、this绑定
A:在严格模式下,this的默认绑定是undefined,在非严格模式下,this的默认绑定是全局对象
1 function foo(){ 2 "use strict"; 3 console.info(this.a); 4 } 5 var a = 2; 6 foo(); //TypeError : this is undefined 7 8 9 function foo(){ 10 console.info(this.a); 11 } 12 var a = 2 ; 13 (function(){ 14 "use strict"; 15 foo(); //2 16 })();
注意:foo的运行环境不是在严格模式下,严格模式下调用foo不影响默认绑定
B:隐式绑定:需要考虑调用位置是否含有上下文对象,或者说被某个对象所拥有或包含
1 function foo(){ 2 console.info(this.a); 3 } 4 var obj = { 5 a : 2, 6 foo : foo 7 } 8 obj.foo(); //2
调用foo的时候,this被绑定到obj,所以上下文中的this都是一样的
注意:对象属性引用链中只有上一层或者说最后一层在调用位置上面起作用,举例来说:
1 function foo(){ 2 console.info(this.a); 3 } 4 var obj2 = { 5 a : 42, 6 foo : foo 7 } 8 var obj1 = { 9 a : 2, 10 obj2 : obj2 11 } 12 obj1.obj2.foo(); //42
C:隐式丢失
1 function foo(){ 2 console.info(this.a) 3 } 4 var obj = { 5 a : 2, 6 foo : foo 7 } 8 var bar = obj.foo; 9 var a = "oops"; 10 bar(); //"oops"
bar其实是对foo函数本身的引用,因此此时bar()其实是一个不带任何修饰的函数调用,函数作为参数传递也是一样的
D:显示绑定
1 function foo(){ 2 console.info(this.a); 3 } 4 var obj = { 5 a : 2 6 } 7 var bar = function(){ 8 foo.call(obj) 9 } 10 bar(); //2 11 setTimeout(bar,100); //2 12 //硬绑定的bar不可能再修改它的this 13 bar.call(window); //2
另一种方式是创建一个包裹函数,负责接收参数并返回值
1 function foo(something){ 2 console.info(this.a,something); 3 return this.a + something; 4 } 5 var obj = { 6 a : 2 7 } 8 var bar = function(){ 9 return foo.apply(obj,arguments); 10 } 11 var b = bar(3) //2 3 12 console.info(b) //5
另一种方法是创建一个可以重复使用的辅助函数
1 function foo(something){ 2 console.info(this.a,something); 3 return this.a + something; 4 } 5 function bind(fn,obj){ 6 return function(){ 7 return fn.apply(obj,arguments); 8 } 9 } 10 var obj = { 11 a : 2 12 } 13 var bar = bind(foo,obj); 14 var b = bar(3); //2 3 15 console.info(b) //5
另一种方法是bind
1 function foo(something){ 2 console.info(this.a,something); 3 return this.a + something; 4 } 5 var obj = { 6 a : 2 7 } 8 var bar = foo.bind(obj); 9 var b = bar(3); // 2 3 10 console.info(b) //5
另一种方法是api调用的上下文
1 function foo(el){ 2 console.info(el,this.id); 3 } 4 var obj = { 5 id : "awesome" 6 } 7 [1,2,3].forEach(foo,obj); 8 //1 awesome 2 awesome 3 awesome
E:new绑定
1 function foo (a) { 2 this.a = a; 3 } 4 var bar = new foo(2); 5 console.info(bar.a); //2
F:显示绑定的优先级高于隐式绑定;new绑定的优先级高于显示绑定
8、对象 -- 可计算属性名(ES6)
1 var prefix = "foo"; 2 var myObject = { 3 [prefix + "bar"] : "hello", 4 [prefix + "baz"] : "world" 5 } 6 myObject["foobar"]; //hello 7 myObject["foobaz"]; //world
9、对象 -- 浅复制(ES6)
function anotherFunction(){} var anotherObject = { c : true } var anotherArray = []; var myObject = { a : 2, b : anotherObject, c : anotherArray, d : anotherFunction } //执行浅复制 var newObj = Object.assign({},myObject); newObj.a; //2 newObj.b === anotherObject ; //true
注意:Object.assign()使用的是=操作符,所有源对象属性的一些特性(比如writable)不会被复制到目标对象
深复制与浅复制:浅复制是复制出的新对象还是引用,深复制还需要复制引用的函数
10、数组遍历 --- ES6
1 var myArray = [1,2,3]; 2 for(var v of myArray){ 3 console.info(v); 4 } 5 //1 6 //2 7 //3
直接遍历数组的值而不是数组下标
原理:每次都会调用next()方法,该方法可以进行数组、对象……的遍历,不仅仅只是遍历数组而已
11、对象属性设置与屏蔽 --- ES6
var anotherObject = { a : 2 } var myObject = Object.create("anotherObject"); anotherObject.a; //= 2 myObject.a; //= 2 anotherObject.hasOwnProperty("a"); //true myObject.hasOwnProperty("a"); //false myObject.a++; //隐式屏蔽 anotherObject.a; //2 myObject.a; //3 myObject.hasOwnProperty("a"); //true
myObject.a++ 看起来应该是查找并增加anotherObject.a属性,但是++操作相当于myObject.a = myObject.a + 1.所以++操作首先会通过[[Prototype]]查找属性a并从anotherObject.a中
获取当前属性值2,然后给这个值+1,接着用[[put]]将值3赋给myObject中新建的屏蔽属性a
12、创建一个合适的关联对象
要创建一个合适的关联对象,我们必须使用Object.create(),而不是使用具有副作用的Foo(),这样做的唯一缺点就是需要创建一个新对象,然后把旧对象抛弃掉,不能直接修改已有的默认对象。
在ES-6之前,修改对象的[[prototype]]关联,我们只能通过设置._proto_属性实现,但是该方法并不是标准,并且无法兼容所有浏览器。
ES-6添加了辅助函数Object.setPrototypeOf(..)
1 //ES-6之前 2 Bar.prototype = Object.create(Foo.prototype); 3 4 //ES-6之后 5 Object.setPrototypeOf(Bar.prototype,Foo.prototype);