阅读 469

Javascript 函数的认知

由事件驱动的或者当它被调用时执行的可重复使用的代码块

函数声明与函数表达式

  • 函数声明是解释器会首先读取,并使其在执行任何代码之前可用;
  • 函数表达式,则必须等到解释器执行到它所在的代码行,才会被真正解析;
// function.js
console.log(add1(1, 2)); // 3
console.log(add2(1, 2)); // Uncaught ReferenceError: Cannot access 'add2' before initialization

//函数声明
function add1(a, b){
    return a + b;
}

//函数表达式
const add2 = function add(a, b){
    return a + b;
}
复制代码

arguments 对象

内置对象,用于获取函数参数和参数长度等。

// factorialArguments.js
const factorialArguments = function(num) {
    if(num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}
console.log('5的阶乘', factorialArguments(5));

// factorial.js
const factorial = function f(num) {
    if(num <= 1) {
        return 1;
    } else {
        return num * f(num - 1);
    }
}
console.log('5的阶乘', factorial(5));
复制代码

call 和 apply

// callAndApply.js
const sum = function sum(a, b) {
    return a + b;
}

const sumByCall = function (a, b) {
    return sum.call(this, a, b);
}

const sumByApply = function (a, b) {
    return sum.apply(this, [a, b]);
}

const sumByApplyArguments = function (a, b) {
    return sum.apply(this, arguments);
}
console.log('sum 求和', sum(2, 7));
console.log('sumByCall 求和', sumByCall(2, 7));
console.log('sumByApply 求和', sumByApply(2, 7));
console.log('sumByApplyArguments 求和', sumByApplyArguments(2, 7));
复制代码

区别: apply 传递参数是数组传递,call 是一个一个传递

参数传递

  • 参数是值就按值传递;
  • 参数是对象,就按对象的引用地址传递;

按值传递

// transmitByValue.js
const transmitByValue = function (str) {
    str += ',我是 pr';
    return str;
}

const str = '你好';
console.log('str before', str); // str before 你好
console.log('str by transmitByValue', transmitByValue(str)); // str by transmitByValue 你好,我是 pr
console.log('str after', str); // str after 你好
复制代码

按地址传递

// transmitByAddress.js
const transmitByAddress = function (str) {
    str.words += ',我是 pr';
    return str;
}

const str = {
    words: '你好'
};
console.log('str before', str.words); // str before 你好
console.log('str by transmitByAddress', transmitByAddress(str)); // str by transmitByAddress { words: '你好,我是 pr' }
console.log('str after', str.words); // str after 你好,我是 pr
复制代码

闭包和匿名函数

  • 匿名函数,顾名思义就是没有名字的函数
  • 闭包是一个函数,它可以访问一个函数作用域里的变量。由于闭包作用域返回的局部变量资源不会立刻被销毁回收,所以就会占用(吃)更多内存,性能就会下降,所以非必用可不用。
// closureHasAnonymousFunction.js
const closure = function () {
    let num = 10;
    return function () {
        return ++num;
    }
}

const closureAlias = closure();
console.log(closureAlias()); // 11
console.log(closureAlias()); // 12
console.log(closureAlias()); // 13
console.log('-----');
// closure()()外层函数每次都执行,即 num 每次都初始化
console.log(closure()()); // 11
console.log(closure()()); // 11
console.log(closure()()); // 11
复制代码

解析

  • 使用局部变量实现累加功能;
  • 定义函数 closure,返回一个匿名函数形成闭包;
  • 将 closure() 赋给 closureAlias,此时函数 closureAlias 会初始化 num,值为匿名函数;
  • 执行 closureAlias;

闭包中的 this

this 对象是在运行时基于函数的执行环境而绑定的,如果全局那就是 window 或 global(Node.js),在对象内部就是这个对象。闭包运行时属于 window,因为不属于这个对象的属性或方法。

// closureHasThis.js
var obj = {
    name: 'say',
    say () {
        return function () {
            return this;
        };
    }
}

console.log(obj.say()()); // 指向 Window
复制代码

1.对象冒充方式解决

// closureHasThis1.js
var obj = {
    name: 'say',
    say () {
        return function () {
            return this;
        };
    }
}

console.log(obj.say().call(obj));
复制代码

2.赋值方式解决

// closureHasThis2.js
var obj = {
    name: 'say',
    say () {
        var that = this;
        return function () {
            return that;
        };
    }
}

console.log(obj.say()());
复制代码

闭包解决循环问题

这个例子在 ES6 的 let 关键字中介绍过。

循环中的每个迭代器在运行时都会给自己捕获一个 i 的副本,根据作用域的工作原理,循环中的 10 个函数虽然是在各个迭代器中分别定义的,但它们都会被封闭在全局作用域中,实际只有一个 i,结果每次都会输出 10。

// loop.js
for (var i = 0; i <= 9; i++) {
    setTimeout(function () {
        console.log(i);
    });
}
复制代码

1.使用闭包,在每个循环迭代中加个闭包作用域

// loopHasClosure.js
for (var i = 0; i <= 9; i++) {
    (function () {
        console.log(i);
    })(i);
}
复制代码

2.使用 let

// loopHasLet.js
for (let i = 0; i <= 9; i++) {
    console.log(i);
}
复制代码

本次代码 Github

你可以...

关注下面的标签,发现更多相似文章
评论