JS中变量和函数的使用

243 阅读8分钟

一.变量

顾名思义,变量就是一个可以改变的量。我们通常把一个用来存贮数据的容器叫做变量。
一个变量有两部组成:变量名和变量值。
变量本质是就是一块内存空间,变量名指内存空间的别名,变量值指内存空间的数据。
声明变量,也可以理解为创造一个变量,需通过使用关键字来进行声明。

声明变量:

关键字 变量名;            例: 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来测试一下varlet的不同之处

for(var/let i = 0; i<10; i++){
    setTimeout(function(){
        console.log(i)
    },1000)
}

二.函数

在js中,JavaScript 函数是被设计为执行特定任务的代码块,仅会在某代码调用它时被执行。

一、如何创建一个函数

  1. 使用函数表达式创建函数

    var obj = function([形参1,形参2,...,形参n]){函数中的语句};

  2. 使用函数声明形式创建函数

    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也可以省,{}也可以省

箭头函数的一些特点:

  1. 不可以当作构造函数 ,也就是说,不可以使用new命令,否则会抛出一个错误。
  2. 不可以使用arguments对象,该对象在函数体内不存在,如果要用实参列表,可以用 rest 参数代替
  3. 在箭头函数内部,没有自己的this。
  4. 箭头函数的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、按值传递

  1. 什么是按值传递?

按值传递,就是把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。

我们可以把ECMAScript函数的参数想象成局部变量。在向参数传递基本类型的值时,被传递的值被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来说,就是arguments对象中的一个元素)。在向参数传递引用类型时,会把这个值在内存中的地址(指针)复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。传递完后俩个变量各不相干!

var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1
  1. 按引用传递

拷贝虽然很好理解,但是当值是一个复杂的数据结构的时候,拷贝就会产生性能上的问题。

所以还有另一种传递方式叫做按引用传递。

所谓按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。

var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2