- 高级语言的几种分类方式:编译型语言与解释型语言、弱数据类型与强数据类型、面向过程与面向对象(JS既面向过程又面向对象)、脚本语言与非脚本语言。
- 下面介绍编译型语言与解释型语言:
高级语言翻译成机器语言要么通过编译的方式,要么通过解释的方式。也因此产生编译型语言与解释型语言的分类。
编译型语言:通篇翻译(将所有代码都浏览后,进行一次翻译)后产生一个最终的文件,这个文件将用来执行程序。如C++、C语言等。优点&缺点:快;不跨平台。
解释型语言:“浏览一行(分号判定语句的结束)——翻译一行——执行一行”。如JS、php、python等语言。优点&缺点:慢;跨平台。- 注意:java语言既不是编译型语言也不是解释型语言。.java文件通过javac指令编译成.class文件,然后通过jvm虚拟机进行解释执行。实现了跨平台性。
对预编译进行讲解:
- 真正了解预编译前,需知道以下规则:
- 对于函数来说:声明&定义整体提升;对于变量来说:声明提升,赋值不提升。
(“提升”指的是“提升上来,最先执行”)- 任何一个变量,如果变量未经声明就赋值,此变量就为全局对象所有。(Imply Global 暗示全局变量)
eg: a = 123;
//a未声明,直接赋值
eg: var a = b = 123;
//连续赋值,从左至右。顺序:123赋给b(这一过程,b是未经声明的变量,归window域上);然后声明a;最后将b的值赋给a。- 全局上的任何变量,都是window域/GO(后面讲解)上的属性。
eg: var a = 123; ==> window.a = 123;
- 预编译过程讲解(以下面的函数为例;函数的预编译发生在函数执行的前一刻):
<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>
- 创建AO(Activation Object ;中文被称作“执行期上下文”;其作用相当于一个“作用域”)对象。
AO { }
- 找函数里的形参和变量声明,将变量和形参名都作为AO属性名,值为undefined。
AO { a : undefined, //即使有多个名为a的东西,也只写一个 b : undefined }
- 将实参值和形参统一。
AO { a : 1, b : undefined }
- 在函数体里面找函数声明,将函数名也作为属性名写入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>
a = 1 && 0 && 1; //返回值为0。
a = 1 && false && 1; // 返回值为false。
var data = ...; //data是从后端传输过来的有效数字
data && (一个用到data的语句) //这样,执行的语句得到了保障,因为只有data真正有意义时,才会执行后面的语句。
var a = 0 || 1;
document.write(a);//输出1
var a = 2 || 1;
document.write(a);//输出2
下面的几种数据如果以布尔类型输出,都将为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
console.log(typeof(a)); //控制台将输出undefined
- 123.9、“123.9”、123.3都将被转换成123;
- "123abc"可以被转换成123(砍断原则:遇到非数字位时结束,但将前面所有的数字返回);
- null、true的转换结果为NaN;
- parseInt(10, 16);结果为16——把a看作是b进制的数字,然后转换成10进制显示出来。
- var dema = 10;
var num = demo.toString();
var num = demo.toString(8); //把10转换为了8进制- undefined 、 null不可以调用toString()
很多时候,我们不希望自动发生隐式转换。于是,JS中出现了“===”与“!= =”(中间没有空格)分别表示“绝对等于”与“绝对不等于”。
function test() {
函数体
}
①、命名函数表达式
var test1 = function f() { 函数体 } //控制台中输入test1.name,输出为f
②、匿名函数表达式(常用)
var test2 = function () { 函数体 } //控制台中输入test2.name,输出为test2
<script type="text/javascript">
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var global = 100;
a();
</script>
Step1、只要函数在程序中有定义,就会产生一个该函数对应的GO{}作用域链:
Step2、当执行a()函数时,该函数的作用域链首先被改变——增加其自身变量产生作用域对象AO{}:
Step3、由于函数b()在函数a()内,所以执行a()时,就产生b()定义情况下的作用域链:
PS. “b直接拿到a的劳动成果;且是a的执行过程产生的劳动成果;且是a的作用链的引用,即,若在b中修改a函数中创建的变量,则a的作用域链上的这个变量也会改变”。
PS. 如果不执行a()函数,就不会产生b()定义时的作用域链;但对于a()来说,由于它就是最外层的函数,所以只要执行这个程序,就一定会产生a()的作用域链。
Step4、当执行b()函数时,该函数的作用域链首先被改变——增加其自身变量产生作用域对象AO{}:
Step5、b()先执行完,销毁AO,即恢复到定义状态时的作用域链:
Step6、a()执行完,销毁AO,即恢复到定义状态时的作用域链:
<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>
*************************基础版*************************
<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>
*************************升级版*************************
<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>
<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>
<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>
<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>
(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个不同的值。