一.变量
顾名思义,变量就是一个可以改变的量。我们通常把一个用来存贮数据的容器叫做变量。
一个变量有两部组成:变量名和变量值。
变量本质是就是一块内存空间,变量名指内存空间的别名,变量值指内存空间的数据。
声明变量,也可以理解为创造一个变量,需通过使用关键字来进行声明。
声明变量:
关键字 变量名; 例: var a;
定义变量(即是在声明的同时赋值):
关键字 变量名:变量值; 例: var a = 1;
对于变量来说,关键字有两个,一个是var,一个是let。
如果在创建一个变量时不使用关键字,那么这个变量就会变为全局变量,在函数外部也能访问到。
加var与不加var的区别在于:
1. 加var的变量不能被删除,而不加var的变量可以被删除,不加var的变量实际上是作为window对象的属性保存的。
2. 加var的变量会被提升,不加var的变量不会被提升。
使用var的变量不足之处 :
>1. 会对变量进行提升,所有使用var声明的变量都会被提升到全局作用域中
>2. 可重复声明变量,后面的变量会覆盖前面的
>3. 可以省去**var**,也就是说我们无法分辨出该变量是否已经被声明
使用let则可以避免var的许多不足,在使用let关键字声明变量时:
>1. 它会创建一个块级作用域,在块级作用域以外的位置无法访问这个变量
>2. 不支持重复声明变量,这样也可以用来检测我们是否之前用**let**声明过这个变量
>3. 使用let声明的变量存在暂存性死区,不会被提升
全局作用域与局部作用域的分界就是函数。在函数外就是全局作用域,在函数内就是局部作用域。
下面是一个简单的js来测试一下var与let的不同之处
for(var/let i = 0; i<10; i++){
setTimeout(function(){
console.log(i)
},1000)
}
二.函数
在js中,JavaScript 函数是被设计为执行特定任务的代码块,仅会在某代码调用它时被执行。
一、如何创建一个函数
-
使用函数表达式创建函数
var obj = function([形参1,形参2,...,形参n]){函数中的语句};
-
使用函数声明形式创建函数
function obj([形参1,形参2,...,形参n]){函数中的语句}
在上述两种函数创建方法中,obj 为函数名,可自定义;()中的形参可理解为声明在函数内部的变量,可设置一个或多个形参,各个形参之间用","隔开,在ES6中,形参也可赋给一个初始值,例如[形参1,形参2=def,形参3];调用函数可直接使用obj() ,在调用函数时,可以在()中指定实参(也叫实际参数),实参会按照顺序赋值给对应的形参(会覆盖掉形参的初始值),多余的实参不会被赋值,缺少的实参(如果对应的形参没有初始值的话)会被定义为underfined。
此外,这两种方式的不同之处在于--使用函数声明创建的函数会进行提升,而使用函数表达式创建的函数不会被提升。且函数声明的提升比使用var声明的变量的提升更加优先。
二、函数的一些基本知识
1、return
可以使用return设置函数的返回值
语法: return 返回值;
return后的值将作为函数的执行结果返回,可以定义一个变量来接受。且,在函数内return语句后的语句都不会被执行,return后也可以不接任何值或者不写return语句,这样都将会返回underfined。return后的值可以是任意的,但只能是一个值,可以定义一个数组来返回多个值。
2、arguments
在调用函数时,函数内都会出现两个隐藏的参数,一个是this,另一个就是arguments。
我们所传递的实参都将会在arguments中保存,arguments是一个类数组对象,我们可以通过索引来操作实参数据
callee属性
Arguments 对象的 callee 属性,通过它可以调用函数自身。
在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()**。当一个函数必须调用自身的时候, 避免使用 **arguments.callee(),通过要么给函数表达式一个名字,要么使用一个函数声明.
讲个闭包经典面试题使用 callee 的解决方法:
var data = [];
for (var i = 0; i < 3; i++) {
(data[i] = function () {
console.log(arguments.callee.i)
}).i = i;
}
data[0]();
data[1]();
data[2]();
// 0
// 1
// 2
3、箭头函数
这是在ES6中新出现的一种简化创建函数的方法
var obj=()=>{}
如有多个形参,或者没有形参,则()不能少
如果只有一句函数体(非return),则可以不写{}
如果只有一句函数体,并它是return语句 ,则return也可以省,{}也可以省
箭头函数的一些特点:
- 不可以当作构造函数 ,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在,如果要用实参列表,可以用 rest 参数代替
- 在箭头函数内部,没有自己的this。
- 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this。
4、执行上下文
每一个代码在调用时,都会产生一个执行上下文。我们把执行的代码分为两类:一类是全局代码,一类是函数代码。
全局代码只会有一个,函数代码可以有很多个
执行函数代码就会产生一个函数执行上下文;执行全局代码就会产生一个全局执行上下文
全局执行上下文只有一个。函数执行上下文会有多个。
每一次执行函数,就会产生一个函数执行上下文。例如,一个函数f,被调用了10次,就会产生10执行上下文。
执行上下文的作用:函数(代码)在执行的过程中,需要的一切数据都由执行上下文来提供。数据包含:变量,函数。
执行上下文的具体内容:
每一个函数(例如f)执行上下文由两部分组成:
1. f内部定义的变量,arguments,内部定义的函数
2. 函数f的父级函数的执行上下文
父级函数:在定义f时,把直接f包起来的那个函数。如果f外层没有函数,则父级函数就理解全局代码。
在《JavaScript深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
5、立即执行函数
如果IIFE前面是对象,也需要在前面的对象{}后加分号
1)方式一:
(Function(){})()
2)方式二:
(Function(){}())
如果连续使用方式一或者方式二,则需要在每个函数的结束位置加一个分号";",以避免出错
3)方式三:在前面加运算符
+ function () {
console.info(1);
}()
- function () {
console.info(1);
}()
* function () {
console.info(1);
}()
/function () {
console.info(1);
}()
% function () {
console.info(1);
}()
!function () {
console.info(1);
}()
6、按值传递
- 什么是按值传递?
按值传递,就是把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
我们可以把ECMAScript函数的参数想象成局部变量。在向参数传递基本类型的值时,被传递的值被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来说,就是arguments对象中的一个元素)。在向参数传递引用类型时,会把这个值在内存中的地址(指针)复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。传递完后俩个变量各不相干!
var value = 1;
function foo(v) {
v = 2;
console.log(v); //2
}
foo(value);
console.log(value) // 1
- 按引用传递
拷贝虽然很好理解,但是当值是一个复杂的数据结构的时候,拷贝就会产生性能上的问题。
所以还有另一种传递方式叫做按引用传递。
所谓按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。
var obj = {
value: 1
};
function foo(o) {
o.value = 2;
console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2