创建函数

函数是一种特殊的函数

  1. // 具名函数
  2. function fn(x, y) {
  3. return x + y
  4. }
  5. // 匿名函数
  6. function (x, y) {
  7. return x + y
  8. }
  9. // 箭头函数
  10. let a = x => x * x
  11. let a2 = (x, y) => {
  12. console.log('hi!');
  13. return x + y
  14. }
  15. let a3 = x => ({
  16. name: x
  17. })

函数的要素

调用时机

  1. let i = 0
  2. for(i = 0; i<6; i++){
  3. setTimeout(()=>{
  4. console.log(i)
  5. },0) // 马上处理完循环 再执行 console.log(i)
  6. }
  7. // 会打印 6 个 6

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

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

执行下面代码

  1. for( let i = 0; i<6; i++){
  2. setTimeout(()=>{
  3. console.log(i)
  4. },0)
  5. }

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

其他方法

  1. function fn1() {
  2. for (var i = 0; i < 6; i++) {
  3. function fn2(i) {
  4. setTimeout(() => {
  5. console.log(i)
  6. }, 0)
  7. }
  8. fn2(i);
  9. }
  10. }

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

作用域

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

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

window的属性是全局变量

其他的都是局部变量

局部变量

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

全局变量

  1. var a = 1;
  2. function f() {
  3. console.log(a);
  4. }
  5. f()
  6. // 1

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

  1. var a = 1;
  2. var x = function () {
  3. console.log(a);
  4. };
  5. function f() {
  6. var a = 2;
  7. x();
  8. }
  9. f() // 1

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

闭包

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

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

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

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

  1. function f1(){
  2. let a = 1
  3. function f2(){
  4. let a = 2
  5. function f3(){
  6. console.log(a) // 这里的a 调用了外面的a
  7. }
  8. a = 22
  9. f3()
  10. }
  11. console.log(a)
  12. a = 100
  13. f2()
  14. }
  15. f1()
  16. // 1
  17. // 22

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

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

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

  1. function fn(item) {
  2. return function () {
  3. return item++;
  4. };
  5. }
  6. var inc = fn(5);
  7. inc() // 5
  8. inc() // 6
  9. inc() // 7

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

形式参数

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

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

形参的本质就是变量申明

返回值

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

没有写return 就会返回 undefined

调用栈

什么是调用栈

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

    11 JS函数 - 图1

爆栈

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

函数名提升

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

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

  1. f();
  2. var f = function (){};
  3. // TypeError: undefined is not a function
  4. var f;
  5. f();
  6. f = function () {};

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

arguments(除了箭头函数)

arguments 是一个伪数组

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

this(除了箭头函数)

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

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

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

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

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

this是隐藏参数

arguments是普通参数

  1. let person = {
  2. name: 'frank',
  3. sayHi(this){
  4. console.log(`你好,我叫` + this.name)
  5. }
  6. }
  7. person.sayHi()
  8. //相当于
  9. person.sayHi(person)
  10. //person.sayHi()会隐式地把 person 作为 this 传给 sayHi
  11. //然后 person 被传给 this 了(person 是个地址)
  12. //这样,每个函数都能用 this 获取一个未知对象的引用了

this就是调用函数的对象

.call

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

为什么要多写一个 undefined

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

箭头函数

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

立即执行函数

  1. !function () { /* code */ }();
  2. ~function () { /* code */ }();
  3. -function () { /* code */ }();
  4. +function () { /* code */ }();