JavaScript学习——基础

〇、前言

  1. 专业素养问题(要有意识的提高)
  2. 前端工程师主要与浏览器打交道。下面介绍一些主流浏览器(所谓主流,表示它在市场上占据很大地位,并且都有自己独立研发的内核):
    IE——trident、FireFox——Gecko、Opera——presto、Safari——webkit、Chrome——webkit/blink(2014年后Chrome主要使用blink内核)
  3. 浏览器的两大部分
  • Shell(外壳):主界面等部分
  • 内核:主要分为两大模块——渲染引擎(负责html、css部分)与JS引擎(负责js部分),另外,还有很多其他小模块。

一、基础

1.1 基础概况

  1. 特点:解释型语言 单线程
  • 高级语言的几种分类方式:编译型语言与解释型语言、弱数据类型与强数据类型、面向过程与面向对象(JS既面向过程又面向对象)、脚本语言与非脚本语言。
  • 下面介绍编译型语言与解释型语言:
    高级语言翻译成机器语言要么通过编译的方式,要么通过解释的方式。也因此产生编译型语言与解释型语言的分类。
    编译型语言:通篇翻译(将所有代码都浏览后,进行一次翻译)后产生一个最终的文件,这个文件将用来执行程序。如C++、C语言等。优点&缺点:快;不跨平台。
    解释型语言:“浏览一行(分号判定语句的结束)——翻译一行——执行一行”。如JS、php、python等语言。优点&缺点:慢;跨平台。
  • 注意:java语言既不是编译型语言也不是解释型语言。.java文件通过javac指令编译成.class文件,然后通过jvm虚拟机进行解释执行。实现了跨平台性。
  1. 三大组成部分:ECMAScript(ES)、DOM(通过JS可以操作HTML)、BOM(通过JS可以操作浏览器)
  2. JS代码运行三部曲:语法分析→预编译→解释执行

对预编译进行讲解:

  • 真正了解预编译前,需知道以下规则:
  1. 对于函数来说:声明&定义整体提升;对于变量来说:声明提升,赋值不提升。
    (“提升”指的是“提升上来,最先执行”)
  2. 任何一个变量,如果变量未经声明就赋值,此变量就为全局对象所有。(Imply Global 暗示全局变量)
    eg: a = 123;
    //a未声明,直接赋值
    eg: var a = b = 123;
    //连续赋值,从左至右。顺序:123赋给b(这一过程,b是未经声明的变量,归window域上);然后声明a;最后将b的值赋给a。
  3. 全局上的任何变量,都是window域/GO(后面讲解)上的属性。
    eg: var a = 123; ==> window.a = 123;
    JavaScript学习——基础_第1张图片
  • 预编译过程讲解(以下面的函数为例;函数的预编译发生在函数执行的前一刻):
<script type="text/javascript">
	function fn(a) {
      
		console.log(a);
		var a = 123;
		console.log(a);
		var a = 123;
		coonsole.log(a);
		function a() {
      }
		console.log(a);
		var b = function () {
      }
		console.log(b);
		function d() {
      }
	}
	fn(1);
</script>
  1. 创建AO(Activation Object ;中文被称作“执行期上下文”;其作用相当于一个“作用域”)对象。
AO { }
  1. 找函数里的形参和变量声明,将变量和形参名都作为AO属性名,值为undefined。
AO {
	a : undefined, //即使有多个名为a的东西,也只写一个
	b : undefined
}
  1. 将实参值和形参统一。
AO {
	a : 1,
	b : undefined
}
  1. 在函数体里面找函数声明,将函数名也作为属性名写入AO中,对应的值是这个函数的函数体。
AO {
	a : function a() {},//函数名与属性名同名的情况:a已经存在了,即使后面又有了函数名为a的,也不必再添加,但对应的属性值需要得到修改,即原来的值被新的覆盖掉
	b : undefined,
	d : function d() {}
}
  • 接下来我们看一下上面的函数的整个执行过程,既预编译完成后,如何执行呢?
function fn(a) {
       
	console.log(a); //从AO中查找出a对应的值——function a() {}
	var a = 123;    //由于在编译时遵循“函数&定义整体提升,而变量仅声明提升”,因此var a已执行,但a = 123还未执行。所以此时将AO中a对应的值修改为123
	console.log(a); //打印出123
	function a() {
      } //由于在编译时遵循“函数&定义整体提升,而变量仅声明提升”,所以该语句已执行,此时无需再执行
	console.log(a); //打印出123
	var b = function () {
      } //var b已执行,但b =  function () {}还未执行,所以此时b在AO中的值被赋为 function () {}
	console.log(b); //打印出 function () {}
	function d() {
      } //已执行,不再执行
}
  • 因此,最终控制台的打印结果为:
 function a() {}
 123
 123
 function () {}
  • 然而,预编译不仅仅发生在函数的执行上,也是发生在整个代码执行上的。请看下面的代码:
<script type="text/javascript">
	console.log(a);  //打印结果为:function a() {}
	var a = 123;
	function a() {
      }
	console.log(a);  //打印结果为:123
</script>
  • 对于全局来说,没有参数,上面的预编译环节少了第三环节(也没有第二环节里的找形参);此外,还有一个不同点是:全局的预编译第一环节生成的是名为GO的(Global Object)对象。而GO其实就是window域(同一个东西的不同叫法)!
  • 分析下面的代码:
*****************************1***********************
<script type="text/javascript">
	console.log(a);		//打印结果为:function a() { ... } [GO作用域上] (莫忘了全局上的函数是在GO上的;函数里的函数在AO上)
	function a(a) {
      
		console.log(a);	//打印结果为:function a() { } [AO作用域上]
		var a = 234;
		console.log(a);	//打印结果为:234 [AO作用域上] 
		function a() {
       }
	}
	a(1);
	var a = 123;
</script>
*****************************2***********************
<script type="text/javascript">
	global = 100;
	function fn() {
      
		console.log(gloabl);	//打印结果为:undefined [AO作用域上] (AO作用域上有的话,就不再去看GO作用域上该属性对应的值;否则,若某个属性在AO作用域上找不到,就会从GO作用域上进行查找)
		global = 200;
		console.log(gloabl);	//打印结果为:200 [AO作用域上] 
		var gloabl = 300;
		console.log(gloabl);	//打印结果为:300 [AO作用域上] 
	}
	fn();
	var gloabl;
</script>
*****************************3***********************
<script type="text/javascript">
	function fn() {
      
		console.log(b);		//打印结果为:undefined [AO作用域上]
		if(a) {
      				
			var b = 100;	//if里的内容在预编译环节需要参与
		}
		console.log(b);		//打印结果为:undefined [AO作用域上](if语句没有执行,因为判断条件a此时为undefined,无法执行if语句,所以b仍为undefined)
		c = 234;			//未声明的变量为全局变量所有
		console.log(c);		//打印结果为:234 [GO作用域上] 
	}
	var a;					//声明总能被提升到最先执行
	fn();
	a = 10;
	console.log(c);			//打印结果为:234 [GO作用域上] 
</script>
**********************4(百度面试题)********************
<script type="text/javascript">
	function bar() {
      
		return foo;
		foo = 10;
		function foo() {
      }
		var foo = 11;
	}
	console.log(bar());			//结果:function foo() {} (由函数编译的第四环节决定)
</script>
**********************5(百度面试题)********************
<script type="text/javascript">
	console.log(bar());			//结果:11
	function bar() {
      
		foo = 10;
		function foo() {
      }
		var foo = 11;
		return foo;
	}
</script>

1.2 基本运算

①、&&
  • 一旦遇到真值为假的部分,就立刻中断此语句,返回值就是这个假的值。
  • 实例:
    a = 1 && 0 && 1; 		//返回值为0。
    a = 1 && false && 1;	// 返回值为false。
  • 用处:一般将其用在“短路语句”中。所谓短路语句:只有当&&前面的真值为真时,才会执行后面的部分。如:2 > 1 && document.write(“2 > 1”); 由于2 > 1成立,故此时将输出2 > 1。这就类似于if语句。实际开发中常常这样使用:
var data = ...;     	 	//data是从后端传输过来的有效数字
data &&  (一个用到data的语句) //这样,执行的语句得到了保障,因为只有data真正有意义时,才会执行后面的语句。
②、||
  • 碰到“真”就停,并返回这个真值。
  • 实例:
var a = 0 || 1;
document.write(a);//输出1
var a = 2 || 1;
document.write(a);//输出2
③、Boolean

下面的几种数据如果以布尔类型输出,都将为false:undefined、null、“”(空串)、false、0、NaN(Not a Number)。利用下面的方法可以对其验证:

var a = !"";
document.write(a); //输出为true。"!"可以使其变成布尔值,如果这里没有"!",则无输出结果,因为是个空串。
var a = !!"";
document.write(a); //输出为false
var a = !!0;
document.write(a); //输出为false
var a = !!NaN;
document.write(a); //输出为false

JavaScript学习——基础_第2张图片

④、typeof()
  • 只有一种情况使用未定义的变量而不发生错误,这个语句是:
console.log(typeof(a)); //控制台将输出undefined
  • 其他时候,如console.log(a);,控制台将报错:a没有定义。
  • 问:console.log(typeof(typeof(a))); 输出什么?
    答:输出string! 当typeof(a)是null、string、boolean、number、object、function时,typeof(typeof(a))也都是string。

1.3 显式转换 隐式转换

1.3.1 显式转换

  • Number() 转换成数字类型
  • null、false 被转换为0;undefined 被转换为NaN
  • “abc123”这种出现字母的字符串不能被转换(结果将显示NaN),但“123”这种可以被转换为对应的数字(见下图)
    JavaScript学习——基础_第3张图片
  • parseInt() / parseInt(a, b) 转换成整型数字
  • 123.9、“123.9”、123.3都将被转换成123;
  • "123abc"可以被转换成123(砍断原则:遇到非数字位时结束,但将前面所有的数字返回);
  • null、true的转换结果为NaN;
  • parseInt(10, 16);结果为16——把a看作是b进制的数字,然后转换成10进制显示出来。
  • parseFloat() 转换成浮点型数字
  • String() 转换成字符串
  • Boolean() 转换成布尔类型
  • toString() / toString(radix)
  • var dema = 10;
    var num = demo.toString();
    var num = demo.toString(8); //把10转换为了8进制
  • undefined 、 null不可以调用toString()

1.3.2 隐式转换

  • 隐式执行Number()转换:isNaN()、++/–、-*/% 、表示正负的+/-
  • 隐式转换为String类型:数字与字符串相加
  • 隐式转换为Number类型:数字与除了字符串之外的类型相加
  • 隐式转换为Boolean类型:&& || !、< > <= >= 、== !=

很多时候,我们不希望自动发生隐式转换。于是,JS中出现了“===”与“!= =”(中间没有空格)分别表示“绝对等于”与“绝对不等于”。
JavaScript学习——基础_第4张图片

1.4 函数

1.4.1 两种定义方式

  1. 函数声明
 function test() { 
 	函数体 
 }
  1. 函数表达式

①、命名函数表达式

 var test1 = function f() { 函数体 } //控制台中输入test1.name,输出为f

②、匿名函数表达式(常用)

var test2 = function () { 函数体 } //控制台中输入test2.name,输出为test2

1.4.2 说明

  • 传递参数时,个数任意!如,只有两个形参,但实际传递时,既可以比两个参数多,也可以比两个参数少。原因:定义的每个函数中都隐含有一个专门用来接收实参值的数组arguments!·这个数组依次对应形参,它们指向同一个位置,相互映射。
  • 另外,即使自己写的函数体中没有出现return语句,但实际函数体的最后一行隐式的写有return语句(return;),用来终止函数体。此外,它主要的用途是返回某个值(也同时有结束函数体的作用)。

二、进阶

2.1 作用域精讲

2.1.1 相关概念讲解

  • 执行期上下文AO
    函数执行前,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,当函数执行完毕,它所产生的执行上下文被销毁。函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文。
  • 函数的属性举例
    显式(可以用函数名.进行调用):name、prototype
    隐式(不可拿来用):[[scope]]
  • [[scope]]
    每个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,因为这些属性仅供JavaScript引擎存取。[[scope]]就是其中一个,它指的就是我们所说的作用域,其中存储了运行期上下文的集合——作用域链。
  • 作用域链
    [[scope]]中所存储的执行器上下文对象的集合,这个集合呈链式连接,故将其称为作用域链。
  • 查找变量
    每个函数都有一个自己的作用域链,在函数执行时用到了哪个变量,就从该函数的作用域链的顶端依次向下查找。

2.1.2 一个例子

  • 在程序中定义一个函数a(),以此为例进行讲解:
<script type="text/javascript">
	function a() {
     
		function b() {
     
			var b = 234;
		}
	var a = 123;
	b();
	}
	var global = 100;
	a();
</script>

Step1、只要函数在程序中有定义,就会产生一个该函数对应的GO{}作用域链:
JavaScript学习——基础_第5张图片
Step2、当执行a()函数时,该函数的作用域链首先被改变——增加其自身变量产生作用域对象AO{}:
JavaScript学习——基础_第6张图片
Step3、由于函数b()在函数a()内,所以执行a()时,就产生b()定义情况下的作用域链:
PS. “b直接拿到a的劳动成果;且是a的执行过程产生的劳动成果;且是a的作用链的引用,即,若在b中修改a函数中创建的变量,则a的作用域链上的这个变量也会改变”。
PS. 如果不执行a()函数,就不会产生b()定义时的作用域链;但对于a()来说,由于它就是最外层的函数,所以只要执行这个程序,就一定会产生a()的作用域链。
JavaScript学习——基础_第7张图片
Step4、当执行b()函数时,该函数的作用域链首先被改变——增加其自身变量产生作用域对象AO{}:
JavaScript学习——基础_第8张图片
Step5、b()先执行完,销毁AO,即恢复到定义状态时的作用域链:
JavaScript学习——基础_第9张图片
Step6、a()执行完,销毁AO,即恢复到定义状态时的作用域链:
JavaScript学习——基础_第10张图片

  • 例子:
    JavaScript学习——基础_第11张图片
    PS. 印证了"里面的函数能看到外面的函数所定义的一切变量,而外层的函数看不到其内层函数中的变量"。

2.2 闭包

  1. 定义:当内部函数被保存到外部时,会生成闭包。
  • “被保存到外部”是通过“return这个内部函数”做到的;
  • 相当于函数里定义了一个函数,但是这个内层的函数并没有被执行,因为没有语句调用它(有关他的语句只有一个return);
  • 与《作用域链精讲》时用到的程序相对比,闭包里a()函数没有调用其内部函数b()而已,即,少了b();语句。
  • 被return的这个内部函数将可以作为全局变量使用。
  1. 影响:闭包会导致原有作用域链不释放,造成内存泄漏。
  2. 作用:
  • 不依赖于外部变量/实现公有变量(eg: 函数累加器)
  • 可以做为外部不可见的缓存/存储结构来使用
<script type="text/javascript">
	funtion eater() {
     
		var food = "";
		//obj是自定义的一个对象,对象里可以有属性也可以有方法,将作为存储结构使用			
		var obj = {
     
			name : null,
			eat : function() {
     
				console.log("I am eating " + food);
				food = "";
			},
			push : function(myFood) {
     
				food = myFood;
			}
		}
		return obj;			//返回obj对象,这个对象相当于我们设定的一个存储结构	
	}	
	var eater1 = eater();	//接收返回的obj对象
	eater1.push("apple");	//对象调用其中的方法
	eater1.eat();
</script>
  • 可以实现封装,属性私有化
<script type = "text/javascript">
	function Deng(name, wife) {
     
		var prepareWife = "bbb";

		this.name = name; 
		this.wife = wife;
		this.divorce = function() {
     
			this.wife = prepareWife;
		}
		this.changePrepareWife = function(target) {
     
			prepareWife = target;
		}
		this.sayPrepareWife = function() {
     
			console.log(prepareWife);
		}
	}

	var deng = new Deng('deng', 'aaa');
</script>

构造函数中的prepareWife 属性,用户是无法访问到的,只有构造函数内部才能使用
在这里插入图片描述
在这里插入图片描述
这是因为prepareWife形成了闭包,于是就成了这个构造函数的私有化变量
再看个例子:

<script type = "text/javascript">
	var init = (function () {
     
		var name = 'abc'; //私有化变量
		function callName() {
     
			console.log(name);
		}
		return function () {
     
			callName();
		}
	}())

	init();
</script>
  • 模块化开发,防止溢出/污染全局变量
  1. 具体讲解(以下面程序为例):
*************************基础版*************************
<script type="text/javascript">
	function a() {
     
		function b() {
     
			var bbb = 234;
			document.write(aaa);
		}
		var aaa = 123;
		return b;			//将b()函数作为返回值
	}
	var global = 100;
	var demo = a();			//由于a()的执行结果是返回b(),因此demo成了一个函数,而这个函数其实就是b()
	demo();					//执行上条语句形成的demo()函数,即b()
</script>
  • 执行过程详解:
    a()定义 → a()执行 → b定义;
    JavaScript学习——基础_第12张图片
    a()执行完(即,执行完return语句)后,就销毁自身函数作用域链上的执行期上下文AO(销毁指的是不再指向这个AO,如果不清楚这一过程,应回头学习《作用域精讲》这一部分知识点),回归到被定义状态时的作用域链,直到下次执行,重新生成AO;
    JavaScript学习——基础_第13张图片
    return使得b()被抛到全局变量demo处保存下来,b()因此脱离了a()的影响而单独存在。存到全局作用域的变量将永远不会被销毁,除非整个程序执行完毕。
    当遇到demo();语句时,就真正执行b(),b()此时将产生自身的AO。
  • 打印结果:123
    问题在于,当执行demo(),即b()时,我们要打印aaa,而aaa是在外层函数定义的,所以本应当出现错误,但事实上却没有出现错误。这就是因为执行demo()时,也即b()时,b()已经有了a()的执行期上下文作为自己作用域链的“地基”,所以在真正执行b()时,能够找到aaa变量,使得打印出123。
*************************升级版*************************
<script type="text/javascript">
	function test() {
     
		var arr = [];
		for (var i = 0; i < 10; i++) {
     	//arr中的每一项都被赋值为一个函数!
			arr[i] = function () {
     
				document.write(i + " ");
			}
		}
		return arr;						//返回装有10个函数的数组;由于返回了函数,所以形成了闭包
	}
	var myArr = test();
	for(var j = 0; j < 10; j++) {
     		//开始执行10次function () { document.write(i + " "); }语句
		myArr[j](); 
	}
</script>
  • 执行过程详解:
    执行test()时,不执行里面的function () { document.write(i + " "); }语句!只有在外部执行被返回的结果的函数时,才使得其真正执行。即执行myArrj;语句才会使得上面的语句执行。
    这样,执行test()时已经使作用域链上的i变成10。所以外部循环执行myArrj;语句时,打印结果都是10。
    由于arr[i]这些函数在执行test()时就已经形成了定义状态下的作用域链,所以都有i变量在自己的作用域链上,且每个函数的i都是同一个,因为本来就只有一个i,只是这10个函数的作用域链都执行它罢了,当然,实际上指向的是test()执行时形成的作用域链,他的作用域链是那10个函数作为“地基”使用的。
  • 打印结果:10 10 10 10 10 10 10 10 10 10
  • 事实上,我们希望的打印结果是0 1 2 3 4 5 6 7 8 9,但是结果不尽人意,原因上面已经解释过了。如何进行改进呢?利用下一知识点即可修正。

2.3 立即执行函数

  1. 定义:普通函数中定义出了一种特别性质的函数——执行完该函数后,函数被立即销毁,这种函数被称作立即执行函数
  • 这一性质可以体现在:执行完有立即执行函数的程序后,在控制台中输入这个函数名,系统将提示“undefined”。
  • 立即执行函数也会产生自己的作用域链。
  • 立即执行函数的优势:节省空间,用一次的东西就可以让他立即销毁了,不占用空间。
  1. 语法格式:(函数名 f 可写可不写,";"也可写可不写)
    ①、(function f(){}()); //(建议使用)
    ②、(function f(){}) ();
<script type="text/javascript">
****************************************************
	var num = (function(a, b, c) {
     	//定义形参。当然,也可以没有参数
		return (a + b + c);
	}(1, 2, 3);						//定义实参。如果上面定义了形参,但这里却没有给实参,就会报语法错误
	document.write(num));
****************************************************
	var num1 = (function(a, b, c) {
     	
		return (a + b + c);
	})(4, 2, 3);		
	document.write(num1);
</script>
  1. 辨析下面的一些相似的语句:
<script type="text/javascript">
***************立即执行函数构成的一个函数表达式:****************
	var test = function() {
     
		console.log("a");
	}();						
*****************普通函数构成的一个函数表达式:******************
	var test1 = function() {
     
		console.log("b");		//执行完之后,还可以找到test1
	};
	
//下面语句将引起语法解析错误;错误之处在于:不是表达式,是函数声明,所以"();"引起的语句不能被执行
	function test2() {
     
		document.write("aaa");
	}();
	
//对上面引起语法错误的语句进行修改:
//方式一、“+ - !”等运算符可以将其转换为表达式(隐式类型转换),从而可以正确被解析;相当于立即执行函数
	+ function test2() {
     				
			document.write("aaa");
		}();
//方式二、直接改写为语法格式为①的立即执行函数
	(function test2() {
     
		document.write("aaa");
	}());
//方式三、直接改写为语法格式为②的立即执行函数
	(function test2() {
     
		document.write("aaa");
	})();//改进:

//根据上面那个出现语法错误的原因,下面的函数本应也不正常,但事实上确是可以执行的!
	function test2(a, b) {
     
		document.write(a + b);
	}(1, 2);
//原因:因为上面的语句被系统理解为:
			function test2(a, b) {
     
				document.write(a + b);
			}		
			(1, 2);	//这是可以执行的表达式,但系统的这种解释并不会使test2真正执行

//PS. 需记住——只有表达式才能被函数的执行符号"()"执行
</script>
  1. 解决上一知识点中遗留的问题(希望得到打印结果"0 1 2 3 4 5 6 7 8 9",而非"10 10 10 10 10 10 10 10 10 10"):
<script type="text/javascript">
	function test() {
     
		var arr = [];
		for (var i = 0; i < 10; i++) {
     
			(function(x) {
     						//x是形参
				arr[x] = function() {
     
					document.write(x + " ");
				}
			}(i));								//i是实参
		}
		return arr;
	}
	var myArr = test();
	for(var j = 0; j < 10; j++) {
     
		myArr[j](); 
	}
</script>
  • 执行过程详解:
    ①、与打印出“10 10 10 10 10 10 10 10 10 10”的程序相比,这里仍然是只有在执行myArr[j] ();时,才会使得function() {document.write(x + " ");}语句执行。
    ②、为什么function() {document.write(x + " ");}没有执行呢,因为我们返回了arr[]10个函数,所以根据闭包的性质,被返回的这个函数不会真正执行,但实质是根本没有调用这个函数的语句,又怎么会执行呢?而返回语句就使得这个函数变成全局函数来使用,所以之后可以随时调用。
    ③、每次传入一个i,x就随之改变,所以i从0~ 9,就有x从0~9;这样,在外部执行myArr[j] ();即function() {document.write(x + " “);}时,就会找到对应的立即执行函数,每个立即函数有自己对应的x,所以可以得到打印结果"0 1 2 3 4 5 6 7 8 9”。立即执行函数是在执行了myArr[j] ();语句后才销毁的。
	(function(x) {
     
		arr[x] = function() {
     
		document.write(x + " ");
		}
	}(0));
	(function(x) {
     
		arr[x] = function() {
     
		document.write(x + " ");
		}
	}(1));
	(function(x) {
     
		arr[x] = function() {
     
		document.write(x + " ");
		}
	}(2));
	(function(x) {
     
		arr[x] = function() {
     
		document.write(x + " ");
		}
	}(3));
	......

④、不利用立即执行函数,直接把function() {document.write(i + " ");}中的i改成j,也可以实现想要的结果,但这种写法就是利用的作用域链上j的不断变化,导致这里的j不断变化。不推荐这种做法,并没有什么技术含量。
⑤、总结:“10 10 10 10 10 10 10 10 10 10”程序,是“10个对应一个function() {document.write(i + " ");}函数”;而"0 1 2 3 4 5 6 7 8 9"程序,是“10个对应10个立即执行函数”,所以可以打印出10个不同的值。

你可能感兴趣的:(#,2.,JS,JS)