你不知道的JavaScript(上)笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q821901733/article/details/85294760

1. 作用域

  • 1.1 概念

      作用域是一套规则,用于确定如何在当前作用域以及嵌套的子作用域中,根据标识符名称,进行变量查找。
    
  • 1.2 LHS和RHS(查找类型)

       LHS:左查找,对变量进行赋值
       RHS:右查找,对变量进行引用
    
    	function fn(a) {	//  a = 2(隐式变量分配)是LHS
    		var b = 3;	// LHS
    		return a + b;	// RHS
    	}
    	fn(2);	// fn() 是LHS
    

2. 词法作用域

  • 2.1 概念

      词法作用域是定义在词法阶段的作用域。
    
  • 2.2 EVAL和WITH(修改作用域,尽量不要使用,消耗性能)

     eval: 执行传入的参数,严格模式下无效
    
    	function fn(str) {
    		eval(str);		
    		console.log(a);	// 严格模式下报错 ReferenceError: a is not defined
    	}
    	fn('var a = 2');		// 2
    
     with: 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,
     		但在这个块内正常的var声明,并不会被限制在这个块的作用域中,而是会被添加到with所处的函数作用域中。
    
    	function fn(obj) {
    		with(obj) {
    			obj.a = 22
    		}
    	}
    	var obj1 = { a: 1 },
    		obj2 = { b: 3 };
    	fn(obj1);	//	obj1.a = 22
    	console.log(a);	// a is not defined
    	fn(obj2);	//	obj.a = undefined
    	console.log(a);	// a = 22	
    	(非严格模式下,作用域对a进行了LHS查找,但在obj2和全局作用域中都未找到,便自动创建了一个全局变量a)
    

3 函数作用域和块作用域

  • 3.1 隐藏内部实现(最小暴露原则)

      好处:私有化内部变量和函数;规避命名冲突。
      坏处:污染该函数或对象所在的作用域;必须显示调用。
      解决:匿名自执行函数。
    
  • 3.2 立即执行函数表达式(IIFE)

      使用:(function() {...})() / (function() {}())
      传参:(function() {})(window,...)
    
  • 3.3 块级作用域

      let:声明一个作用域被限制在块级中的变量、语句或者表达式。(没有变量声明提升)
      const: 声明创建一个常量的只读引用(变量标识符不能重新分配),作用域可以是全局或本地声明的块。
    

4 提升

  • 4.1 概念

      包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
      只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。
    
  • 4.2 变量声明提升

    	a = 2;								var a;
    	var a;						=>       a = 2;
    	console.log(a);	// 2					
    	// ------------------------------------------------------
    	console.log(b);	// undefined		var b;
    	var b = 2;					=> 		console.log(b);
                                             b = 2;
    
  • 4.3 函数声明提升(函数表达式不会提升)

    	fn();	// 1
    	function fn() {
    		console.log(1)
    	}
    	// ---------------------------------------------------------------
    	
    	fn2();	// TypeError: fn2 is not a function	(此时fn2 = undefined)
    	var fn2 = function() {
    		console.log(2)
    	}
    	// ---------------------------------------------------------------		
    	
    	fn3();	//	3.1							functoin fn3() {			// 函数声明提升 优先于 变量声明提升
    	var fn3;									console.log(3.1)
    	function fn3() {						}
    		console.log(3.1)		=>			fn3()		// 3.1
    	}										fn3 = function() {
    	fn3 = function() {							console.log(3.2)
    		console.log(3.2)					};
    	}
    	fn3();	// 3.2							fn3();	// 3.2( 函数表达式无提升,会覆盖前面声明的函数(注意函数调用位置!))
    	// ---------------------------------------------------------------		
    
    	fn4();	// 4.2		// 普通块内部的函数声明,通常会被提升到所在作用域顶部,不受条件判断控制(应尽量避免这种声明方法)
    	if (true) {
    		function fn4() { console.log(4.1) }
    	} else {
    		function fn4() { console.log(4.2) }
    	}
    

5 作用域闭包

  • 5.1 概念

      当函数可以记住并访问所在的词法作用域时,就产生了闭包。
      MDN: 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。
    
    	function outer() {
    		var a = 0;
    		function inner() {
    			a++;
    			console.log(a);
    		}
    		return inner;
    	}
    	var add = outer();
    	add();	// 1
    	add();	// 2
    	add();	// 3
    
  • 5.2 循环与闭包

      例:分别每隔一秒每次一个打印1-5。
    
    	for (var i = 1; i <= 5; i ++) {
    		setTimeout(function() {
    			console.log(i)
    		}, 1000 * i)
    	}
    	// 结果是每秒一次的频率输出五次6。(循环事件结束后,setTimeout才会执行;每次循环都引用了i,但i在所处作用域中只有一个,而此时i =  6。)
    	
    	// 使用闭包--------------------------------------------------------------------
    	for (var i = 1; i <= 5; i++) {
    		(function(j) {
    			setTimeout(function() {
    				console.log(j)
    			}, j * 1000)	
    		})(i)
    	}
    
    	// 新建一个变量------------------------------------------------------------------
    	for (var i = 1; i <= 5; i ++) {
    		var a = 1;
    		setTimeout(function() {
    			console.log(a++)		
    		}, 1000 * i)	
    	}
    	// 使用let(每轮循环都会根据上轮循环的值,重新声明i,)-----------------------------
    	for (let i = 1; i <= 5; i++) {
    	setTimeout(function() {
    		console.log(i)
    	}, 1000 * i)
    }
    
  • 5.3 模块

    • 5.3.1 概念
        必须有外部的封闭函数,该函数必须被至少调用一次(每次调用都会创建一个新的模块实例);
        封闭函数必须返回至少一个内部函数,这样函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。
      
      	function common() {
      		var str = 'string in common';
      		function doSomething() {
      			console.log(str)
      		}
      		return {	// 也可直接返回一个内部函数
      			doSomething: doSomething
      		};
      	}
      	var fn = common();
      	fn.doSomething();	// string in common
      
    • 5.3.2 将模块转换成单例模式
      	var common = (function() {
      		var str = 'string in common';
      		function doSomething() {
      			console.log(str)
      		}
      		return {	// 也可直接返回一个内部函数
      			doSomething: doSomething
      		};
      	})();
      	common.doSomething();	// string in common
      
    • 5.3.3 ES6的模块化
      	a.js
      	function hello(name) {
      		return 'hello' + name + '!'
      	}
      	export hello;
      	---------------------------------
      	b.js
      	function () {}
      

6 this

  • 6.1 概念

      this是在函数被调用时发生的绑定,指向什么完全取决于函数在哪里被调用。
    
  • 6.2 四种绑定规则

    • 6.2.1 默认绑定
        被直接使用,不带任何修饰符的函数引用(独立调用)。只能使用默认绑定,此时 this 默认指向全局对象。
        注意(严格模式下时,全局对象无法使用默认绑定)
      
      	function foo() {					function foo() {	
      		console.log(this.a);	=>			"use strict"
      	}										console.log(this.a)
      	var a = 1;							}
      	foo();	// 1						var a = 2;
      											foo(); // TypeError: Cannot read property 'a' of undefined
      	//-----------------------------------------------------------------
      	疑问:这里的 foo()是运行在严格模式下,怎么还能打印出来this.a?
      	书中原话:虽然 this 的绑定规则完全取决于调用位置,
      			   但是只有 foo() 运行在非 strict mode 下时,默认绑定才能绑定到全局对象;
      			   严格模式下与 foo()的调用位置无关。
      	(PS:还是没懂。。。)
      	function foo() {
      		console.log( this.a );
      	}
      	var a = 2;
      	(function(){
      		 "use strict";
      		 foo(); // 2
      	})();
      
    • 6.2.2 隐式绑定
        函数的调用位置是否有上下文对象。
      
      	function foo() {
      		console.log( this.a );
      	}
      	var obj2 = {
      		 a: 42,
      		 foo: foo
      	};
      	var obj1 = {
      	 	a: 2,
      		obj2: obj2
      	};
      	obj1.obj2.foo(); // 42	(PS:对象属性链中只有最后一层会影响函数的调用位置)
      
        隐式丢失:被隐式绑定的函数会丢失绑定对象,从而应用到默认绑定规则。
      
      	var obj = {
          a: "obj inner",
          foo: function() {
                  console.log(this.a);
              }
          };
          var bar = obj.foo;	// (PS:这里实际上引用的foo函数本身,所以调用bar()时,实际是独立调用)
          var a = "global"; 
          bar(); // "global"
          setTimeout(obj.fn, 100);	// "global"	(原因同上)
          // -----------------------------------------------------------------------------
      
      
    • 6.2.3 显式绑定
        直接指定this的绑定对象。(apply/call/bind)
      
      	function foo(a,b,c) {
              console.log(this.a, a, b, c);
          }
          var obj = { a: 123 }
          foo.call(obj, 1,2,3)        // 123 1 1 2 3
          foo.call(obj, [1,2,3])      // 123 [1,2,3] undefined undefined
          foo.apply(obj, [1,2,3]);    // 123 1 2 3
          foo.bind(obj, 1, 2, 3)();   // 123 1 2 3
          (PS:apply和call的唯一区别是传参,apply需要传一个数组,call需要枚举所有参数;
          	  bind会返回一个硬绑定的新函数)
      
        API调用的上下文(函数提供了一个可选参数,作为执行的上下文,等同于bind(...))。
        例:array.forEach(callback(currentValue[, index[, array]]), [, thisArg])
      
      	function foo(el) {
              console.log( el, this.id );
          }
          var obj = {
              id: "awesome"
          };
          var arr = [1, 2, 3];
          arr.forEach(foo, obj);  // 1 "awesome" 2 "awesome" 3 "awesome"
          arr.forEach(foo.bind(obj));   // 1 "awesome" 2 "awesome" 3 "awesome"
      
    • 6.2.4 new绑定

猜你喜欢

转载自blog.csdn.net/q821901733/article/details/85294760