JavaScript 函数

648 阅读17分钟

函数

简介

什么是函数

  1. 什么是函数? 函数是专门用于封装代码的, 函数是一段可以随时被反复执行的代码块

  2. 函数格式

    function /*函数名称*/(/*形参列表*/){
          /*被封装的代码*/;
    }
    
  3. 不使用函数的弊端

    1. 冗余代码太多
    2. 需求变更, 需要修改很多的代码
  4. 使用函数的好处

    1. 冗余代码变少了
    2. 需求变更, 需要修改的代码变少了

函数的定义步骤

  1. 书写函数的固定格式
  2. 给函数起一个有意义的名称
    1. 为了提升代码的阅读性
    2. 函数名称也是标识符的一种, 所以也需要遵守标识符的命名规则和规范
  3. 确定函数的形参列表, 看看使用函数的时候是否需要传入一些辅助的数据
  4. 将需要封装的代码拷贝到{}中
  5. 确定函数的返回值 可以通过return 数据; 的格式, 将函数中的计算结果返回给函数的调用者

函数的注意点

  1. 一个函数可以有形参也可以没有形参(零个或多个) 定义函数时函数()中的变量我们就称之为形参

    // 没有形参的函数
    function say() {
        console.log("hello world");
    }
    say(); // hello world
    
    // 有形参的函数
    function say(name) {
        console.log("hello " + name);
    }
    say("fhs"); // hello fhs
    
  2. 一个函数可以有返回值也可以没有返回值

    // 没有返回值的函数
    function say() {
        console.log("hello  world");
    }
    say();
    
    // 有返回值的函数
    function getSum(a, b) {
        return a + b;
    }
    let res = getSum(10 , 20);
    console.log(res); // 30
    
  3. 函数没有通过 return 明确返回值, 默认返回 undefined

    function say() {
        console.log("hello world");
        return;
    }
    let res = say();
    console.log(res); // undefined
    
  4. return的作用和break相似, 所以return后面不能编写任何语句 (永远执行不到) break作用立即结束switch语句或者循环语句 return作用立即结束当前所在函数

    function say() {
        console.log("hello world");
        return;
        console.log("return后面的代码");
    }
    say(); // hello world
    
  5. 调用函数时实参的个数和形参的个数可以不相同 调用函数时传入的数据我们就称之为实参

  6. JavaScript中的函数和数组一样, 都是引用数据类型 (对象类型) 既然函数是一种数据类型, 所以也可以保存到一个变量中 将来可以通过变量名称找到函数并执行函数

    let say = function () {
        console.log("hello world");
    }
    say(); // hello world
    

函数与参数

参数 arguments

  1. 因为 console.log(); 也是通过 () 来调用的, 所以 log 也是一个函数

  2. log 函数的特点 可以接收 1 个或多个参数

  3. 为什么 log 函数可以接收 1 个或多个参数 内部的实现原理就用到了 arguments

  4. arguments 的作用 保存所有传递给函数的实参

  5. 每个函数中都有一个叫做arguments的东西, arguments其实是一个伪数组

    function getSum() {
        let sum = 0;
        for (let i = 0; i < arguments.length; i++){
            let num = arguments[i];
            sum += num;
    	}
    	return sum;
    }
    let res = getSum(10, 20, 30, 40);
    console.log(res); // 100
    

    函数括号内每个参数都被放进了 arguments 对象(伪数组)里

    // 补充
    function test(a) {
    	console.log(arguments);
    }
    test(0, 1, 2, 3, 4, 5);
    // [Arguments] { '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5 }
    

扩展运算符

  1. 扩展运算符在等号左边, 将剩余的数据打包到一个新的数组中 注意点: 只能写在最后 let [a, ...b] = [1, 3, 5]; a = 1; b = [3, 5];

  2. 扩展运算符在等号右边, 将数组中的数据解开

    let arr1 = [1, 3, 5];
    let arr2 = [2, 4, 6];
    let arr = [...arr1, ...arr2];
    let arr = [1, 3, 5, 2, 4, 6];
    
  3. 扩展运算符在函数的形参列表中的作用 将传递给函数的多出来的所有实参打包到一个数组中 注意点: 和在等号左边一样, 也只能写在形参列表的最后

    function getSum (a, ...values) {
        console.log(a);
        console.log(values);
    }
    getSum(10, 20 , 30);
    // 10
    // 20, 30
    

函数形参默认值

ES6 之前可以通过逻辑运算符来给形参指定默认值 格式: 条件A || 条件B

  • 如果条件 A 成立, 那么就返回条件 A
  • 如果条件 A 不成立, 无论条件 B 是否成立, 都会返回条件B
 function getSum(a, b) {
     a = a || "211";
     b = b || "996";
     console.log(a, b);
 }
getSum(123, "abc"); // 123, "abc" (妈欸这里控制台又有引号, 纯字符串又没引号, 薛定谔的引号, 非常淦)

从 ES6 开始, 可以直接在形参后面通过 = 指定默认值 (和解构赋值的指定默认值呼应上了) 注意点: ES6 开始的默认值还可以从其它的函数中获取

function getSum(a = "211", b = getDefault()) {
    console.log(a, b);
}
getSum();
function getDefault() {
    return "996"
}

函数作为返回值

  • 将函数赋给其他变量
let say = function () {
    console.log("hello world");
}
let fn = say;
fn();
  • 将函数作为其他函数的参数
let say = function () {
    console.log("hello world");
}
function test(fn) { // 相当于 let fn = say;
    fn();
}
test(say);
  • 将函数作为其他函数的返回值
function test() {
    // 注意点: 在其它编程语言中函数是不可以嵌套定义的, 但是在JavaScript中函数是可以嵌套定义的
    let say = function () {
        console.log("hello world");
    }
    return say;
}
let fn = test(); // 相当于 let fn = say;
fn();

注意点: 在其它编程语言中函数是不可以嵌套定义的, 但是在JavaScript中函数是可以嵌套定义的

花哨的函数

匿名函数

  1. 什么是匿名函数? 匿名函数就是没有名称的函数
  2. 匿名函数的注意点 匿名函数不能够只定义不使用
  3. 匿名函数的应用场景
    1. 作为其他函数的参数
    2. 作为其他函数的返回值
    3. 作为一个立即执行的函数
  • 有名称的函数
function say() {
    console.log("hello fhs");
}
let say = function() {
    console.log("hello fhs");
}
  • 匿名函数
function() {
    console.log("hello fhs");
}
  • 匿名函数作为其他函数的参数
function test(fn) { // let fn = say;
    fn();
}
test(function () {
    console.log("hello world");
});
  • 匿名函数作为其他函数的返回值
function test() {
    return function () {
        console.log("hello fhs");
    };
}
let fn = test(); // let fn = say;
fn();
  • 匿名函数作为一个立即执行的函数 注意点: 如果想让匿名函数立即执行, 那么必须使用()将函数的定义包裹起来才可以
(function () {
    console.log("hello it666");
})();

箭头函数

  1. 什么是箭头函数?

    • 箭头函数是ES6中新增的一种定义函数的格式

    • 目的: 就是为了简化定义函数的代码, 给一套花里胡哨的关于 this 的用法

    • let arr = new Array();
      let arr = [];
      
  2. 在 ES6 之前如何定义函数

    function 函数名称(形参列表){
        /*需要封装的代码*/;
    }
    let 函数名称 = function(形参列表){
        /*需要封装的代码*/;
    }
    
  3. ES6 新增的函数定义方式

    let 函数名称 = (形参列表) =>{
        /*需要封装的代码*/;
    }
    
  4. 箭头函数的注意点:

    1. 在箭头函数中如果只有一个形参, 那么 () 可以省略

      // function say() {
      //     console.log("hello lnj");
      // }
      let say = () => {
          console.log("hello lnj");
      }
      say();
      
    2. 在箭头函数中如果 {} 中只有一句代码, 那么{}也可以省略

      // function say(name) {
      //     console.log("hello  " + name);
      // }
      // let say = (name) => {
      //     console.log("hello  " + name);
      // }
      // let say = name => {
      //     console.log("hello  " + name);
      // }
      let say = name => console.log("hello  " + name); // 花里胡哨
      say("ccc");
      
    3. 当箭头函数的函数体只有一个 return 语句时,可以省略 return 关键字和方法体的花括号

      var elements = [
       'Hydrogen',
        'Helium',
       'Lithium',
        'Beryllium'
      ];
      elements.map(element => element.length); // [8, 6, 7, 9]
      
    4. 如果箭头函数的函数体只有一句代码,就是返回一个对象,可以像下面这样写:

      // 用小括号包裹要返回的对象,不报错
      let getTempItem = id => ({ id: id, name: "Temp" });
      
      // 但绝不能这样写,会报错。
      // 因为对象的大括号会被解释为函数体的大括号
      let getTempItem = id => { id: id, name: "Temp" };
      
    5. 箭头函数和正统函数的 this 不同

      • 箭头函数中的 this, 是父作用域的 this, 而非调用者

      • 箭头函数的 this 无法变更

      • 对象不会成为箭头函数的 this

      let p = {
          name: "fhs",
          say: function () {
           console.log(this);
          },
       // 因为没有将箭头函数放到其它的函数中, 所以箭头函数属于全局作用域
          // 在 JS 中只有定义一个新的函数才会开启一个新的作用域 (害, 块呢???)
          hi: () => {
              console.log(this);
          }
      }
      p.say(); // {name: "fhs", say: ƒ}
      p.hi(); // Window 
      console.log(this); // Window
      
      • 函数成为箭头函数的 this
      function Person() {
          this.name = "fhs";
          this.say = function () {
           console.log(this);
          }
       // 因为将箭头函数放到其它的函数中, 所以箭头函数属于其它函数(当前的其它函数就是构造函数)
          // 既然箭头函数属于构造函数, 所以箭头函数中的 this 就是构造函数的 this
          this.hi = () =>{
              console.log(this);
          }
      }
      let p = new Person();
      p.say(); // Person
      p.hi(); // Person
      
      • 箭头函数的 this 无法变更
      function Person() {
          this.name = "fhs";
          this.say = function () {
              console.log(this);
          }
          this.hi = () =>{
           console.log(this);
          }
      }
      let p = new Person();
      p.say.call({name: "zs"}); // {name: "zs"}
      /*
      注意点:
      箭头函数中的 this 永远都只看它所属的作用域的 this
      无法通过 bind/call/apply 来修改
      */
      p.hi.call({name: "zs"}); // Person
      
      • 类又不同了, 暴躁
      class Person{
          constructor(name){
              this.name = name;
          }   
       say = function () {
              console.log(this);
       }
      hi = () =>{
          console.log(this);
      } 
      }
      
      let a = new Person("fhs");
      console.log(a.say()); // undefined
      console.log(a.hi()); // undefined
      
      • 这儿么写 this 也还是 undefined
      class Person{
          constructor(name){
              this.name = name;
              this.say = function(){
                  console.log(this);
              }
              this.hi = () =>{
                  console.log(this);
              } 
          }   
      }
      
      let a = new Person("fhs");
      console.log(a.say()); // undefined
      console.log(a.hi()); // undefined
      

这里有个挺重要的知识点: this 是 this, 作用域链是作用域链, 俩有关系但不是一回事儿

递归函数

  1. 什么是递归函数? recursion
    • 递归函数就是在函数中自己调用自己, 我们就称之为递归函数
    • 递归函数在一定程度上可以实现循环的功能
  2. 递归函数的注意点:
    • 每次调用递归函数都会开辟一块新的存储空间, 所以性能不是很好
    • 函数执行完毕后会回到函数调用的地方
// let pwd = -1;
// do{
//     pwd = prompt("请输入密码");
// }while (pwd !== "123456");
// alert("欢迎回来");

function login() {
    // 1.接收用户输入的密码
    let pwd = prompt("请输入密码");
    // 2.判断密码是否正确
    if(pwd !== "123456"){
        login();
    }
    // 3.输出欢迎回来
    alert("欢迎回来"); // 输错几次, 在输入正确密码后就会多弹相同次的 欢迎回来	
}
login();

作用域与作用域链

背景介绍

在JavaScript中定义变量有两种方式

  • ES6 之前: var 变量名称;
  • ES6 新增: let 变量名称;

var 与 let 区别

  • 是否能重复定义
    • 通过 var 定义变量,可以重复定义同名的变量,并且后定义的会覆盖先定义的
    • 如果通过 let 定义变量, "相同作用域内"不可以重复定义同名的变量
  • 是否能先使用后定义
    • 通过 var 定义变量, 可以先使用后定义(预解析)
    • 通过 let 定义变量, 不可以先使用再定义(不会预解析)
  • 是否能被 {} 限制作用域
    • 无论是 var 还是 let 定义在 {} 外面都是全局变量
    • 将 var 定义的变量放到一个单独的 {} 里面, 还是一个全局变量
    • 将 let 定义的变量放到一个单独的 {} 里面, 是一个局部变量

全局, 局部(函数), 块级作用域

  1. 在 JavaScript 中 {} 外面的作用域, 我们称之为全局作用域
  2. 在 JavaScript 中函数后面 {} 中的的作用域, 我们称之为"局部作用域", 或“函数作用域”
  3. 在 ES6 中只要 {} 没有和函数结合在一起, 就是"块级作用域" 块级作用域只能约束 let
  4. 块级作用域和局部作用域区别 (气死人的绕)
    1. 在块级作用域中通过 var 定义的变量是全局变量
    2. 在局部作用域中通过 var 定义的变量是局部变量
  5. 无论是在块级作用域还是在局部作用域, 省略变量前面的 let 或者 var 就会变成一个全局变量 (严格模式不给省略)
  • 块级作用域 (选择结构和循环结构都不是函数)
 {
     // 块级作用域
 }

if(false){
    // 块级作用域
}

switch () {
        // 块级作用域
}

while (false){
    // 块级作用域
}

for(;;){
    // 块级作用域
}

do{
    // 块级作用域
}while (false);
  • 函数作用域 (局部作用域)
function say() {
    // 局部作用域
}
  • var 的辨析
{
     // 块级作用域
     var num = 123; // 全局变量
}
console.log(num); // 123

function test() {
    var value = 666; // 局部变量
}
test();
console.log(value); // 报错
  • 块级作用域中的 var 与 let
{
    // var num = 678; // 全局变量
    // let num = 678; // 局部变量
    num = 678; // 全局变量, 严格模式下这个会报错, 引起舒适
}
console.log(num);
  • 局部(函数)作用域中的 var 与 let
function test() {
    // var num = 123; // 局部变量
    // let num = 123; // 局部变量
    num = 123; // 全局变量, 严格模式下这个会报错, 引起舒适
}
test();
console.log(num);

思考: let 相比 var 灵活度被大大削弱, 好用程度却大幅上升, 这就是规矩的好处

注意点:

  1. 在不同作用域范围内可以出现同名变量 (可以但极不推荐)
  2. 只要出现 let, 相同作用域内不能出现同名的变量, 即使另一个是 var 也不行

作用域链

全局作用域也称 0 级作用域

注意点: 初学者在研究"作用域链"的时候最好将 ES6 之前和 ES6 分开研究

ES6 之前的作用域链
  1. 需要明确:
    1. ES6 之前定义变量通过 var
    2. ES6 之前没有块级作用域, 只有全局作用域和局部作用域
    3. ES6 之前函数大括号外的都是全局作用域
    4. ES6 之前函数大括号中的都是局部作用域
  2. ES6之前作用域链
    1. 全局作用域我们又称之为 0 级作用域
    2. 定义函数开启的作用域就是 1级/ 2级/ 3级/...作用域
    3. JavaScript会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链 0 ---> 1 ----> 2 ----> 3 ----> 4
    4. 除0级作用域以外, 当前作用域级别等于上一级 +1
  3. 变量在作用域链查找规则
    1. 先在当前找, 找到就使用当前作用域找到的
    2. 如果当前作用域中没有找到, 就去上一级作用域中查找
    3. 以此类推直到0级为止, 如果 0 级作用域还没找到, 就报错
// 全局作用域 / 0级作用域
var num = 123;
function demo() {
    // 1级作用域
	var num = 456;
    function test() {
        // 2级作用域
		var num = 789;
        console.log(num);
    }
    test();
}
demo(); // 789
ES6 往后的作用域链
  1. 需要明确:
    1. ES6 定义变量通过 let
    2. ES6 除了全局作用域、局部作用域以外, 还新增了块级作用域
    3. ES6 虽然新增了块级作用域, 但是在块级作用域中通过 let 定义变量与函数作用域并无差异 (都是局部变量)
  2. ES6 作用域链
    1. 全局作用域我们又称之为 0 级作用域
    2. 定义函数或者代码块都会开启的作用域就是 1级/ 2级/ 3级/...作用域 (这里就是 ES6 之后的关键不同)
    3. JavaScript 会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链 0 ---> 1 ----> 2 ----> 3 ----> 4
    4. 除 0 级作用域以外, 当前作用域级别等于上一级 +1
  3. 变量在作用域链查找规则
    1. 先在当前找, 找到就使用当前作用域找到的
    2. 如果当前作用域中没有找到, 就去上一级作用域中查找
    3. 以此类推直到0级为止, 如果0级作用域还没找到, 就报错
// 全局作用域 / 0级作用域
let num = 123;
{
    // 1级作用域
	let num = 456;
    function test() {
        // 2级作用域
		// let num = 789;
        console.log(num);
    }
    test(); // 456
}

预解析, 方法

预解析

  1. 什么是预解析?
    • 浏览器在执行JS代码的时候会分成两部分操作:预解析以及逐行执行代码
    • 也就是说浏览器不会直接执行代码, 而是加工处理之后再执行,
    • 这个加工处理的过程, 我们就称之为预解析
  2. 预解析规则
    1. 将变量声明和函数声明提升到当前作用域最前面
    2. 将剩余代码按照书写顺序依次放到后面
  3. 注意点:
    • 通过 let 定义的变量不会被提升(不会被预解析)
变量的预解析
  • var 声明的变量的预解析
 // 预解析之前
console.log(num); //undefined
var num = 123;

// 预解析之后
var num;
console.log(num); //undefined
num = 123;
  • 通过 let 定义的变量不会被提升(不会被预解析)
函数的预解析
  • 正统函数的预解析
 // ES6之前定义函数的格式
say(); 
// ES6之前的这种定义函数的格式, 是会被预解析的, 所以可以提前调用
function say() {
    console.log("hello 996");
}

// 预解析之后的代码
function say() {
    console.log("hello 996");
}
say();

/*
在其它语言中, 函数的声明是:
function say()

在 Javascript 中, 函数的声明是
function say() {
    console.log("hello 996");
}
*/
  • 将函数赋给 var 变量的方式不预解析 (let 更不会啦)
console.log(say); // undefined
say(); // say is not a function
var say = function() {
    console.log("hello itzb");
}

// 预解析之后的代码
console.log(say); // undefined
var say;
say(); // say is not a function
say = function() {
    console.log("hello itzb");
}
  • 箭头函数也不预解析 (都是报错, 这个和 var 报的还不一样, 魔鬼在细节中)
say(); // say is not defined
let say = () => {
    console.log("hello itzb");
}
狗题目

注意点: 浏览器执行的是预解析之后的代码

  • 预解析规则很简单, 搞顺顺
var num = 123;
fun(); // undefined
function fun() {
    console.log(num);
    var num = 666;
}

/*
预解析之后:
var num;
function fun() {
var num;
console.log(num);
num = 666;
}
num = 123;
fun(); // undefined
*/
var a = 666;
test();
function test() {
    var b = 777;
    console.log(a); // 作用域链的就近原则
    console.log(b);
    console.log(c); // 虽然这里会报错, 但前面该输出的也会输出	
    var a = 888;
    let c = 999;
}
/*
预解析之后:
var a;
function test() {
var b;
var a;
b = 777;
console.log(a); // undefined
console.log(b); // 777
console.log(c); // 报错
a = 888;
let c = 999;
}
a = 666;
test();
*/
  • 高级浏览器不会对 {} 中定义的函数进行提升
 if(true){
     function demo() {
         console.log("1");
     }
 }else{
     function demo() {
         console.log("2");
     }
 }
demo(); // 1

/*
纯逻辑: 预解析, 函数提升, 后写覆盖先写, 于是输出 2

实际中:
高级浏览器不会对 {} 中定义的函数进行提升,
于是顺序执行, 输出 1
*/
  • 如果同级作用域 var 变量名与函数名同名, 函数的优先级高于变量 (淦! 居然没一以贯之用‘覆盖’的逻辑)
  • 一定要记住, 在企业开发中千万不要让变量名称和函数名称重名
console.log(value); // 输出函数的定义
var value = 123;
function value() {
    console.log("fn value");
}
console.log(value); // 123

/*
预解析之后
function value() {
    console.log("fn value");
}
console.log(value);
var value;
value = 123;
console.log(value);
*/

方法

创建默认对象
  1. JavaScript 中提供了一个默认的类 Object, 我们可以通过这个类来创建对象
  2. 由于我们是使用系统默认的类创建的对象, 所以系统不知道我们想要什么属性和行为, 所以我们必须手动的添加我们想要的属性和行为
  3. 如何给一个对象添加属性 对象名称.属性名称 = 值;
  4. 如何给一个对象添加行为 对象名称.行为名称 = 函数;

创建对象的三种方式

  1. new 一个, 往这个对象里加属性和方法

    let obj = new Object();
    obj.name = "fhs";
    obj.age = 23;
    obj.say = function () {
        console.log("hello world");
    }
    console.log(obj.name);
    console.log(obj.age);
    obj.say();
    
  2. 把个不完整的对象(甚至空对象也可)赋给变量

    let obj = {}; // let obj = new Object(); 的简写
    obj.name = "fhs";
    obj.age = 23;
    obj.say = function () {
        console.log("hello world");
    }
    console.log(obj.name);
    console.log(obj.age);
    obj.say();
    
  3. 直接把完整的对象赋给变量 (格式上有区别的哈, 这里是键值对的键和值以冒号隔开, 且键值对间以逗号隔开)

    let obj = {
        name: "fhs",
        age: 23,
        say: function () {
            console.log("hello world");
        }
    };
    console.log(obj.name);
    console.log(obj.age);
    obj.say();
    

注意点:

对象内方法只应该是函数赋给变量的形式, 以正统函数形式存在于对象内的函数不是对象的方法

函数与方法的区别

对象的行为称为方法而非函数

  1. 什么是函数? 函数就是没有和其它的类显示的绑定在一起的, 我们就称之为函数

  2. 什么是方法? 方法就是显示的和其它的类绑定在一起的, 我们就称之为方法

  3. 函数和方法的区别

    1. 函数可以直接调用, 但是方法不能直接调用, 只能通过对象来调用
    2. 函数内部的 this 输出的是 window, 方法内部的this输出的是当前调用的那个对象
  4. 无论是函数还是方法, 内部都有一个叫做 this 的东西

    this 是什么? 谁调用了当前的函数或者方法, 当前的 this 就是谁

工厂函数, 构造函数

我悟了, js 中构造函数的名字就是 js 的所谓类名

工厂函数

什么是工厂函数? 工厂函数就是专门用于创建对象的函数, 我们就称之为工厂函数

作用: 降低代码冗余度

/*
let obj = {
	name: "zs",
	age: 23,
    say: function () {
        console.log("hello world");
	}
};
let obj = {
	name: "xhx",
	age: 22,
    say: function () {
        console.log("hello world");
    }
};
*/
function createPerson(myName, myAge) {
    let obj = new Object();
    obj.name = myName;
    obj.age = myAge;
    obj.say = function () {
        console.log("hello world");
    }
    return obj;
}
let obj1 = createPerson("zs", 23);
let obj2 = createPerson("xhx", 22);
console.log(obj1);
console.log(obj2);

构造函数

朴素版构造函数
  1. 什么是构造函数
    • 构造函数和工厂函数一样, 都专门用于创建对象
    • 构造函数本质上是工厂函数的简写 (还限制了创建方式, 更专业)
  2. 构造函数和工厂函数的区别
    • 构造函数的函数名称首字母必须大写
    • 构造函数只能够通过 new 来调用
function Person(myName, myAge) {
    // let obj = new Object();  // 系统自动添加的
    // let this = obj; // 系统自动添加的
    this.name = myName;
    this.age = myAge;
    this.say = function () {
        console.log("hello world");
    }
    // return this; // 系统自动添加的
}
let obj1 = new Person("zs", 23);
let obj2 = new Person("xhx", 22);
console.log(obj1);
console.log(obj2);

当我们 new Person("zs", 23); 系统做了什么?

  1. 会在构造函数中自动创建一个对象
  2. 会自动将刚才创建的对象赋值给 this
  3. 会在构造函数的最后自动添加 return this;
  4. 注意到, 如果手动补齐构造函数中 js 自动为我们添加的部分, 那就成为了工厂函数
优化构造函数
为什么优化
  • 朴素版构造函数两个对象中的 say 方法的实现都是一样的, 但是保存到了不同的存储空间中, 这样性能不好
  • 要优化以提升性能
第一版优化

当前这种方式存在的弊端

  1. 阅读性降低了
  2. 污染了全局的命名空间
function mySay() {
    console.log("hello world");
}
function Person(myName, myAge) {
    this.name = myName;
    this.age = myAge;
    this.say = mySay;
}
let obj1 = new Person("zs", 23);
let obj2 = new Person("xhx", 22);
// 通过三等来判断两个函数名称, 表示判断两个函数是否都存储在同一块内存中
console.log(obj1.say === obj2.say); // true
第二版优化

fns 是 function name space 的缩写, hhh 这个相比第一版并没有进步很多嘛

// function mySay() {
//     console.log("hello world");
// }
let fns = {
    mySay: function () {
        console.log("hello world");
    }
}
function Person(myName, myAge) {
    this.name = myName;
    this.age = myAge;
    this.say = fns.mySay;
}
let obj1 = new Person("zs", 23);
let obj2 = new Person("xhx", 22);
console.log(obj1.say === obj2.say); // true
三版优化 (原型入门)

prototype 是对象, 在这里 prototype 是构造函数这个对象的对象

用原型做到了真正不污染, 但可读性方面存在一定门槛

我在 chrome 尝试了一下, 构造函数本质和普通函数还真是一样的

// let fns = {
//     mySay: function () {
//         console.log("hello world");
//     }
// }
function Person(myName, myAge) {
    this.name = myName;
    this.age = myAge;
}
Person.prototype = { // 用 Person.prototype.say = function (){} 也能行, 亲测能行
    say: function () {
        console.log("hello world");
    }
}
let obj1 = new Person("zs", 23);
let obj2 = new Person("xhx", 22);
console.log(obj1.say === obj2.say); // true

补充: 第十行这种被称为自定义原型对象, 第十行注释部分称为给原型对象动态添加方法