JS函数浅析

218 阅读6分钟

创建函数

函数是一种特殊的函数

  		// 具名函数
		function fn(x, y) {
            return x + y
        }
		// 匿名函数
        function (x, y) {
            return x + y
        }
		// 箭头函数
        let a = x => x * x

        let a2 = (x, y) => {
            console.log('hi!');
            return x + y
        }
        let a3 = x => ({
            name: x
        })

函数的要素

调用时机

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)  // 马上处理完循环 再执行 console.log(i)
}

// 会打印 6 个 6

我们先了解setTimeout()方法:它是设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。setTimeout()是异步任务 for循环是同步任务,只有先把同步任务执行完在执行异步任务。这里就是先把循环执行完 ,执行完循环后的 i = 6 ,然后再执行setTimeout里面的箭头函数。打印出i 也就是6 。也就是打印出6个6。

让他打印 0、1、2、3、4、5

执行下面代码

for( let i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

在for循环中使用let的情况下,由于块级作用域的影响,导致每次迭代过程中的 i 都是独立的存在。也就是说这6次循环i独立出了 0、1、2、3、4、5、6这7个数 。

其他方法

        function fn1() {
          
            for (var i = 0; i < 6; i++) {
                function fn2(i) {
                    setTimeout(() => {
                        console.log(i)
                    }, 0)
                }
                fn2(i);
            }
        }

我的理解如下这里fn2引用了外面函数的变量形成了闭包,在闭包的状态下使得变量i始终保存在内存中,每一次调用都是在上一次调用的基础上进行计算。

作用域

JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。

在顶级作用域申明的变量是全局变量

window的属性是全局变量

其他的都是局部变量

局部变量

	    function fn(x){
            let a = 1
            x=a
        }
        console.log(a);
        // a 会报错 a is not defined

全局变量

var a = 1;

function f() {
  console.log(a);
}

f()
// 1

作用域与函数执行无关的是静态作用域

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

函数x是在函数f外部声明的,所以他的作用域绑定外层 函数x 的内部变量是不会在f函数内部取值。

闭包

闭包简单理解成“定义在一个函数内部的函数”。

如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。

正常情况下,函数外部无法读取函数内部声明的变量。如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。

父对象的所有变量,对子对象都是可见的,反之则不成立。

function f1(){
  let a = 1
  function f2(){
    let a = 2
    function f3(){
      console.log(a)  // 这里的a 调用了外面的a
    }
    a = 22
    f3()
  }
  console.log(a)
  a = 100
  f2()
}
f1()

// 1 
// 22

上面代码只解释以下 f2 和 f3 ;函数f3在函数f2里面 这时 f2 内部的所有局部变量对f3是可见的。

既然f3可以读取f2的局部变量,那么只要把f3作为返回值,我们不就可以在f1外部读取它的内部变量。

闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

		 function fn(item) {
            return function () {
                return item++;
            };
        }

        var inc = fn(5);

inc() // 5
inc() // 6
inc() // 7

上述代码 item是fn 的内部变量 ,在闭包的状态下被保留 ,每一次调用都是在上一次调用的基础上进行计算。闭包inc使得函数fn的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

形式参数

函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

function add(x, y){
  return x+y
}
// 其中 x 和 y 就是形参,因为并不是实际的参数
add(1,2)
// 调用 add 时,1 和 2 是实际参数,会被赋值给 x y

形参的本质就是变量申明

返回值

每个函数都有返回值 执行完才有返回值

没有写return 就会返回 undefined

调用栈

什么是调用栈

  • JS 引擎在调用一个函数前
  • 需要把函数所在的环境 push 到一个数组里
  • 这个数组叫做调用栈
  • 等函数执行完了,就会把环境弹(pop)出来
  • 然后 return 到之前的环境,继续执行后续代码

image-20210209161536819

爆栈

如果调用栈中压入的帧过多,程序就会崩溃

函数名提升

f()
function fn(){}
// 不管你把具名函数声明在哪里,它都会跑到第一行

表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。

f();
var f = function (){};
// TypeError: undefined is not a function


var f;
f();
f = function () {};

上面代码第二行,调用f的时候,f只是被声明了,还没有被赋值,等于undefined,所以会报错。

arguments(除了箭头函数)

arguments 是一个伪数组

由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。

this(除了箭头函数)

如果不给this任何条件 this会默认指向window

function fn(){
    console.log(this)
} 
fn()
// 输出 Window {window: Window, self: Window, document: document, name: "", location: Location, …}

如果传的这个this不是对象那个JS会自动封装成一个对象

目前可以用 fn.call(xxx, 1,2,3) 传 this 和 arguments

function fn(){
'use strict' ;  // 让this 不要乱来  开启严格模式 这里就不会变成对象
console.log(this)
}

this是隐藏参数

arguments是普通参数

let person = {
  name: 'frank',
  sayHi(this){
    console.log(`你好,我叫` + this.name)
  }
}

person.sayHi()
//相当于
person.sayHi(person) 
//person.sayHi()会隐式地把 person 作为 this 传给 sayHi
//然后 person 被传给 this 了(person 是个地址)
//这样,每个函数都能用 this 获取一个未知对象的引用了

this就是调用函数的对象

.call

		function add(x,y){
            return x+y
        }
        add.call(null,1,2)

为什么要多写一个 undefined

  • 因为第一个参数要作为 this
  • 但是代码里没有用 this
  • 所以只能用 undefined 占位
  • 其实用 null 也可以
     Array.prototype.forEach = function(){
          console.log(this);
      }
      let arr = [1,2,3]
      arr.forEach.call(arr) // 这里的arr就是this
      
      // forEach 源代码  遍历当前数组
      Array.prototype.forEach = function(fn){
       for(let i = 0;i<this.length;i++){
       fn(this[i],i)
       }
      }
      arr.forEach.call(arr.(item)=> conaole.log(item))

箭头函数

console.log(this) // window
let fn = () => console.log(this) 
fn() // window

立即执行函数

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

资料来源:饥人谷 Javascript教程-网道

本文为贰贰的原创文章,著作权归本人和饥人谷所有,转载务必注明来源