bookmark_border[译] JavaScript 之闭包

原文地址:A Simple Explanation of JavaScript Closures

回调 ( callbacks ),事件句柄 ( event handlers ),高优先级函数 ( higher-order function ① ) 等这些之所以可以访问其作用域外的变量,就是因为闭包;闭包是面向函数范式编程里一个非常重要的概念,同时也是 JavaScript 面试时经常被问到的一个知识点

① 在函数内操作的其他函数(接受它们作为参数或返回它们)称为高优先级函数

虽然闭包随处可见,但其实真正理解并不简单;如果你对理解闭包还差那么一层窗户纸,那么这篇文章就是帮你捅破那层窗户纸

首先,我会给你们介绍两个基本术语:作用域和词法作用域( scope and lexical scope ),当你了解这些基础以后,你离理解闭包也就只有一步之遥了。文章的最后,我会通过一个真实世界的例子来试图解释闭包

开始之前,我强烈建议你不要跳过作用域和词法作用域这两个小节,这两个概念对理解闭包非常重要

1. 作用域( The scope )

当你定义一个变量,你总是希望能在一定的范围内用它。比如:你定义一个变量 result ,看起来这个变量就应该在名为 calculate() 的函数内,用它来保存这个函数的计算结果并返回 。在 calculate() 函数外部,这个变量并没什么用

作用域就是管理变量的可访问性的,你可以在定义该变量的作用域内访问该变量,但是在这个变量的作用域外,你就禁止访问该变量。 在 JavaScript 种,作用域一般是由函数或者代码块产生

让我们来看看变量 count 是怎么被作用域影响的, count 属于函数 foo() 创造的一个作用域内的变量 :

function foo() {
  // The function scope
  let count = 0;
  console.log(count); // logs 0
}

foo();
console.log(count); // ReferenceError: count is not defined

变量 count 在函数 foo() 内可被自由访问。但是在函数 foo() 外,count 变量就被禁止访问,如果你试图访问,就会抛异常 ReferenceError: count is not defined

“ 作用域的作用是规定变量是否可以被访问 ”

因为作用域隔绝了变量,所以在不同的作用域内,你可以取相同的变量名。比如不同方法内使用一些很通用的变量名( i,count,index,current,value 等等),示例如下:

function foo() {
    // "foo" function scope
    let count = 0;
    console.log(count); // logs 0
}

function bar() {
    // "bar" function scope
    let count = 1;
    console.log(count); // logs 1
}

foo();
bar();

变量 count 在函数 foo(),bar() 内都被定义了,而代码也良好的运行,并没有产生冲突

2. 作用域嵌套(Scopes nesting)

作用域很简单,对不对?那我们就进阶点难度,在一个作用域内嵌套另一个作用域会怎么样呢? 方法 innerFunc() 是嵌套在 outerFunc() 方法内:

这两个方法的作用域是怎么相互影响的呢?在 innerFunc() 函数的作用域内是否可以访问变量 outerVar 呢?我们用个例子来说明一下:

function outerFunc() {
    // the outer scope
    let outerVar = 'I am outside!';
    function innerFunc() {
        // the inner scope
        console.log(outerVar); // => logs "I am outside!"
    }
    innerFunc();
}
outerFunc();

从打印结果来看,函数 innerFunc() 作用域内可以访问变量 outerVar ,从这点可以推断以下2个结论:

  • 作用域可以嵌套
  • 外层作用域的变量可以被内层作用域访问

3. 词法作用域(The lexical scope)

JavaScript 是如何将函数 outerFunc() 作用域的变量 outerVar 映射到 innerFunc() 函数作用域内的呢?

因为 JavaScript 实现了一个作用域机制,名叫 词法作用域 (或者静态作用域) 。 词法作用域确定了变量的可访问性由变量所在源代码中的位置所决定 。简单一句话就是,词法域规定:在嵌套作用域的环境中,内部作用域可以访问外部作用域的变量

它被称为词法(或静态),因为引擎(在词法分析时)仅通过查看 JavaScript 源代码来确定作用域的嵌套,而不执行它 。下面是 JavaScript 引擎理解上面示例代码的过程:

  • 1. 我发现你定义了一个函数 outFunc(),该函数内包含一个变量 outerVar
  • 2. 在outFunc() 内部,我发现你定义了一个函数 innderFunc()
  • 3. 在innerFunc()内部,我发现一个变量 outerVar ,但是在该函数内部并未定义这个变量。因为使用词法作用域,那么我就认为,函数innderFunc()内部的变量 outerVar 其实就是 函数 outerFunc() 内的变量 outerVar

所以,词法作用域可概括为:

词法作用域由静态确定的外部作用域组成

示例如下:

const myGlobal = 0;

function func() {
    const myVar = 1;
    console.log(myGlobal); // logs "0"
    function innerOfFunc() {
        const myInnerVar = 2;
        console.log(myVar, myGlobal); // logs "1 0"
        function innerOfInnerOfFunc() {
            console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0"
        }
        innerOfInnerOfFunc();
    }
    innerOfFunc();
}

func();

函数 innerOfInnerOfFunc() 的词法作用域由 函数innerOfFunc()的作用域,函数 func() 的作用域,以及全局作用域三部分组成。在函数 innerOfInnerOfFunc() 内你可以通过词法作用域访问 变量 myVar 和 myGlobal

函数 func() 的词法作用域 仅仅是由全局作用域组成,所以在函数 func() 内你可以访问全局变量 myGlobal ,但是并不能访问变量 myInnerVar

4. 闭包(The closure)

通过上面的知识,我们知道词法作用域允许静态地访问外部作用域的变量 ,这离理解闭包又进了一步

让我们回头再来看包含函数 outerFunc() 和 innderFunc() 的代码片段:

function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => logs "I am outside!"
  }
  innerFunc();
}
outerFunc();

我们知道,在函数 innerFunc() 作用域内,通过词法作用域可以访问外部变量 outerInner ,请仔细注意,函数 innerFunc() 的调用是在其词法作用域内(函数outerFunc() 的作用域)

我们来稍微改动一下,让 函数 innerFunc() 的执行在词法作用域范围之外,这样的话,函数 innderFunc() 是否依然能访问 变量 outerVar 呢? 代码片段如下:

function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => logs "I am outside!"
  }
  return innerFunc;
}
const myInnerFunc = outerFunc();
myInnerFunc();

现在,函数 innerFunc() 被执行的时候,是在定义函数 innerFunc() 的词法作用域之外了,重点来了:

函数 innerFunc() 依然能正常访问它词法作用域内的变量,即便它是在定义它的词法作用域外被执行

一句话,函数 innerFunc() 能通过词法作用域映射( 也可以理解成捕获,记住等) 变量 outerVar . 也就是说,innerFunc() 是个闭包函数,因为它通过词法作用域映射了变量 outerVar

所以呢,闭包就是一个函数访问其词法作用域内的变量,哪怕这个函数是在词法作用域外被执行

更简单点说,闭包就是一个函数记住了它定义处词法域内的变量以及变量的值,不管这个函数以后在哪儿被执行。所以确定闭包很简单:如果在函数中看到一个外来变量(未在函数中定义),那么该函数很可能是一个闭包,因为它捕获了外来变量

在上文的代码片段中, 变量 outerVar 对于闭包函数 InnerFunc() 来说,是个外来变量,它来自函数 outerFunc() 的作用域

让我们用更多例子来说明闭包吧

5. 闭包的例子(Closure examples)

5.1 事件句柄 (Event Handler)

让我们来显示一个按钮被点击了多少次:

let countClicked = 0;

myButton.addEventListener('click', function handleClick() {
  countClicked++;
  myText.innerText = `You clicked ${countClicked} times`;
});

打开示例 然后点击按钮,文本会更新你点击以后的次数。当按钮被点击以后,函数 handleClick() 会在DOM 代码中执行,这时已经远离定义它的位置了,但是因为闭包的原因,函数 handleClick() 通过词法域捕获了变量 countClicked 并刷新它的值。 当前, myText 其实也是通过词法作用域捕获的

5.2 回调(callbacks)

通过词法作用域捕获变量在回调中经常被用到,比如 setTimeout() 的回调方法

const message = 'Hello, World!';

setTimeout(function callback() {
  console.log(message); // logs "Hello, World!"
}, 1000)

函数 callback() 就是一个闭包,因为它捕获了变量 message . 又比如 forEach()的迭代器函数:

let countEven = 0;
const items = [1, 5, 100, 10];
items.forEach(function iterator(number) {
  if (number % 2 === 0) {
    countEven++;
  }
});
countEven; // => 2

函数 iterator 就是一个闭包,因为它捕获了 变量 countEvent

5.3 函数范式编程 (Functional programming)

柯里化函数就是调用一个函数会返回另一个函数 ,直到参数被完全提供。例子如下:

function multiply(a) {
  return function executeMultiply(b) {
    return a * b;
  }
}

const double = multiply(2);
double(3); // => 6
double(5); // => 10

const triple = multiply(3);
triple(4); // => 12

multiply 就是一个柯里化函数。对于函数范式编程来说,柯里化是个很重要的感念,而之所以能实现柯里化函数,还得感谢闭包。 excuteMultiply就是一个闭包函数,因为它从词法作用域捕获了变量 a , 当这个闭包函数被调用的时候,被捕获的变量 a 就和传入的参数 b 一起执行计算 a * b

6. 一个真实世界的闭包例子

我知道闭包很难掌握,但是一旦你get到它的点,你就永远掌握了它(就好比游泳,骑自行车),你可以根据如下的方式去理解闭包

想象一下你有一根神笔,如果你用它在纸上画现实世界的某些物品,那么这幅画就是跟这些物品相关联的窗口,通过这个窗口,无论你在世界的哪个角落,你都可以通过这幅画伸手进去移动这些物品,而真实世界里的这些物品也会被相应的移动。( 哇,这不就是一个任意门吗?好神奇 )

这幅神奇的画就是 闭包,而画里面的物品就是 词法作用域

7. 总结

作用域是 JavaScript 中变量是否可被访问的规则,可以是一个函数或一个范围块;词法作用域允许函数作用域从外部作用域静态地访问变量

bookmark_border[译] JavaScript 之 Temporal Dead Zone

原文地址:Don’t Use JavaScript Variables Without Knowing Temporal Dead Zone

让我问你一个小问题,以下2个代码片段,你觉得哪一个会报错?

片段1:在定义类之前实例化一个类:

new Car('red');
class Car {
    constructor (color) {
        this.color = color;
    }
}

片段2:在定义方法之前调用该方法:

greet('World');
function greet(who) {
    console.log("Hello " + who);
}

正确答案是:片段1,报得错误是:ReferenceError: Car is not defined ;片段2能正确执行

如果你的答案不是片段1,或者你只是猜测而不是真的知道原因,那么你就得好好理解 Temporal Dead Zone( TDZ )

在 JavaScript 中,对于关键字 let ,const 和 class 来说,TDZ是个非常重要的东西

1. 什么是 Temporal Dead Zone?

让我们从一个最基础的变量定义开始,如果你先定义然后初始化这个变量,然后再访问这个变量,那么一切都会正常执行

const white = '#FFFFFF';
console.log(white); // 输出:#FFFFFF 

如果我们先访问这个变量,再定义的话

console.log(white); //抛异常:ReferenceError
const white = '#FFFFFF';

在表达式 const white=’#FFFFFF’ 之前的行, 就是变量 white的Temporal Dead Zone(禁制访问区域),在TDA访问 ‘white’会抛异常:ReferenceError: Cannot access ‘white’ before initialization

“Temporal Dead Zone”寓意定义变量之前,禁止访问这个变量。它给我们传递的信号是:未定义,勿访问

2. TDZ对表达式的影响

2.1 const 变量

如下例所示,const变量也受TDZ影响,在定义之前访问的话,会报错

pi; // throws `ReferenceError`
const pi = 3.14;

如果你在定义之后访问,则能正常执行

const pi = 3.14;
pi;

2.2 let 变量

let 变量受TDZ影响和cost变量一样

count; // throws `ReferenceError`
let count =10;

而在定义之后再访问,则正常

let count;
count; // => undefined
count = 10;
count; // => 10

2.3 class 类

如文章最开始的时候,在定义类之前,不能使用它

const myNissan = new Car('red'); // throws `ReferenceError`

class Car {
    constructor (color) {
        this.color = color;
    }
}

如果你想正确使用,那么记得在定义之后再访问

class Car {
    constructor (color) {
        this.color = color;
    }
}

const myNissan = new Car('red');
myNissan.color; // => 'red'

2.4 constructor()内的super()

如果你是继承了一个父类,那么在构造函数中,调用super()方法之前 ,this 就处于TDZ,请看如下代码:

class MuscleCar extends Car {
    constructor (color, power) {
        this.power = power;
        super(color);
    }
}

const myCar = new MuscleCar('blue', '300HP'); // `ReferenceError`

在构造函数内,在调用super()之前,this是不能使用的,TDZ 机制建议调用父类的构造函数来初始化实例,然后在子类的构造函数内对实例化的对象进行你想要的操作

2.5 函数参数

函数的默认参数处于一个全局作用域和函数作用域之间的 “中间作用域”( intermidiate scope ),这个中间区域的参数,依然要遵循TDZ的规则约束:

const a = 2;
function square(a = a) {
    return a * a;
}
square(); // throws `ReferenceError`

” function square(a = a) ” ,是想表达a = a 与 const a= 2 是处于不同的作用域中,所以可以变量名一致。而 a = a 这个表达式相当于如下代码:

a;
let a = ?; 

其中 a = a,可以分成两部分,第一部分是先执行右边,访问 a,拿到a的值,然后再执行左边 let a=?这个赋值操作。所以会出现定义之前访问 a 的问题。所以为了解决这个问题,可以把代码改成如下:

const init = 2;
function square(a = init) {
  return a * a;
}
square(); // => 4

3. TDZ对var,function,import 的影响

一句话,TDZ对这三个关键字定义的对象不起任何作用,因为他们会自动提升到当前作用域最前面。你在var变量定义之前访问它,不会报错,会得到undefined

value; // => undefined
var value;

同时,你也可以在方法被定义之前,直接调用该方法

greet('World'); // => 'Hello, World!'
function greet(who) {
  return `Hello, ${who}!`;
}
greet('Earth'); // => 'Hello, Earth!'

通常,我们对函数的实现是不太感兴趣的,这也是为什么有时候在函数定义之前调用它是有意义的。所以,import也能自动提升就是很有必要的

myFunction();
import { myFunction } from './myModule';

以上代码,能正常执行。但实际上,我们不建议这么做,import放在文件最开头是个好的编程习惯

4. 在TDZ获取变量类型

typeof 操作通用被我们用来判断当前作用域变量的类型,比如,一个名叫notDefined的变量,我们直接用typeof 去获取它类型的时候,不会报错:

console.log(typeof notDefined); //=> undefined

因为这个变量未定义,所以 typeof notDefined相当于undefined. 但是当你在TDZ区域使用 typeof 的时候,就会报引用错误,代码如下:

typeof variable; // throws `ReferenceError`
let variable;

所以,总体来说,在定义变量之前,任何访问该变量的操作都是不允许,会抛引用异常的

5. 不同作用域内的TDZ

TDZ只影响当前变量所在的作用域:

让我们来看一个详细例子:

function doSomething(someVal) {
  // 函数作用域
  typeof variable; // => undefined
  if (someVal) {
    // if内部块作用域
    typeof variable; // throws `ReferenceError`
    let variable;
  }
}

doSomething(true);

通过这个示例我们能很明确的知道,一个变量的TDZ只对它当前所在的作用域有效果

6. 总结

TDZ是影响 const、let 和 class 语句可用性的重要概念,它不允许在声明之前使用变量。相反,如果想在声明之前就可以使用变量,那么你可以继续使用var来兼容。不过你应该避免那样做。因为在我看来,TDZ是语言规范中良好的编码实践之一

bookmark_border[译] JavaScript 之 函数

“ 函数是 JavaScript 的精髓 , 同时也是这门语言的魅力所在 ” —— Douglas Crockford

原文地址:The power of JavaScript Functions

函数( Functions )可以说是 JavaScript 中最强大的对象,其他编程语言中很多不同的特性功能,比如:程序( procedures ),方法( methods ),构造函数( constructors ) 甚至是 类( classes )和 模块( modules ) 等 ;所有这些,在 javaScript 中都可以用函数实现

在最新版的 Javascript 中,引入了类( Class ) 、方法( method )和构造函数( constructor )等概念,其实这些只不过是用函数包装的语法糖

一旦你理解了函数的细节微妙,你其实就掌握了相当一部分 JavaScript ;当然,你还得学习在不同的作用域有效的使用函数,才能更上一层楼

本文的目的就是为你揭开函数的神秘面纱,让你不仅看到它的表象,更能洞彻它的精髓

函数对象(Functions as objects)

Javascript 的一个重要的特性:函数是一种特殊类型的对象。声明一个函数和声明一个对象其实没什么两样,因为声明一个函数就是产生一个函数对象

函数对象是第一类对象( first classed object ) : 它可以当作参数来传递,可以是其他函数返回的对象,可以把它赋值给一个变量,也可以存储在对象或者数组中。它继承自Function.prototype ,所以函数还可以直接调用 bind() , apply() 和 call() 方法

将函数看作是一个特殊类型的对象,对有其他编程语言经验的人员来说,可能是个不小的挑战;但是一旦你接受了这个设定,则对于理解JavaScript非常有帮助

函数结构(Function structure)

声明一个函数有如下两种方式:

var sum = function(a, b) { //函数表达式
     return a + b; 
}

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

第 1 种方式,函数名是可选的(上面的示例就没有函数名);第 2 种方式,必须要有函数名

另一个值得注意的是,JavaScript没有”函数签名(function signature)”的概念,你可以传入零个或者多个参数去调用函数,而不用担心会报错;如果函数需要的参数你没传入,那么参数值默认就是undefined 。如果你想知道传入的参数是什么?你可以通过 arguments 对象来查看。示例如下:

function sum(a, b) {
    for (let i = 0; i < arguments.length; i++) {
        console.log("argument: " + arguments[i]);
    }
    console.log("sum: " + (a + b));
}

sum();
sum(1, 2, 3);

输出结果:

sum: NaN
argument: 1
argument: 2
argument: 3
sum: 3

函数没有明确要求一定得返回什么,如果一个函数没有明确返回的对象,那么默认返回 undefined. 谨记:

  • 函数签名概念在js中不存在 —— 函数可以被传入任意参数调用
  • 函数永远会返回一个值 —— 如果没明确提供返回的值,那么返回undefined(构造函数除外,它一定会返回新对象)

变量作用域(Variable scope)

变量作用域决定了变量的可访问性(可见性) , 在JavaScript中,有两种作用域—全局(global)和本地(local 主要是只函数内的 )

当一个函数被调用,那么一个作用域就形成了,本地变量只能在本地作用域内被访问

当使用var声明一个变量时,它会自动添加到最直接的可用范围中。在函数中,最直接的可用范围是函数的上下文环境。由于局部变量只能在函数内部识别,所以同名的变量可以用于不同的函数中

Javascript的一个特殊性是它没有块级范围。块级范围意味着如果一个变量在一个块内声明(for、while、if等),那么它只能在块内访问,不能在块外访问。例如,你可能期望这段代码能正常工作:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
function print_matrix(matrix, n) {
    for (var i = 0; i < n; ++i) {
        row = matrix[i];
        for (var i = 0; i < n; ++i) {
            console.log(row[i]);
        }
    }
}

print_matrix(matrix, 3);

输出结果:1 2 3 问题出在哪儿呢? 因为第一个 for 的变量 i 每次都会在第二个for中重新赋值,这个函数将在第一行之后退出。 为了解决这个问题,在最新版本的Javascript (ECMAScript 6)中引入了关键字let。在块内部使用let声明的变量将具有块级范围。所以简单地改变var,上面的例子就能按照你的期望执行

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
function print_matrix(matrix, n) {
    for (let i = 0; i < n; ++i) {
        row = matrix[i];
        for (let i = 0; i < n; ++i) {
            console.log(row[i]);
        }
    }
}

print_matrix(matrix, 3);

变量提升(Variable hoisting)

提升是Javascript中的一种默认行为,它表示在代码执行之前将所有变量的声明移到其作用域的顶部。所以在函数中声明一个变量并不重要,变量从执行上下文开始就属于那个范围。但是,提升只针对变量声明,而不是变量赋值。所以在到达变量被显式赋值的那一行之前,变量的值是undefined

function myFunction() {
    console.log(myVar); // undefined
    var myVar = 3;
    console.log(myVar); // 3
}

myFunction();

因为变量提升,所以上面的代码相当于下面这段代码:

function myFunction() {
    var myVar;          // 变量定义会被提升到最顶端
    console.log(myVar); // 此时变量并未赋值,所以输出undefined
    myVar = 3;          // 变量赋值不会提升
    console.log(myVar); // 3
}

myFunction();

这适用于任何变量和函数表达式,但是,函数表达式和函数声明之间有一些细微的区别。提升会把声明移动到当前作用域最顶端,但是不包括赋值(初始化),在赋值(初始化)之前,变量的值是undefined

函数表达式 vs 函数声明

在继续之前,我有一件事要坦白。文章开头引人注目的引语是不完整的。我漏掉了一部分。让我们看看完整版:

“ 函数是 JavaScript 的精髓 , 同时也是这门语言的魅力所在。当然,像JavaScript中的其他东西一样,它很难确切的掌握 ” —— Douglas Crockford

就比如函数,就有两种声明函数的方法,它们之间还有细微的差别,这就是“不太确切”的一个例子

区别在于浏览器如何将它们加载到执行上下文中。函数表达式的加载方式与其他变量完全相同。当编译器到达这行代码时,它们被赋值(在本例中是函数对象)

但是,函数声明在执行任何代码之前加载。因此,即使函数在调用它的代码行下面声明,它也能工作。谨记:

  • 当你调用一个函数表达式,而它的声明代码在你调用的代码后面时,会报错
  • 当你调用一个函数,哪怕这个函数在你调用的代码后面声明,它依然能正确执行(因为它在编译之前会被提升到当前作用域前面)

为什么函数有这两种定义?

问的好,函数声明是在ECMAScript 3中引入的,它让我们记住Javascript是浏览器的语言,而用户很少更新浏览器。很多时候,浏览器的新版本只有在用户更新操作系统时才会接触到他们。所以为了考虑兼容性,就有了这两种定义方式

如前所述,从数据类型的角度来看,每个函数都是一个对象。函数对象有三个非常重要的属性来表示函数的身份: this 、arguments 和 prototype

this 是啥?

关于JavaScript的 this ,可以写一篇很长的文章,此处不打算详细介绍。只要记住this 是指向调用它的对象, 所以 this 的值在函数调用时分配

当从全局作用域简单地调用一个函数时,this 显然会指向全局作用域本身。这是造成混乱的一个重要原因,这就是为什么在严格模式下运行时,值是未定义的。通常,您不会对这样的函数使用 this , This 对于用作方法的函数非常有用。这样,该方法就可以访问对象属性

var myObj = {
    name: "John",
    sayName: function (name) {
        console.log("Hi, " + this.name); // 'this' 指向 myObj 
    }
};

myObj.sayName();

“This”在函数用作构造函数时也有重要作用。构造函数没有任何特殊的结构。当使用运算符new调用它时,它是一个构造函数。该操作符在底层创建一个新对象,并通过’ This ‘将其传递给函数

function Car(name) {
   this.name = name;
}

var myCar = new Car("Subaru");
console.log(myCar.name);

在上面的例子中,函数Car被用作一个函数。用大写字母声明它是一种常见的做法,目的很明显,但它只是一个简单的函数

执行new关键字,其实执行了如下步骤:

  • 创建一个空对象
  • this指向新创建的对象,给新对象增加name属性并赋值
  • 将Car函数的prototype赋值给新对象的prototype
  • 返回创建的对象

每个函数都可以访问从 Array.prototype 继承的.apply()、.bind()和call()。它们允许使用显式设置的’ this ‘的任何值来调用函数。一般来说有以下四种方式调用一个函数:

  • function form (‘this’ will be the global object or undefined in strict mode)
  • method form (‘this’ will reference the object that made the call)
  • constructor form (‘this’ will be the new object)
  • apply/bind/call form (‘this’ is explicitly set)

函数参数(Function arguments)

如前所述,声明中提供的参数不是可靠的信息来源。可以使用不同数量的参数调用该函数。如果它们太多,则忽略额外的参数。如果它们太少,丢失的值将是未定义的。

幸运的是,函数的arguments属性在这里可以帮助我们。它是一个类似数组的对象,包含调用时提供的参数。注意,它并不是一个真的数组,因为它不是从数组继承的,它唯一能调用的就是获取数组的长度,参数的存储方式与数组完全一样,索引从0开始

虽然arguments对象不是只读的,但是强烈建议不要修改它。更改arguments对象也会影响已命名的参数,这很容易引起混淆。

它的一个常见用法是创建具有未定义数量的参数的函数。例如,一个函数返回它的参数和:

function sum() {
    var i;
    var total = 0;
    var n = arguments.length;
    for (i = 0; i < n; i++) {
        total += arguments[i];
    }
    return total;
}
console.log(sum(1, 2, 3, 4)); // 10 

记住:参数对象是关于函数调用时提供的信息的惟一可靠来源,要将参数视为只读结构

函数原型(Function Prototype)

函数的原型对象只有在函数用作构造函数时才有意义。它在 Javascript 中创建继承时起着关键作用。通过它,使用相同构造函数创建的对象可以共享相同的属性和行为

原型继承是一个庞大的主题,超出了本文的范围,以后有时间再详谈

bookmark_border何为分级基金

想要了解何为分级基金,就必须先了解什么是基金


       话说,深圳有个下沙村,村里的人都以海边捕鱼为生,生活艰苦。某天,有个领导人在深圳的海边画了个圈,深圳就火箭般的速度发展;下沙村临近海边,房地产开发商看到了满满的海景房,于是去下沙村征地,下沙村的村民从贫农变成了富一代


       村民都有钱了,有些人拿着钱去东莞挥霍;有些人赶紧把钱存银行吃利息;还有一部分人呢,想用钱生钱,于是这部分村民一起开会。有人说:要不我们投资房地产,以后房价肯定涨;另一部分人说:我们投资互联网公司吧,那个叫什么腾讯的公司我看就很有潜力;还有一部分说:最近不是股票很火么,要不我们投资股票吧。三个臭皮匠,顶个诸葛亮,各种基金的模型应运而生


       投资房地产的村民,就把钱放在一起,专门投资房地产,而且取个好听的名字,叫做下沙房地产基金。准备投资互联网公司的人也跟风,成立了下沙天使投资基金,更高大上。炒股票的立马跟上,叫做下沙股票基金。于是成立了三个基金,村民们根据自己的钱对应里面的股份,开始准备用钱生钱了


       基金有了,但是村民不是很擅长做投资,于是请来三个博士来管理基金,一个房地产博士,一个互联网博士,一个经济学博士。这就是传说中的基金经理。大家觉的直接把钱给基金经理的话太危险了,万一他拿钱跑路了呢?于是村民们成立了一个公司,下沙基金公司,然后去银行开了个账户。所以基金的管理人是:下沙基金公司;基金托管人是:开户的银行


       有了以上基础知识,我们单独讲下沙股票基金。假如一开始村民一共凑了1亿元,然后分成1亿股。一年后,由于博士买股票太牛了,1亿的基金变成了市值2亿的基金,这样下沙股票基金的持有者,村民的钱就翻倍了,老王当初花100万买了100万股,现在100万股价值200万,这酸爽。不成想,不到一个月,股市大跌,本来值2亿的基金现在变成了只值五千万(因为基金购买的股票全部大跌,所以市值缩水一半),老王那叫一个心疼哟,他的100万股,只值50万了。老王心脏不好,一急之下住院了


       这下博士深深的自责,但股市大跌谁也没想到来的那么快那么猛。还有好多村民天天堵博士家门口,问他怎么不去死?于是博士想了个点子,号召大家开个会。首先是深深的道歉,死是不能死的,要为大家赚回本再说。为了防止这种情况发生,博士有了个新的想法,因为村民有一部分很激进,也有些很保守。他就想,能不能弄个基金,可以拆分成A和B。选A的呢,每年固定收益,当然比存银行收入多点,也保本。选B的呢,不保本,获取除开A每年固定的收益的所有利润,当然如果基金损失也要连A的损失一起承担。这么一说,很多村民听不懂,于是博士就举了个例子:


       假如老王出100块钱,老刘也出100块钱,老王承受风险能力不行,老王的钱就算A;老刘呢,比较激进就算B 。 于是我就有200块钱了,我现在拿着这200块钱去炒股,假如一年后我赚了,200变成了300,那么老王可以拿到100+100%10=110(假如每年规定的固定收益是10%),而老刘呢,就可以拿到300-110=190。假如不幸,我一年后亏了,200变成了150那么老王还是可以拿到110,老刘只能拿到40。这么一说,村民感觉懂了,又有村民问,假如你亏到只剩100怎么办?博士说,这个你不用担心,我会在每时每刻算好一定保证A收益的,假如到了临界点情况我会把基金买的股票全部卖掉,换成现金还给A,保证A的本金和收益,B就对不起了,你选择了风险,你就要承担损失


       这么一说,老王就出院了,这点子靠谱。而且博士还给这个基金一个响亮的名字,下沙股票分级基金,这样以后,老王再也不用住院了。每天拿着稳定的收益妥妥的,下沙村民从此过着幸福的生活——养富二代,富三代

当然,实际上分级基金还涉及到上折,下折,净值,市值等等概念。如果有兴趣深入了解,可以查看我的雪球专栏

bookmark_border什么是财政?

什么是财政?一个人活着,就要赚钱,赚了钱才能花钱,有钱花才能活下去(循环下去,生生不息);一个家庭也是,有收入才能有支出,才能传宗接代壮大家庭;一个企业也是,要生存也要赚钱然后再生产扩大规模;这些都有一些私人性质。但是社会上还有其他的公共部门,比如:政党(组织部,宣传部等),政府(国防部,公安部,消防部等),还有一些事业单位,学校,医院等,我们需要这些公共部门提供服务。比如,我们需要保障人身安全,就需要警察局;我们需要教育,医疗,所以需要学校,医院;以上所有这些部门,统称公共部门


      私人部门,自己赚钱,然后花钱。公共部门呢?需要办公楼,需要车,需要武器等,他们钱怎么来?他们不直接生产东西拿出去卖,本身不创造物质财富。假设要他们自己去筹钱,自己维持运行。那么国防部,公安局可能要钱就轻松些,直接拿抢挨家挨户的要。而民政局,组织部,卫生部等去收钱就没人理他了。而且时间久了,性质会慢慢转变,给的钱多,就给你更好的服务,所以这样明显不行。另外一个办法是,有一个专门的部门来负责所有的公共部门筹集资金,然后再把钱拨给公共部门过日子,这个公共部门,就是财政部


    财政其实说白了,就是 :收钱,花钱。收钱的,现在我们叫做税务局。税务局收钱的方式主要有两种,第一种:收税;第二种:收费。收税都懂,收费是什么?比如:汽车上牌照要交钱,办护照要钱等。收税,权利和义务不是很明显,为什么要交税,因为公共部门要花钱,为什么公共部门花钱要我交税,因为我提供公共服务了,比如保证安全,给你提供医疗,教育等,所以你要交税。你赚了大把钱,如果没有公安局保证你的安全,你就被人抢了;如果没有法院,检察院,你的委屈跟谁诉说。但是,税并不是说,你交的多,你享受的服务就更多。你交100块,他交100万,享受到的服务还是一样的(至少国家的想法是这样,至于具体实际情况另当别论)。比如,还有一些人从来不交税,也享受公共服务,所以交税跟享受公共服务不成比例。但是收费,就比较明显对等关系,用了才收费,比如你买车上牌照,就要交钱,不买车不享受这个服务,就不需要交钱


       钱收上来了,现在就是发下去。国防部,教育部要钱,财务部,税务局自己也要钱。那么问题来了,钱怎么分?国防部给少了,分分钟开坦克来收拾你;给多了其他部门就有意见,财政部太难了。所以,就想出了个办法,你不是要钱么?你就要报一个计划,你今年要花多少钱,详细列出来,这样我也好跟大家交代。比如:国防部你今年要买几辆坦克,手雷,规划好,交给财政部门。财政部就开始审核,根据你们的编制,岗位人数等核定。但是不是财政部审核通过了就直接打钱?不行,万一财政部里面有你干爹呢?所以最后一步,交给我们国家的最高权力机关:人民代表大会去审核(所以每年两会,其实还是有事儿做的,不都是明星去睡觉上镜,还是有人去为民众操心,为国家操心的做实事的),最后人民代表大会批准了,才行。这就是传说中的预算。预算会形成法律文件,具有法律效应


    讲到这里,才能理解,因为有了国家,所以才有财政。没有国家我要这些公共部门干嘛?自己请军队来,自己保护自己,缅甸很多军火商,毒贩就是这样,你敢去问他们收费收税?国家就是一个统治机器,所以才有了这些公共部门,才有了国防,国民的概念,国家是什么性质,就决定财政是什么性质


    马克思列宁主义认为:国家是一个阶级统治和压迫另一个阶级的工具,这叫暴力论。西方学者认为:国家是一个契约,是人民同意把一部分权力转让给你们公共部门,你来组件国家机器,我们来交税交费,你来提供公共商品和服务。就好像双方达成一个合同,一个条约,这就叫契约论。这部契约就叫:宪法


    财政有没有阶级性呢?财政会不会更好的服务统治阶级呢?美国是:美国政府的背后,就是各大资本财团,所以你当总统,主要就是为资产阶级服务。有时候还为军火商服务,军火商说,怎么老不打仗啊,我这东西都卖不出去,特朗普就整点事儿,打几仗,卖卖军火,清清库存。

以上就是关于财政的说明,你懂了吗?

bookmark_border阿里云ECS搭建WordPress

入职猎豹(深圳游戏中心)以后,项目赶,老板狠导致阿里云服务器到期后忘了续费,服务器被回收了,以前所有的文章都没了,甚是可惜

所以就从零开始换成了阿里云ECS服务器(以前用的是菁云服务器),重新搭个博客框架。写篇教程,也当作是重新拾起写作的习惯吧。

WordPress是目前世界上最流行的博客框架,我购买的是阿里云ECS Ubuntu系统的服务器,打开阿里云控制台,选中你买的服务器,点击 “远程连接” 进入以后开始操作以下指令

1.更新资源

Advanced Package Tool,又名apt-get,是一款适用于Unix和Linux系统的应用程序管理器 ,下面两行指令,是更新和升级当前系统软件包

sudo apt-get update
sudo apt-get upgrade

2.安装Apache2

Apache 是当前最流行的Web服务器软件之一,我们的网站内容就是通过它来展示给读者(你也可以安装Nginx),所以必须安装

sudo apt-get install apache2

这一步执行以后,你可以在你自己的电脑浏览器内输入:http://公网ip/,如果能看到apache的介绍页面,那说明安装成功;如果没有, 则打开控制台的服务器实例>本实例安全组>配置规则,添加安全组规则 ,如下图:

设置完毕以后,再刷新浏览器,肯定就可以看到apache的介绍网页了

3.安装Php

因为WordPress是php代码写得,所以肯定得配置php环境,第一行是安装php,第二行是安装相关组件,安装完成后,输入:php -v 可查看是否安装成功

sudo apt-get install php7.0 -y
sudo apt-get install libapache2-mod-php7.0

4.安装MySQL

既然是博客,那博客的内容肯定是要存入数据库的,对吧。所以我们得安装一个数据库,我安装的是MySQL,注意安装过程中会提示设置root密码,一定要记住。安装完成后,输入第二行,准备执行sql命令,第三行则是创建数据库命令(最后一个逗号要加上哦)

sudo apt-get install mysql-server
mysql -u root -p
mysql -> create database wordpress;

5.安装WordPress

最后,安装wordPress,这一步呢,也可以用指令完成,但是很慢。我们可以直接去官网下载包,然后解压。将所有内容上传到 /var/www/html/ 目录下。怎么上传呢?我建议下载 WinScp这款软件远程连接你的服务器。注意,要把这个目录设置成所有用户可读可写,因为我不熟悉linux命令行操作,所以这一步我也是通过WinScp右键文件夹属性操作的。最后,就是设置你的WordPress啦,这些步骤比较简单