1.变量
(1)变量提升
(2)变量作用域
(3)一等公民
(4)函数名提升
(5)函数内的变量提升
(6)*函数参数的默认值
(1)变量提升
在编码过程中,其保存的值可以发生改变的量称为变量。我们已经对变量十分熟悉了,但是我们今天来讨论一下变量的一些使用问题。先来看看下面的一段代码:
console.log(num);
var num = 1;
很显然,上面这段代码中输出语句被写在了变量的初始化之前。按照html的规则这段代码肯定无法正常运行的,因为在输出num变量的时候变量还没有被赋初值。
但是事实并不是如此,从浏览器中我们能够直观的看到输出结果是undefined。
原因:
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果就是所有的变量的声明语句,都会被提升到代码的头部。javascript的这种读取变量的机制就叫做变量提升。
所以上面一段代码实际上在执行的时候是下面的过程:
var num;
console.log(num);
num = 1;//最后的结果是显示undefined,表示变量a已声明,但还未赋值
ps:变量提升只对var命令声明的变量有效,如果一个变量不是用var命令声明的,就不会发生变量提升。
(2)变量作用域
用var声明的变量:
console.log(num);
var num = 1;//undefined
不用var声明的变量:
console.log(num);
num = 1;//error!
很明显的区别,不使用var声明的变量在使用时被系统识别为错误。这是因为javascript中存在一个叫做局部变量的东西:
var声明的变量称为局部变量。
局部变量仅在其所在的函数范围内生效,变量生效的范围被称为变量作用域。
而变量如果不适用var来声明,则表示变量在整个文件内生效。即全局变量。
(3)一等公民
在很多传统语言(C/C++/Java/C#等)中,函数都是作为一个二等公民存在,你只能用语言的关键字声明一个函数然后调用它,如果需要把函数作为参数传给另一个函数,或是赋值给一个本地变量,又或是作为返回值,就需要通过函数指针(function pointer)、代理(delegate)等特殊的方式周折一番。
但是在JavaScript世界中函数却是一等公民,它不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样赋值、传参、返回,这样的函数也称之为第一级函数(First-class Function)或一等公民。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)。这样的多重身份让JavaScript的函数变得非常重要。
简化总结:
JavaScript的函数与其他数据类型(数值、字符串、布尔值等等)处于同等地位,可以使用其他数据类型的地方就能使用函数。
比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者把函数作为函数的结果返回。
将函数赋值给变量:
var func1 = function(){};
将函数赋值给对象的属性:
var frank = { ability:null };
frank.ability = function(){console.log("吃");};
frank.ability();//吃
将函数作为参数传入其他函数:
function introduce(Func){Func();}
function sayHello(){console.log(“hello”);}
introduce(sayHello);//hello
将函数作为函数的结果返回:
function jiSuanQi(){ return sum(4,10);}
function sum(num1,num2){ return num1+num2; } console.log(jiSuanQi());//14
(4)函数名提升
JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。
qiuHe(1,2);
function qiuHe(a,b) { console.log(a+b); }//3
ps:只有function声明的函数会发生函数名提升,而如果通过赋值语句写的函数则不会。
qiuHe(1,2);
var qiuHe = function(a,b){ console.log(a,b); };//qiuHe is not a function
(5)函数内的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
(6) * 函数参数的默认值
function func(num){
num = num || 1; // num默认值为1
return num;
}
func() // 1
这种写法会对函数参数num进行一次布尔运算,只有为true时才会返回num。避免了因为忘写参数而导致的函数调用失败问题。
可是除了undefined以外,0、空字符、null等的布尔值也是false。也就是说,在上面的函数中,不能让num等于0或空字符串,否则在明明有参数的情况下,也会返回默认值1。
func('') // 1
func(0) // 1
2.内存
javascript的内存相关的知识有太多,像内存管理、内存回收、内存分配等不是一两天就能够介绍完毕并掌握的,因此今天我们并不对内存本身作过多的介绍。
我们今天主要学习的是从javascript内存中总结延展出来的一些应用和规律。对于内存本身的知识,我们随着不断地深入学习会逐渐学习到。
(1)【值传递】和【地址传递】
(2)函数的同名参数
(3)arguments对象
(4)eval函数
(5)instanceof类型检测
(6)javascript垃圾回收机制
(1)【值传递】和【地址传递】
原始数据类型(数值、字符串、布尔值)的内容传递方式是值传递(pass by value)
而复合数据类型(数组、对象、其他函数)的内容传递是地址传递(pass by reference)
var num1 = 123;
var num2 = num1;
num2 = 456;
console.log(num1);
console.log(num2);
num1向num2复制基本类型的值的时候,会在第二行创建一个新值(即num2),然后把该值复制到num2上。
两个变量可以参与任何操作而不会互相影响,因为从内存上来讲num1和num2已经是两个完全不同的变量了。
var frank = { age : 18 };
function func(obj) {
obj.age = 17;
}
func(frank);
console.log(frank.age)//17
传入函数的实际上是frank对象的内存地址。
因此在函数内部修改参数,将会影响到frank对象本身。
值传递和地址传递 结论:
原始数据类型(数值、字符串、布尔值)的内容传递方式是值传递(pass by value)
值传递传递的内容时一个具体的数值
例子:
var num1=10;
var num2=num1;
var num3=num1;
num1=20;
console.log(num2);
console.log(num3);
而复合数据类型(数组、对象、其他函数)的内容传递是地址传递(pass by reference)
地址传递的内容时一个具体的内存地址
例子:
var arr1=[1,2,3,4,5];
var arr2=arr1;
var arr3=arr1;
arr1.pop();
console.log(arr2);
console.log(arr3);
总结用法:值传递不会对原有内容产生改变,而地址传递能够改变原数据
(2)函数的同名参数
如果函数有同名的参数,则取最后出现的那个参数值。
function func(num, num) {
console.log(num);
}
func(1, 2) // 2
如果函数没有提供第二个参数,num的取值就变成了undefined
function func(num, num) {
console.log(num);
}
func(1) // undefined
原则上来讲:尽量不要写同名参数,而且定义函数的时候写了几个参数,在调用的时候尽量保证和定义时一致。
(3)arguments对象
由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制来在函数体内部读取所有参数。这就是arguments对象的由来。
arguments对象包含了函数【运行时】的所有参数。
arguments[0]就是函数的第一个参数,arguments[1]是第二个,以此类推。这个对象只有在【函数内部】才可以使用。
var func = function(one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
func(1, 2, 3);
能够看到,尽管在函数被声明的时候并没有多个参数。但是在函数运行的时候有三个参数,因此arguments对象便能够获取到这三个参数。
arguments对象除了可以读取参数,还可以为参数赋值
var func = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
console.log(func(1, 1)); // 5
当然也可以通过arguments对象length属性,判断函数调用时究竟带几个参数
function func() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0
需要注意的是,虽然arguments很像数组,但它是一个对象。
数组专有的方法(比如slice),不能在arguments对象上直接使用。
(4)eval函数
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。(eval命令的作用是,将字符串当作语句执行。)
语法:eval(string)
该方法只接受【原始字符串】作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。因此请不要为 eval() 函数传递 String 对象来作为参数。
eval('var num = 100;');
console.log(num);// 100
ps:如果eval函数在使用的过程中发生了非法调用或者传入参数出错,则会抛出异常。
pss:虽然 eval() 的功能非常强大,但在实际使用中用到它的情况并不多。
(5)instanceof类型检测
typeof用来检测基本数据类型简直是神器,但是如果是引用数据类型,则需要使用instanceof操作符。
instanceof 用于判断一个变量是否某个对象的实例。
var arr = new Array();
console.log(arr instanceof Array); //true
console.log(arr instanceof Object);//true(Array 是 object 的子类)
上面我们强调了arguments是一个对象。
在这里我们可以用instaceof测试会发现 arguments不是一个Array对象,尽管看起来很像。
function func() {
console.log(arguments instanceof Array); //false
console.log(arguments instanceof Object);//true
}
func();
(6)javascript垃圾回收机制
对于其他语言来说,如C,C++,需要开发者手动的来跟踪并管理内存。
而JS的垃圾回收机制使得JS开发人员无需再关系内存的情况,所有的内存分配以及回收都会由垃圾回收器自动完成,执行环境会对执行过程中占有的内存负责。
其原理就是找出那些不在被使用的变量,然后释放其所占有的内存。回收器一般是按照固定的时间间隔或者预设的时间进行处理的。
function test1(){
var item = { name:'frank' };
}
function test2(){
var item = { name:'frank' };
return item;
}
var m1 = test1();
var m2 = test2();
3.Math对象
Math对象是JavaScript的内置对象,提供一系列数学常数和数学方法。该对象不是构造函数,所以不能生成实例,所有的属性和方法都必须在Math对象本身上调用。
javascript为Math对象提供了一系列的属性和方法来帮助更好的使用:
(1)Math对象属性:Math对象的属性表示只读的数学常数
Math.E:常数e。 // 2.718281828459045
Math.LN2:2的自然对数。// 0.6931471805599453
Math.LN10:10的自然对数。// 2.302585092994046
Math.LOG2E:以2为底的e的对数。// 1.4426950408889634
Math.LOG10E:以10为底的e的对数。// 0.4342944819032518
Math.PI:常数Pi。// 3.141592653589793
Math.SQRT1_2:0.5的平方根。// 0.7071067811865476
Math.SQRT2:2的平方根。// 1.4142135623730951
(2)Math对象的方法
【Math.round()方法】:四舍五入
当参数为正数时,正常的四舍五入标准。(小于0.5取0,大于等于0.5取1)
当参数为负数时,向下取整标准。(小于等于-0.5取-0,-大于0.5取-1)
Math.round(0.1) // 0
Math.round(0.5) // 1
Math.round(-1.1) // -1
Math.round(-1.6) // -2
【Math.floor()方法】:返回小于参数值的最大整数。(向下取整)
Math.floor(3.9) // 3
Math.floor(-3.2) // -4
【Math.ceil()方法】:返回大于参数值的最小整数。(向上取整)
Math.ceil(3.2) // 4
Math.ceil(-3.9) // -3
【Math.abs()方法】:返回参数值的绝对值
Math.abs(1) // 1
Math.abs(-1) // 1
【Math.max()方法】:返回最大的参数
Math.max(2, -1, 5) // 5
【Math.min()方法】:返回最小的参数
Math.min(2, -1, 5) // -1
【Math. pow()方法】:返回以第一个参数为底数、第二个参数为幂的指数值
Math.pow(2, 2) // 4
Math.pow(2, 3) // 8
【Math. sqrt()方法】:返回参数值的平方根。如果参数是一个负值,则返回NaN
Math.sqrt(4) // 2
Math.sqrt(-4) // NaN
【Math. log()方法】:返回以e为底的自然对数值。
Math.log(Math.E) // 1
Math.log(10) // 2.302585092994046
【Math. exp()方法】: 返回常数e的参数次方。
Math.exp(1) // 2.718281828459045
Math.exp(3) // 20.085536923187668
【三角函数方法】
Math.sin方法返回参数的正弦
Math.cos方法返回参数的余弦
Math.tan方法返回参数的正切。
Math.asin方法返回参数的反正弦
Math.acos方法返回参数反余弦
Math.atan方法返回参数反正切
Math.sin(0) // 0
Math.cos(0) // 1
Math.tan(0) // 0
【Math. random()方法】:返回0到1之间的一个伪随机数。可能等于0,但是一定小于1。
Math.random() // 0.7151307314634323
4.Date对象
Date对象是JavaScript提供的日期和时间的操作接口。
在JavaScript内部,所有日期和时间都储存为一个整数。
这个整数是当前时间距离1970年1月1日00:00:00的毫秒数,正负的范围为基准时间前后各1亿天
同Math对象一样,javascript也为Date对象提供了很多内置的方法。
(1)Date()函数
Date函数可以被Date对象可以直接调用,返回一个当前日期和时间的字符串。
Date(); //"Fri Nov 7 2014 14:52:00 GMT+0800 (中国标准时间)"
Date(2000, 1, 1); //"Fri Nov 7 2014 14:52:00 GMT+0800 (中国标准时间)"// 无论有没有参数,直接调用Date总是返回当前时间
(2)Date(日期字符串|日期参数)构造函数
Date对象是一个构造函数,对它使用new命令,会返回一个Date对象的实例。
【如果不加参数,生成的就是代表当前时间的对象。】
语法:
var date1 = new Date();
var today = new Date();
// "Fri Nov 7 2014 14:52:00 GMT+0800 (中国标准时间)"
【如果添加一个日期字符串作为参数,返回字符串所对应的时间。】
语法:
var date2 = new Date("January 6, 2013");
// Sun Jan 06 2013 00:00:00 GMT+0800 (中国标准时间)
一些其他合法的日期字符串写法
new Date(datestring)
new Date(“2013-2-15”)
new Date(‘2013/2/15’)
new Date(“2013-FEB-15”)
new Date(“FEB, 15, 2013”)
new Date(“FEB 15, 2013”)
new Date(“Feberuary, 15, 2013”)
new Date(“Feberuary 15, 2013”)
new Date(“15, Feberuary, 2013”)
Sun Jan 06 2013 00:00:00 GMT+0800 (中国标准时间)
【如果添加日期参数,返回字符串所对应的时间。】
语法:var date2 = new Date(year, month, day, hours, minutes, seconds, ms)
使用日期参数时,年月必须写,其他则默认和可以省略,省略为0。
new Date(2013, 2)// Fri Mar 01 2013 00:00:00 GMT+0800 (中国标准时间)
new Date(2013, 0, 15)// Tue Jan 15 2013 00:00:00 GMT+0800 (中国标准时间)
new Date(2013, 0, 22, 7)// Tue Jan 22 2013 07:00:00 GMT+0800 (中国标准时间)
new Date(2013, 0, 1, 8, 15, 47, 18) //Tue Jan 01 2013 08:15:47 GMT+0800 (中国标准时间)
(3)日期运算
之前提到过,javascript内部存储日期类型的时候等于距离1970年1月1日零点的毫秒数。而显示给用户的时候是转换为字符串显示的。因此:
两个日期对象进行减法运算,返回的就是它们间隔的毫秒数。 两个日期对象进行加法运算,返回的就是连接后的两个字符串。
var d1 = new Date(2000, 2, 1);
var d2 = new Date(2000, 3, 1);
console.log(d2-d1);//2678400000
console.log(d2+d1);//Sat Apr 01 2000 00:00:00 GMT+0800 (中国标准时间)Wed Mar 01 2000 00:00:00 GMT+0800 (中国标准时间)
(4)日期对象的get*系列方法
getTime()://返回实例对象距离1970年1月1日00:00:00对应的毫秒数,等同于valueOf方法。
getDate()://返回实例对象对应每个月的几号(从1开始)。
getDay()://返回星期几,星期日为0,星期一为1,以此类推。
getYear()://返回距离1900的年数。
getFullYear()://返回四位的年份。
getMonth()://返回月份(0表示1月,11表示12月)。
getHours()://返回小时(0-23)。
getMilliseconds()://返回毫秒(0-999)。
getMinutes()://返回分钟(0-59)。
getSeconds()://返回秒(0-59)。
编写函数:计算本年还剩下多少天。
function leftDays() {
var today = new Date();
var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);
var msPerDay = 24 * 60 * 60 * 1000;
return Math.round((endYear.getTime() - today.getTime()) / msPerDay);
}