跳到主要内容

函数

函数

  • 在函数中声明的变量只在该函数内部可见。
  • 函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。
  • 只有在没有局部变量的情况下才会使用外部变量。
  • 函数的参数也是局部变量。作为参数传递给函数的值,会被复制到函数的局部变量。
  • 在 JavaScript 中,每次函数在没带个别参数的情况下被调用,默认参数会被计算出来。
    function showMessage(from, text = anotherFunction()) {
    // anotherFunction() 仅在没有给定 text 时执行
    // 其运行结果将成为 text 的值
    // 如果传递了参数 text,那么 anotherFunction() 就不会被调用。
    }
  • 空值的 return 或没有 return 的函数返回值为 undefined

函数表达式

  • 用 函数声明 的方式创建函数

    function sum(a, b) {
    return a + b;
    }
  • 用 函数表达式 的方式创建函数

    let sum = function(a, b) {
    return a + b;
    };
  • JavaScript 引擎 对于 函数声明 与 函数表达式 创建函数的时机不同。在函数声明被定义之前,它就可以被调用。例如,一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。这是内部算法的缘故。当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。

  • 函数表达式 是在代码执行到它时才会被创建,并且仅从那一刻起可用。一旦代码执行到赋值表达式 let sum = function… 的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。

  • 严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。

    let age = 16;

    if (age < 18) {
    welcome(); // \ (运行)
    // |
    function welcome() { // |
    alert("Hello!"); // | 函数声明在声明它的代码块内任意位置都可用
    } // |
    // |
    welcome(); // / (运行)

    } else {

    function welcome() {
    alert("Greetings!");
    }
    }

    // 在这里,我们在花括号外部调用函数,我们看不到它们内部的函数声明。
    welcome(); // Error: welcome is not defined
    // 使用函数表达式
    let age = prompt("What is your age?", 18);

    let welcome;

    if (age < 18) {

    welcome = function() {
    alert("Hello!");
    };

    } else {

    welcome = function() {
    alert("Greetings!");
    };

    }

    welcome(); // 现在可以了
    // 使用问号运算符 ? 来进一步对代码进行简化
    let age = prompt("What is your age?", 18);

    let welcome = (age < 18) ?
    function() { alert("Hello!"); } :
    function() { alert("Greetings!"); };

    welcome(); // 现在可以了
  • 无论函数是如何创建的,函数都是一个值。可以像使用其他类型的值一样使用它。

    function sayHi() {   // 声明创建了函数,并把它放入到变量 sayHi
    alert( "Hello" );
    }

    let func = sayHi; // 将 sayHi 复制到了变量 func

    func(); // 运行复制的值(正常运行)输出 Hello
    sayHi(); // 运行输出 Hello
  • 在大多数情况下,当我们需要声明一个函数时,最好使用函数声明,因为函数在被声明之前也是可见的。这使我们在代码组织方面更具灵活性,通常也会使得代码可读性更高。仅当函数声明不适合对应的任务时,才应使用函数表达式。

回调函数

主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。如下例子中,showOk 是回答 “yes” 的回调,showCancel 是回答 “no” 的回调。

function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}

function showOk() {
alert( "You agreed." );
}

function showCancel() {
alert( "You canceled the execution." );
}

// 用法:函数 showOk 和 showCancel 被作为参数传入到 ask
ask("Do you agree?", showOk, showCancel);
  • 可以使用函数表达式来编写一个等价的、更简洁的函数。直接在 ask(...) 调用内声明两个匿名函数,这样的函数在 ask 外无法访问(因为没有对它们分配变量),这正符合 JavaScript 语言的思想。

    function ask(question, yes, no) {
    if (confirm(question)) yes()
    else no();
    }

    ask(
    "Do you agree?",
    function() { alert("You agreed."); },
    function() { alert("You canceled the execution."); }
    );
  • 如何将js回调函数中的数据返回给最外层函数?

日常开发使用场景之一:

const brandInfo = {
id: 1,
name: '选择品牌',
children: [],
expanded: true,
};
const normInfo = {
id: 2,
name: '选择规格',
children: [],
expanded: true,
}
const chosenBrand = null;

function created() {
if (id) getCategoryInfo(id, (data) => {
brandInfo.children = data;
brandInfo.children = brandInfo.children.map(ele => {
ele.imgUrl = process.env.VUE_APP_IMG + ele.imgUrl;
return ele;
});
});
}

async getCategoryInfo(id, cb) {
try {
const categoryResult = await queryMeterCategory(id);
if (!categoryResult.data) throw categoryResult.msg;
cb(categoryResult.data);
} catch (error) {
this.$nutToast.text(error);
}
},

chooseBrandSku(chosenSku) {
chosenBrand = chosenSku;
brandInfo.expanded = false;
getCategoryInfo(chosenSku.id, (data) => {
normInfo.children = data;
normInfo.children = normInfo.children.map(ele => {
return { ...ele, checked: false };
})
});
},

箭头函数

创建函数还有另外一种非常简单的语法,并且这种方法通常比函数表达式更好,即箭头函数:

// 创建了一个函数 func,它接受参数 arg1..argN,然后使用参数对右侧的 expression 求值并返回其结果
let func = (arg1, arg2, ..., argN) => expression;

// 上面是下面这段代码的更短的版本
let func = function(arg1, arg2, ..., argN) {
return expression;
};

// 如果没有参数,括号则是空的(但括号必须保留)
let sayHi = () => alert("Hello!");
  • 箭头函数可以像函数表达式一样使用。

    let age = prompt("What is your age?", 18);

    let welcome = (age < 18) ?
    () => alert('Hello!') :
    () => alert("Greetings!");

    welcome();
  • 多行的箭头函数: 用花括号括起来,需要包含 return 才能返回值

    let sum = (a, b) => {  // 花括号表示开始一个多行函数
    let result = a + b;
    return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”
    };

    alert( sum(1, 2) ); // 3
  • 箭头函数没有自己的 this。当我们并不想要一个独立的 this,反而想从外部上下文中获取时,它很有用。

构造函数

构造函数(或简称构造器)在技术上是常规函数。不过有两个约定:

  • 它们的命名以大写字母开头。
  • 它们只能由 new 操作符来执行。

new User(...) 做的事情可以简单理解为:

function User(name) {
// this = {};(隐式创建)

// 添加属性到 this
this.name = name;
this.isAdmin = false;

// return this;(隐式返回)
}
function User(name) {
this.name = name;
// 添加方法到 this
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}

let john = new User("John");

john.sayHi(); // My name is: John

/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
  • 构造器的主要目的: 创建多个类似的对象。
  • 从技术上讲,任何函数(除了箭头函数,它没有自己的 this)都可以用作构造器。
  • 如果没有参数,可以省略括号,但不建议这样做
    let user = new User; // <-- 没有参数
    // 等同于
    let user = new User();

new function() { … }

可以用 new function() { … } 封装构建单个对象的代码,而无需将来重用。这个构造函数不能被再次调用,因为它不保存在任何地方,只是被创建和调用。如果我们有许多行用于创建单个复杂对象的代码,就可以使用 new function() { … },例如:

let user = new function() {
this.name = "John";
this.isAdmin = false;

// ……用于用户创建的其他代码
// 也许是复杂的逻辑和语句
// 局部变量等
};

new.target

在一个函数内部,我们可以使用 new.target 属性来检查它是否被使用 new 进行调用了。对于常规调用,它为 undefined,对于使用 new 的调用,则等于该函数。

如下方式有时被用在库中以使语法更加灵活。这样人们在调用函数时,无论是否使用了 new,程序都能工作。

构造器的return

通常,构造器没有 return 语句。如果有,则:

  • 如果 return 返回的是一个对象,则返回这个对象,而不是 this
  • 如果 return 返回的是一个原始类型,则忽略。

pure functions and side effects

What are Pure Functions and Side Effects in JavaScript?