认识函数,声明提前及数据类型比较

304 阅读8分钟

函数

可复用的代码块

  • 函数是一种对象
  • 所有的函数都是由Function构造的

定义函数

函数声明(具名函数)

function printName(){
   return 返回值; 
}

函数表达式 (匿名函数)

var a = function(x,y){//左边是赋值,右边是表达式
    return x+y;
}

构造函数

var add = new Function('x','y','return x + y')
//等同于
function add(x,y){
    return x + y;
}

函数本身和函数调用

fn和fn()

let fn = () => console.log('hello');//fn保存了匿名函数地址
let fn2 = fn;//将这个地址赋值给fn2
fn2();//hello  fn2调用了匿名函数

参数

函数可以传递多个参数

没有传递值的命名参数将自动被赋予undefined值。

function fn(a){
    return a;
}
fn();//undefined

形参和实参

function add(x,y){//x,y是形参
    return x+y;
}
add(1,2);//1,2是实参

形参可认为是变量声明

function add(){
    var x = arguments[0];
    var y = arguments[1];
    return x+y;
}
add(2,3);//5

arguments

arguments对象是所有(非箭头)函数中都可用的局部变量

arguments对象不是一个 Array,它是一个(伪)类数组,没有数组的方法。

在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给参数的每一个参数arguments[i]

使用arguments对象的length属性可以获知有多少个参数传递给了函数。

typeof arguments; // "undefined"

function howManyArgs() {   
    console.log(arguments.length);
}
howManyArgs('string',45); // 2,传递了2个参数

使用Array.from()方法将参数转换为真实数组

var args = Array.from(arguments);
//
var args = Array.from('arr')
console.log(args);//["a", "r", "r"]

//
function f() {
  return Array.from(arguments);
}
f(1, 2, 3);//[1, 2, 3]

重载

ECMAScript没有重载

  • 在其他语言中,可以为一个函数编写两个定义,只要两个定义的签名(接受的参数的类型和数量)不同就可以。
  • 但是在ECMAScript中函数没有签名,其参数是包含零个或多个值的数组来组成的。它所谓的命名参数只是提供便利,但不是必需的。没有函数签名,就做到无法重载。
  • 如果ECMAScript定义了两个名字相同的函数,则该名字属于后定义的函数。

声明提前

变量声明前置

示例:

console.log(a); 
var a = 1;
console.log(a);

结果为undefined 1。

JavaScript 仅提升声明,而不提升初始化

上面示例可理解为:

var a;
console.log(a);
a = 1;
console.log(a);

函数声明前置

执行代码前会先读取函数声明,即函数声明不必放在调用的前面,它可以放在当前作用域的任何位置。

示例:

fn();
function fn() {
return('hello');
}

function声明会在代码执行之前就创建、初始化、赋值(都提升)

理解:

  1. 找到function函数中所有被声明的变量,在环境中创建变量。
  2. 将这些变量初始化并赋值。
  3. 开始执行代码fn()。

比较

变量声明和函数声明都会提升,函数声明提升的优先级高于变量声明提升

关于let、var和function:

  • let 的「创建」过程被提升了,但是初始化没有提升。
  • var 的「创建」和「初始化」都被提升了。
  • function 的「创建」「初始化」和「赋值」都被提升了。

示例: 注意区分函数声明和函数表达式

fn()
sum(3,4)
var fn = function(){
    console.log('fn...')
}//函数表达式
function sum(a,b){
    return a + b;
}

可理解为:

function sum(a,b){
    return a + b;
}
var fn
fn()
sum(3,4)
fn = function(){
    console.log('fn...')
}
function fn(){
console.log(2)
}
var fn = function(){
console.log(3)
}
fn()// 3

可理解为

function fn(){
console.log(2)
}
var fn
fn = function(){
console.log(3)
}
fn()// 3
// 出现命名冲突,就近原则函数表达式的值为最后结果

立即执行的函数表达式

IIFE也称自执行匿名函数,表达式的变量不能从外部访问。

(function () {
    statements
})();

声明一个匿名函数,马上调用这个匿名函数。

作用:创建一个独立的作用域,避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。

命名冲突

就近原则

function fn(fn){
  console.log(fn);

  var fn = 3;
  console.log(fn);
}
fn(10) //10 3

可以写为

function fn(){
var fn = arguments[0];
  console.log(fn);

  var fn = 3;
  console.log(fn);
}
fn(10) //10 3

递归

自己调用自己

需要设置结束条件。

function f(n){
    if(n === 1){
        return 1
    }
    return n * f(n-1)
}
f(3)  //6

尾递归

即最后一步调用另外一个函数

function f(n,total){
    if(n === 1){
        return total;
    }
    return f(n-1,n*total)
}

作用域

JavaScript作用域是靠函数来形成的,也就是一个函数内定义的变量,函数外不可以访问。

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链。

作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问

  • 作用域的前端始终是当前执行代码所在环境的变量对象。
  • 全局执行环境的变量对象是作用域链中的最后一个对象。

步骤

  1. 函数在执行的过程中,先从自己内部找变量。
  2. 如果找不到,再从创建当前函数所在的作用域去找,以此往上。
  3. 注意找的是变量的当前状态。

实例

var color = "blue";
function changeColor(){
        var anotherColor = "red";
        function swapColors(){
                var tempColor = anotherColor;
                anotherColor = color;
                color = tempColor;
                // 这里可以访问color、anotherColor、tempColor
        }
        // 这里可以访问color、anotherColor,但不能访问tempColor
        swapColors();
}
// 这里只能访问color
changeColor();

作用域链图 作用域链.jpg

var a = 1;
function fn1(){
  function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2();
    var a = 4;
  }
  var a = 2;
  return fn3;
}
var fn = fn1(); 
fn() 
//输出 undefined

可理解为

var a = 1;
function fn1(){
    var a;
  function fn3(){
    var a;
    function fn2(){
      console.log(a)
    }
    fn2();
    a = 4;
  }
  a = 2;
  return fn3;
}
var fn = fn1(); 
fn() //undefined

解析: 在本例中先执行var a = 1,var fn = fn1(),进入function fn1(),执行return fn3,进入function fn3(),由于变量声明前置,先执行var a,在执行fn2(),进入function fn2(),执行console.log(a),在该作用域内没有a,前往上一级作用域寻找a,找到 var a,未赋值,所以结果为undefined。

示例2

var a = 1
function fn1(){
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2
  return fn3
}
function fn2(){
  console.log(a)
}
var fn = fn1()
fn() 
//输出1

可理解为

var a = 1
function fn1(){
    var a
  function fn3(){
    var a = 4
    fn2()
  }
   a = 2
  return fn3
}
function fn2(){
  console.log(a)
}
var fn = fn1()
fn() //1

示例3

var a = 1
function fn1(){
  function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2()
   var a = 4
  }
  var a = 2
  return fn3
}
var fn = fn1()
fn() 
//输出 undefined

可理解为

var a = 1
function fn1(){
    var a 
  function fn3(){
    var a
    function fn2(){
      console.log(a)
    }
    fn2()
    a = 4
  }
   a = 2
  return fn3
}
var fn = fn1()
fn() 

数据类型详解

基本类型

保存在栈内存中的简单数据段。

包括:number、string、Boolean、null、undefined

引用类型

保存在堆内存中的是可能由多个值构成的对象。

变量中保存的实际上是一个指针,这个指针指向的位置保存的是对象。

包括:Object、Array、Function、正则

基本类型和引用类型比较

不同

存储位置

  • 基本类型保存在栈内存中
  • 引用类型保存在堆内存中

访问方式

  • 基本类型是按值访问
  • 引用类型是按引用访问

值能否改变

  • 基本类型的值是不可变的,互相不影响

变量会被赋予新值,但是原值不可改变。

var a = 'hello'
a.toUpperCase() //HELLO
console.log(a) // hello
// a的值未改变。

var a = 5
a = 10
console.log(a) // 10
//结果是10是因为原值被替换了但是没有被改变。

var num1 = 10
var num2 = num1
num1 = 100
console.log(num1) // 100
console.log(num2) // 10
//将num1的值赋给num2,再改变num1的值,num1和num2仍然独立的个体,num2不受num1影响。
  • 引用类型的值可变的,且互相影响
var a = {num:1}
var b = a
b.num = 2
console.log(a.num) //2
console.log(a == b) //true
//变量a和b实际上是指针都指向同一个对象,当为b改变num属性的值时,可以通过a访问到,因为引用的时同一对象。

值能否添加属性和方法

  • 基本类型的值不可以添加属性和方法
var name = 'Jane'
name.age = 18
cosnole.log(name.age) // undefined
  • 引用类型的值可以添加、改变、删除其属性和方法
var person = new Object()
person.name = 'John'
console.log(person.name) // John
person.name = 'Ann'
console.log(person.name) // Ann

比较

  • 基本类型的比较是值的比较
var a = 1
var b = true
console.log(a == b) //true
  • 引用类型的比较是引用的比较

引用类型比较的是指向堆内存的地址是否相同。

var a = {}
var b = {}
console.log(a == b) // false

var a = {num:1}
var b = a
b.num = 2
console.log(a.num) //2
console.log(a == b) // true

检测数据类型

  • 检测基本类型typeOf操作符

typeOf检测null和对象都会返回Object,检测函数时会返回function。

  • 检测引用类型instanceOf操作符

instanceOf检测基本类型会始终返回false。

相同

ECMAScript中所有函数的参数都是按值传递的

function add(n){
    n += 10
    return n
}
var count = 100
var result = add(count)
console.log(count) // 100
console.log(result) // 110


function setName(obj){
    obj.name = 'Nicholas'
    obj = new Object()//这里obj引用的地址改变了
    obj.name = 'Jane'
}
var person = new Object()
setName(person)
console.log(person.name) // Nicholas
//创建一个对象并将其保存在变量person中,这个变量被传递到setName()中复制给了obj。在函数内部obj和person引用的是同一个对象。
//若引用类型的参数是按引用传递的,obj.name修改成jane时,会影响person.name的值,但是结果并没有,所以是按值传递的