Day.18 「从函式物件认识 作用域 与 提升!」 —— JavaScript 函式(Function) & 作用域(Scope) & 提升(Hoisting)

「从函式物件认识 作用域 与 提升!」 —— JavaScript 函式(Function) & 作用域(Scope) & 提升(Hoisting)

函数也是个物件型别,可以封装一些功能(程序码),在需要使用的时候执行功能(程序码)。

例如我们需要把数字相加,在没有函式的时候,需要用的时候就要重复打程序码:

let x = 1;
let y = 1;
let result = x + y;
console.log( result );  // 2
x = 3;
y = 2;
result = x + y;
console.log( result );  // 5

而我们学程序,讲求的就是效率,所以自己定义一个函式封装相加功能:

function sum (x, y) {
  let result = x + y;
  console.log( result );
}
sum(1, 1);  // 2
sum(3, 2);  // 5

认识函式结构

函式结构

从上面的函式结构我们可以看到

  • 函式名称:这其实不一定会有,但我们先介绍基础的方法,主要用处是在使用函式时需要使用名字来呼唤。
  • 参数:这不一定要传,主要是用来传递资料给函式内对应的变数,给功能程序码运算。
  • 功能程序码:就是使用函式後,接收参数并进行功能运作的部分。

如何建创与调用函式

上面已经使用了一个建创方法

函式陈述式

function 函式名 (参数) {
  /* 运作程序码功能 */
}
函式名 (参数);

函式表达式,匿名函式

上面有说函式名称不一定会有,匿名函式就是没有自己的名称,而是直接赋值给变数使用,又称函式表达式

const 变数名 = function (参数) {
  /* 运作程序码功能 */
}
变数名 (参数);

new Function

跟物件与阵列一样,使用 new Function 新增函式。
需注意:

  • 大小写不能拼错
  • 参数与运作程序码皆需要用字串型别
const 变数名 = new Function ("参数", "运作程序码");

但这个方法并不好用,而且效率低,因为程序码会先把字串转成它看得懂的程序码,才进行运算。

返回值(return)

我们会运用功能了,但不想一直使用 console.log 印出来,想要让函式结果继续在程序码里面运算。
可以利用 return 把运算结果返回并终止函式。

function sum (x, y) {
  const res = x + y;
  return res;  // 使用 result 返回值,并终止函式
  console.log("测试有没有执行")  // return 後的程序码不会执行
}
const result = sum(1, 1);  // 返回的值 赋予到 result 变数上
console.log( result );  // 2

立即执行函式,IIFE(Immediately Invoked Function Expression)

有些时候,我们只想在一开始使用一次函式,後续不会调用函式,就像进入超商会听到欢迎光临一样。
此时不想占用变数空间,就可以使用匿名函式立即执行,需注意:

  • 匿名函式需要使用 () 包起来,不然 JavaScript 会认为你忘记为函式命名而报错。
  • 後面需像正常调用函式一样,也可以和一般函式一样带入参数。
( function(text) {
  console.log(text)
} )("欢迎光临")

( function(text) {
  console.log(text)
}("欢迎光临") )

都可以使用,看个人(团体)习惯

作用域(Scope)与 提升(Hoisting)

在我们宣告变数那一篇有介绍,全局作用域 与 函数作用域。

全局作用域

  • 直接编写 script 标签中 JavaScript 程序码,都会在全局作用域。

    var a = 1;
    console.log(a);        // 1
    
  • 全局作用域在网页打开时创建,在关闭网页时清除。

  • 在全局作用域有一个叫 window物件,代表浏览器的视窗,它由浏览器新增并供我们使用其内建的方法。

    console.log(typeof window); // "object"
    
  • 在全局作用域中

    • 宣告变数都会变成 window 的属性保存

      var a = 1;
      console.log(window.a); // 1
      
    • 新增的函式会变成 window 的方法保存

      function sum (x,y) {
        return x + y;
      }
      console.log( window.sum ); 
      /*
         function sum (x,y) {
           return x + y;
         }
      */
      

变数提升

我们先从全局作用域看变数,会发现不管有没有使用 var 宣告都会取得到值

var a = 1;
x = 2;

console.log("a = "+ a);  // "a = 1"
console.log("x = "+ x);  // "x = 2"

这时候我们把变数往後移,因为 Javascript 是由上向下一行一行执行,会发现有没有宣告的差别。

console.log("a = "+ a);  // "a = undefined"
console.log("x = "+ x);  // 报错 x is not defined 

var a = 1;
x = 2;

有先进行 var 声明宣告的变数,在执行程序码之前会先提升至全局作用域的开头,才执行程序码,相当於:

var a  // var 声明宣告会自动提升

console.log("a = "+ a);  // "a = undefined"
console.log("x = "+ x);  // 报错 x is not defined 

a = 1;
x = 2;

函数提升

一样的,函数也会在程序执行前,会先自行提升,但函式有两个新增方法,一个是函式陈述式,一个是函式表达式。
与上面一样正常使用的话是不会报错,但如果反过来?

fun()  // "我是一个 fun 函式"
fun2() // 报错 fun2 is not a function

function fun () {
  console.log("我是一个 fun 函式")
}
var fun2 = function () {
  console.log("我是一个 fun2 函式")
}

我想聪明的你,应该马上就猜想到,没错!就是宣告变数提前,但变数的函式要到後面才会赋值,undefined当函式使用当然会报错!

而使用函式陈述式,会把整个函式提升,在执行程序码!

function fun () {    // 函式陈述式把整个函式提升
  console.log("我是一个 fun 函式")
}
var fun2;  // var 自动提升宣告 undefined

fun()  // "我是一个 fun 函式"
fun2() // 报错 fun2 is not a function

fun2 = function () {  // 到这里才赋值
  console.log("我是一个 fun2 函式")
}

函式作用域

  • 编写在函式里面的 Javascript 程序码,这个区域就称函式作用域

  • 函式作用域只在函式被调用时创建,在函式执行结束时清除。

  • 每调用一次函式会创建一个新的函式作用域,函式作用域彼此都是独立运作的

  • 函式作用域可以往外查找全局作用域的变数,全域作用域不能往内查找函式作用域的变数。

    var a = 1;
    
    function echo () {
      var b = 2;
      console.log("a = "+ a);  // "a = 1",函式内没有 a 变数,会往全域找 a 变数
    }
    
    echo();
    console.log("b = "+ b);  // 报错 b is not defined,全域找不到 b 变数
    
  • 函式作用域中操作变数时,会先在自己的作用域寻找,没有则会往上一层去寻找变数,一路查到全局作用域,直到找到为止,如果找不到就会报错。

    var a = "我是全局的 a";
    var c = "我是全局的 c";
    function echo () {
      var a = "我是 echo 内的 a";
      var b = "我是 echo 内的 b";
    
      console.log("a = "+ a);  // "a = 我是 echo 内的 a",函式内有 a 变数,优先使用
      console.log("a = "+ window.a);  // "a = 我是全局的 a",利用 window 直接查找全局变数
    
      function inside () {
        console.log("b = "+b); // "b = 我是 echo 内的 b",
          // 函式内没有 b 变数,往上一层 echo 内查找到 b 变数
        console.log("c = "+c); // "c = 我是全局的 c",
          // 函式内没有 c 变数,往上一层 echo 内也查找不到变数,再往上一层查找。
      }
    }
    
    echo();
    console.log("a = "+ a);  // "a = 我是全局的 a",不会查找函式内的值
    
  • 定义参数,其实就等於在函式内宣告变数,所以就不会往外查找变数。

    var e = 1;
    
    function echo (e) {
      console.log(e);
    }
    
    echo();    // undefined,因为 var e; 是 undefined
    echo(10);  // 10
    

总结

这边已经先了解了基础的函式,但函式的精随 this 还没介绍,介绍函式的同时也了解了宣告变数 var 的作用域与提升了~但宣告变数还有 letconst 的特性还没有介绍!这等到後面要认识函式的精随的时候,在一起解说~明天将先继续挖深物件型别,加油!我们快脱离基础篇了。

参考资料


<<:  C# 入门之函数(补充)

>>:  IT铁人DAY 12-Prototype 原型模式

Day23 - ListView

上次学的Spinner需要点选下拉钮,才显示项目 而ListView则是直接把所有项目列出来 两者的...

D3JsDay02 学学D3JS 技能提高SSS—为什麽D3

图片来源:unsplash 关於资料视觉化的工具一般使用者最先接触的可能是Microsoft Ex...

如何把动态产生的数据塞入预定的公式中

笔者在做UART传输专案时遇到需要将收到的资料撷取後面不同长度的资料,再填入不同的公式中 预设的作业...

[C#] LeetCode 3. Longest Substring Without Repeating Characters

Given a string s, find the length of the longest s...

Vue.js 从零开始:v-model

表单类型是网页很常见的呈现方式,表单元素有文字框<input>、<textarea...