10.4.JS-函数,作用域

186 阅读7分钟

1. 函数

1.1. 定义

  • 函数声明 function theFirstName(){ }
  • 函数表达式
// 命名函数表达式
let test = function abc(){
  console.log('a')
}
// 匿名函数表达式 ==> 函数表达式
let demo = function(){}
console.log(test.name); // abc
console.log(demo.name); //demo

1.2. 组成形式

  • 函数名称
// 命名函数表达式
let test = function abc(){
  console.log('a')
}
// 匿名函数表达式 ==> 函数表达式
let demo = function(){}
console.log(test.name); // abc
console.log(demo.name); //demo
  • 参数
    • 形参
    • 实参
// 形式参数 -- 形参
function sum(a) {
  // arguments-- [11, 2, 3] 实参列表
}
// 实际参数 -- 实参
sum(11, 2, 3)
* 判读
// 形式参数 -- 形参
function sum(a,b) {
  console.log(sum.length); // 形参长度 2
  console.log(arguments.length); // 实参参长度 3
}
// 实际参数 -- 实参
sum(11, 2, 3)
  • 返回值
    • return 终止, 有值返回值

1.3. 函数使用

  • 高内聚,低耦合
  • 函数复用
function test(){
  document.write('a');
  document.write('b');
  document.write('c');
}
if(1>0){
  test();
}
if(2>0){
  test();
}
if(3>0){
  test();
}
  • 形参和实参不一致好处:求取无穷数相加
function sum(){
  var result = 0;
  for (let index = 0; index < arguments.length; index++) {
    result += arguments[index]
  }
  console.log(result);
}
sum(2,3,4,555,5,222,6,111,7)
  • 映射
// 映射机制,不论实参值修改,还是实参列表修改值,都会改变相应的值
function sum(a,b){
  var a = 2;
  // arguments[0] = 10;
  console.log(a);
}
sum(1,2)
  • 实参列表arguments长度固定

1.4. 函数练习题

  1. 函数:告知你所选定的小动物的叫声
function scream(animal) {
  switch (animal) {
    case "cat":
      console.log('miao!');
      return;
    case "dog":
      console.log('wang!');
      return;
    case "fish":
      console.log('o~o~o!');
      return;
  }
}
scream('cat');
  1. 一组函数:输入数字,逆转并输出汉字形式
// 逆转: reverse 汉字: transfer
function reverse() {
  var num = window.prompt('input');
  var str = "";
  for (var i = num.length - 1; i >= 0; i--) {
    // 原理:类型转换 number + string = String(number) + string
    str += transfer(num[i]);
  }
  document.write(str);
}
function transfer(target) {
  switch (target) {
    case "1":
      return "壹";
    case "2":
      return "俩";
    case "3":
      return "仨";
    default:
      return ">仨"
  }
}
reverse();
  1. 函数实现n的阶乘
// // 规律: n * (n - 1)!
function mul(n){
  // // n的阶乘 方法1: for循环赋值运算
  // let num = 1; 
  // for (let index = 0; index < n; index++) {
  //   num *= index
  // }

  // // n的阶乘 方法2: 递归
  if(n == 1){
    return 1;
  }
  return n * mul(n - 1);
}
console.log(mul(1));
// mul(5);
// mul(5); ==> 5 * mul(4);
// mul(4); ==> 4 * mul(3);
// mul(3); ==> 3 * mul(1);
// mul(2); ==> 2 * 1
  • 递归
    • 方法: 找规律, 找出口
    • 递归特别慢 空间复杂度巨高
    • 第n位等于什么, 有什么规律, 如: 以1结束, 以n开始, *=, += ...
  1. 函数实现斐波那契数列
//  斐波那契额数列
// 找规律
// 1 1 2 3 5 8
// 8 = 5 + 3 
// n = (n - 1) + (n - 2)
// fbnq(n) = fbnq(n - 1) + fbnq(n - 2)
// 找出口
// 第一个1和第二个1,终止
function fbnq(n) {
  if (n == 1 || n == 2) {
    return 1;
  }
  return fbnq(n - 2) + fbnq(n - 1);
}
console.log(fbnq(5));
// fbnq(5)
// fbnq(5) ==> fbnq(4) + fbnq(3)
// fbnq(4) ==> fbnq(3) + fbnq(2)
// fbnq(3) ==> fbnq(2) + fbnq(1)
// fbnq(2) = 1
// fbnq(1) = 1

2. 作用域精解

  • 运行上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
  • 查找变量:从作用域链的顶端依次向下查找。
  • 作用域定义:变量(变量作用域又称上下文)和函数生效(能被访问)的区域
  • 全局、局部变量
  • 作用域的访问顺序

2.1. js运行顺序

  1. 语法分析(判断语法中有无汉字符号..., 为低级错误,一旦出错,代码不执行并报错)
  2. 预编译(定义变量声明,函数声明)
  3. 解释执行(执行代码阶段)

2.2. 预编译阶段

  1. 简单说法:
    • 函数声明整体提升, 变量 声明提升
  2. 预编译前奏:
  • imply global 暗示全局变量: 即任何变量, 如果变量未经声明就赋值,此变量就为全局变量所有。
    • eg: a = 123;
    • eg: var a = b = 123; 在全局环境或局部环境下,b都为全局变量,未声明变量都属于window;
  • 一切声明的全局变量, 全是 window 属性

  • eg: var a = 123; ===> window.a = 123; // 全局访问a 就是访问window.a
  • window 就是全局的域
  • 预编译发生在函数执行的前一刻
  1. 预编译步骤
    1. 创建AO对象
    2. 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
    3. 将实参值和形参统一
    4. 在函数体里面找函数声明,值赋予函数体
  2. 预编译+函数执行实例
function fn(a) {
  console.log(a); // ƒ a() { }
  var a = 123;
  console.log(a); // 123
  function a() { };
  console.log(a); // 123
  var b = function () { };
  console.log(b); // ƒ () { }
  function d() { }
}
fn(1);
// 预编译发生在函数执行的前一刻
// 1. 创建AO对象(Activation Object)(执行期上下文)
// 2. AO对象变化过程
// 2.1. 变量和形参名作为属性名
// AO{
//   a: undefined,
//   b: undefined
// }
// 2.2. 实参值和形参统一
// AO{
//   a: 1,
//   b: undefined
// }
// 2.3. 函数体中找函数声明,函数表达式为赋值操作,在代码执行阶段完成
// AO{
//   a: function a(){},
//   b: undefined,
//   d: function d(){}
// }
// 2.4. 预编译阶段完成
// 3. 代码逐行执行,作用域为AO
// 3.1. 第一行 console.log(a); 从作用域可以找到 ƒ a() { };
// 3.2. 第二行 var a = 123; var阶段已在预编译阶段完成,只需进行赋值操作,此时a变为123
// 3.3. 第三行 console.log(a); 此时a=123,所以打印值为"1";
// 3.4. 第四行 function a() { }; 此代码为函数声明,已在预编译阶段完成,略过。
// 3.5. 第五行 console.log(a); 此时a=123,所以打印值为"1";
// 3.6. 第六行 var b = function () { }; 此代码为函数表达式,var已在预编译阶段完成,对b进行赋值操作,此时b为function () { }
// 3.7. 第七行 console.log(b); 此时b为function () { },打印 function () { }
// 3.8. 第八行 function d() { }; 此代码为函数声明,已在预编译阶段完成,略过。
  1. 函数执行
    1. AO对象就是作用域
    2. 预编译在函数和全局一样使用
    3. 全局GO对象(Global Object) 局部AO对象(Activation Object)
    4. GO对象就是window
    5. 局部环境下的未声明变量暗示在GO对象上
    6. 只有表达式才能被执行符号执行,正负号可以隐式转换函数声明,
    GO {
        变量a: ...
    }
    AO {
        变量a: ...
    }
    

3. 作用域链

  • 变量寻找:根据已层嵌套关系,使用变量,自己所在作用域有此变量,使用自己的,没有的话向上寻找, 找到了使用,找不到报错;
  • 作用域链 GO和AO的关系
  • 从作用域链顶端的AO到底端GO寻找变量
  • 预编译生成GO对象 --> 代码执行 --> 链[GO]在GO里寻找变量 --> 函数执行前预编译生成AO对象 --> 函数执行 --> 链[AO,GO],从AO里开始找变量或函数,找不到在找GO里的变量或函数
  • [[scope]]-上下文集合-作用域链-[……AO,GO]
function a(){
  function b(){
    function c(){
      
    }
    c();
  }
  b();
}
a();
a defined a.[[scope]] --> 0 : GO
a doing   a.[[scope]] --> 0 : aAO
                          1 : GO
                          
b defined b.[[scope]] --> 0 : aAO
                          1 : GO                          
b doing   b.[[scope]] --> 0 : bAo
                          1 : aAO
                          2 : GO
c defined c.[[scope]] --> 0 : bAO
                          1 : aAO
                          2 : GO
c doing   c.[[scope]] --> 0 : cAO
                          1 : bAO
                          2 : aAO
                          3 : GO
  • 在全局环境生存着其中被定义的变量,在函数环境生存着其中被定义的变量
  • 函数销毁前,作用域链中的AO不变,同一个AO不会改变;

4. 闭包

  • 闭包:但凡内部的函数被保存到外部
  • 闭包会导致多个执行函数共用一个公有变量,原有作用域链不释放
  • 立即执行函数:
    • (function(){})()
    • (function(){}())
  • 闭包中返回的函数,其中的作用域链不销毁,共用一个AO;

4.1. 闭包的作用

  • 实现公有变量: 函数累加器
function add(){
  var count = 0;
  function demo(){
    count++;
    console.log(count);
  }
  return demo;
}
var counter = add();
counter(); // 1
counter(); // 2
counter(); // 3
counter(); // 4
  • 做缓存(存储结构): eater
function test(){
  var food = 'apple';
  var obj = {
    eatFood: function(){
      if(food != ''){
        console.log("I am eating "+ food);
      }else{
        console.log("There is nothing! empty");
      }
    },
    pushFood: function(myFood){
      food = myFood;
    }
  }
  return obj;
}
var person = test();
person.eatFood(); // I am eating apple
person.eatFood(); // I am eating apple
person.pushFood('banana');
person.eatFood(); // I am eating banana
  • 封装,属性私有化: Person();

  • 函数:求字符串的字节长度:charCodeAt

function retByteslen(target) {
  var count = target.length;
  for (var i = 0; i < target.length; i++) {
    if (target.charCodeAt(i) > 255) {
      count++;
    }
  }
  console.log(count);
}
retByteslen("世界hello,world"); // 15