资讯专栏INFORMATION COLUMN

重新复习js

xuexiangjys / 2594人阅读

摘要:复习基础到底是什么的应用场合极其广泛。常量不可以通过重新赋值改变其值,也不可以在代码运行时重新声明。布尔对象是原始布尔数据类型的一个包装器整数整数可以用十进制基数为十六进制基数为八进制基数为以及二进制基数为表示。

复习js day1 js基础 JavaScript 到底是什么

JavaScript 的应用场合极其广泛。简单到幻灯片、照片库、浮动布局和响应按钮点击。复杂到游戏、2D 和 3D 动画、大型数据库驱动程序,等等。

JavaScript 相当简洁,却非常灵活。开发者们基于 JavaScript 核心编写了大量实用工具,可以使 开发工作事半功倍。其中包括:

浏览器应用程序接口(API)—— 浏览器内置的 API 提供了丰富的功能,比如:动态创建 HTML 和设置 CSS 样式、从用户的摄像头采集处理视频流、生成3D 图像与音频样本,等等。

第三方 API —— 让开发者可以在自己的站点中整合其它内容提供者(Twitter、Facebook 等)提供的功能。

第三方框架和库 —— 用来快速构建网站和应用。

变量 变量的定义和数据类型

在应用程序中,使用变量来作为值的符号名。变量的名字又叫做标识符,其需要遵守一定的规则。

一个 JavaScript 标识符必须以字母、下划线(_)或者美元符号($)开头;后续的字符也可以是数字(0-9)。因为 JavaScript 语言是区分大小写的,所以字母可以是从“A”到“Z”的大写字母和从“a”到“z”的小写字母。

变量 解释
string 字符串(一串文本)。字符串的值必须将用引号(单双均可,必须成对)扩起来。
Number 数字。无需引号。
Boolean 布尔值(真 / 假)。 true/false 是 JS 里的特殊关键字,无需引号。
Array 数组,用于在单一引用中存储多个值的结构
Object 对象,JavaScript 里一切皆对象,一切皆可储存在变量里。这一点要牢记于心。
数据类型的转换

字符串转换为数字
有一些方法可以将内存中表示一个数字的字符串转换为对应的数字。parseInt()和parseFloat()

parseInt 方法只能返回整数,所以使用它会丢失小数部分。另外,调用 parseInt 时最好总是带上进制(radix) 参数,这个参数用于指定使用哪一种进制。

将字符串转换为数字的另一种方法是使用一元加法运算符。

"1.1" + "1.1" = "1.11.1"
(+"1.1") + (+"1.1") = 2.2   
// 注意:加入括号为清楚起见,不是必需的。
声明变量

var 声明全局变量和局部变量
let 声明块作用域的局部变量
const 声明一个常量

使用var和let声明的变量时没有赋予初始值的,其值为undefined,

var a;
console.log("The value of a is " + a); // a 的值是 undefined

console.log("The value of b is " + b);// b 的值是 undefined 
var b;


console.log("The value of c is " + c); // 未捕获的引用错误: c 未被定义

let x;
console.log("The value of x is " + x); // x 的值是 undefined

console.log("The value of y is " + y);// 未捕获的引用错误: y 未被定义
let y;

你可以使用 undefined 来判断一个变量是否已赋值。在以下的代码中,变量input未被赋值,因此 if 条件语句的求值结果是 true

var input;
if(input === undefined){
  doThis();
} else {
  doThat();
}

undefined 值在布尔类型环境中会被当作 false 。

数值类型环境中 undefined 值会被转换为 NaN。

当你对一个 null 变量求值时,空值 null 在数值类型环境中会被当作0来对待,而布尔类型环境中会被当作 false

var n = null;
console.log(n * 32); // 在控制台中会显示 0

变量的作用域

在函数之外声明的变量,叫做全局变量,因为它可被当前文档中的任何其他代码所访问。在函数内部声明的变量,叫做局部变量,因为它只能在当前函数的内部访问。

ECMAScript 6 之前的 JavaScript 没有 语句块 作用域;相反,语句块中声明的变量将成为语句块所在函数(或全局作用域)的局部变量。例如,如下的代码将在控制台输出 5,因为 x 的作用域是声明了 x 的那个函数(或全局范围),而不是 if 语句块。

if (true) {
  var x = 5;
}
console.log(x); // 5

如果使用 ECMAScript 6 中的 let 声明,上述行为将发生变化。

if (true) {
  let y = 5;
}
console.log(y); // ReferenceError: y 没有被声明
变量提升

JavaScript 变量的另一个不同寻常的地方是,你可以先使用变量稍后再声明变量而不会引发异常。这一概念称为变量提升;JavaScript 变量感觉上是被“提升”或移到了函数或语句的最前面。但是,提升后的变量将返回 undefined 值。因此在使用或引用某个变量之后进行声明和初始化操作,这个被提升的变量仍将返回 undefined 值。

/**
 * 例子1
 */
console.log(x === undefined); // true
var x = 3;


/**
 * 例子2
 */
// will return a value of undefined
var myvar = "my value";

(function() {
  console.log(myvar); // undefined
  var myvar = "local value";
})();


//详细解释就是
/**
 * 例子1
 */
var x;
console.log(x === undefined); // true
x = 3;
 
/**
 * 例子2
 */
var myvar = "my value";
 
(function() {
  var myvar;
  console.log(myvar); // undefined
  myvar = "local value";
})();

由于存在变量提升,一个函数中所有的var语句应尽可能地放在接近函数顶部的地方。这个习惯将大大提升代码的清晰度。

在 ECMAScript 6 中,let(const)将不会提升变量到代码块的顶部。因此,在变量声明之前引用这个变量,将抛出引用错误(ReferenceError)。这个变量将从代码块一开始的时候就处在一个“暂时性死区”,直到这个变量被声明为止。

console.log(x); // ReferenceError
let x = 3;
函数提升

对于函数来说,只有函数声明会被提升到顶部,而函数表达式不会被提升。

/* 函数声明 */

foo(); // "bar"

function foo() {
  console.log("bar");
}


/* 函数表达式 */

baz(); // 类型错误:baz 不是一个函数

var baz = function() {
  console.log("bar2");
};
常量

你可以用关键字 const 创建一个只读的常量。常量标识符的命名规则和变量相同:必须以字母、下划线(_)或美元符号($)开头并可以包含有字母、数字或下划线。

const PI = 3.14;

常量不可以通过重新赋值改变其值,也不可以在代码运行时重新声明。它必须被初始化为某个值。

常量的作用域规则与 let 块级作用域变量相同。若省略const关键字,则该标识符将被视为变量。

在同一作用域中,不能使用与变量名或函数名相同的名字来命名常量。

然而,对象属性被赋值为常量是不受保护的,所以下面的语句执行时不会产生错误。

const MY_OBJECT = {"key": "value"};
MY_OBJECT.key = "otherValue";

同样的,数组的被定义为常量也是不受保护的,所以下面的语句执行时也不会产生错误。

const MY_ARRAY = ["HTML","CSS"];
MY_ARRAY.push("JAVASCRIPT");
console.log(MY_ARRAY); //logs ["HTML","CSS","JAVASCRIPT"];
字面量 (Literals)

译注:字面量是由语法表达式定义的常量;或,通过由一定字词组成的语词表达式定义的常量

在JavaScript中,你可以使用各种字面量。这些字面量是脚本中按字面意思给出的固定的值,而不是变量。(译注:字面量是常量,其值是固定的,而且在程序脚本运行中不可更改

数组字面量 (Array literals)

数组字面值是一个封闭在方括号对([])中的包含有零个或多个表达式的列表,其中每个表达式代表数组的一个元素。当你使用数组字面值创建一个数组时,该数组将会以指定的值作为其元素进行初始化,而其长度被设定为元素的个数。

下面的示例用3个元素生成数组coffees,它的长度是3。

var coffees = ["French Roast", "Colombian", "Kona"];

var a=[3];

console.log(a.length); // 1

console.log(a[0]); // 3
//注意 这里的数组字面值也是一种对象初始化器。

若在顶层(全局)脚本里用字面值创建数组,JavaScript语言将会在每次对包含该数组字面值的表达式求值时解释该数组。另一方面,在函数中使用的数组,将在每次调用函数时都会被创建一次。

数组字面值同时也是数组对象。有关数组对象的详情请参见数组对象一文。

数组字面值中的多余逗号
(译注:声明时)你不必列举数组字面值中的所有元素。若你在同一行中连写两个逗号(,),数组中就会产生一个没有被指定的元素,其初始值是undefined。以下示例创建了一个名为fish的数组:

var fish = ["Lion", , "Angel"];

在这个数组中,有两个已被赋值的元素,和一个空元素(fish[0]是"Lion",fish[1]是undefined,而fish[2]是"Angel";译注:此时数组的长度属性fish.length是3)。

如果你在元素列表的尾部添加了一个逗号,它将会被忽略。在下面的例子中,数组的长度是3,并不存在myList[3]这个元素(译注:这是指数组的第4个元素噢,作者是在帮大家复习数组元素的排序命名方法)。元素列表中其它所有的逗号都表示一个新元素(的开始)。

注意:尾部的逗号在早期版本的浏览器中会产生错误,因而编程时的最佳实践方式就是移除它们。

(译注:而“现代”的浏览器似乎鼓励这种方式,这也很好解释原因。尾部逗号可以减少向数组的最后添加元素时,因为忘记为这最后一个元素加逗号 所造成的错误。)

var myList = ["home", , "school", ];

在下面的例子中,数组的长度是4,元素myList[0]和myList[2]缺失(译注:没被赋值,因而是undefined)。

var myList = [ , "home", , "school"];

再看一个例子。在这里,该数组的长度是4,元素myList[1]和myList[3]被漏掉了。(但是)只有最后的那个逗号被忽略。

var myList = ["home", , "school", , ];

理解多余的逗号(在脚本运行时会被如何处理)的含义,对于从语言层面理解JavaScript是十分重要的。但是,在你自己写代码时:显式地将缺失的元素声明为undefined,将大大提高你的代码的清晰度和可维护性。

布尔字面量 (Boolean literals)

布尔类型有两种字面量:true和false。

不要混淆作为布尔对象的真和假与布尔类型的原始值true和false。布尔对象是原始布尔数据类型的一个包装器

整数 (Integers)

整数可以用十进制(基数为10)、十六进制(基数为16)、八进制(基数为8)以及二进制(基数为2)表示。

十进制整数字面量由一串数字序列组成,且没有前缀0。

八进制的整数以 0(或0O、0o)开头,只能包括数字0-7。

十六进制整数以0x(或0X)开头,可以包含数字(0-9)和字母 a~f 或 A~F。

二进制整数以0b(或0B)开头,只能包含数字0和1。

严格模式下,八进制整数字面量必须以0o或0O开头,而不能以0开头。

整数字面量举例:

0, 117 and -345 (十进制, 基数为10)
015, 0001 and -0o77 (八进制, 基数为8)
0x1123, 0x00111 and -0xF1A7 (十六进制, 基数为16或"hex")
0b11, 0b0011 and -0b11 (二进制, 基数为2)
对象字面量 (Object literals)

对象字面值是封闭在花括号对({})中的一个对象的零个或多个"属性名-值"对的(元素)列表。你不能在一条语句的开头就使用对象字面值,这将导致错误或产生超出预料的行为, 因为此时左花括号({)会被认为是一个语句块的起始符号。(译者:这 里需要对语句statement、块block等基本名词的解释)

以下是一个对象字面值的例子。对象car的第一个元素(译注:即一个属性/值对)定义了属性myCar;第二个元素,属性getCar,引用了一个函数(即CarTypes("Honda"));第三个元素,属性special,使用了一个已有的变量(即Sales)。

var Sales = "Toyota";

function CarTypes(name) {
  return (name === "Honda") ?
    name :
    "Sorry, we don"t sell " + name + "." ;
}

var car = { myCar: "Saturn", getCar: CarTypes("Honda"), special: Sales };

console.log(car.myCar);   // Saturn
console.log(car.getCar);  // Honda
console.log(car.special); // Toyota

更进一步的,你可以使用数字或字符串字面值作为属性的名字,或者在另一个字面值内嵌套上一个字面值。如下的示例中使用了这些可选项。

var car = { manyCars: {a: "Saab", "b": "Jeep"}, 7: "Mazda" };

console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda

对象属性名字可以是任意字符串,包括空串。如果对象属性名字不是合法的javascript标识符,它必须用""包裹。属性的名字不合法,那么便不能用.访问属性值,而是通过类数组标记("[]")访问和赋值。

var unusualPropertyNames = {
  "": "An empty string",
  "!": "Bang!"
}
console.log(unusualPropertyNames."");   // 语法错误: Unexpected string
console.log(unusualPropertyNames[""]);  // An empty string
console.log(unusualPropertyNames.!);    // 语法错误: Unexpected token !
console.log(unusualPropertyNames["!"]); // Bang!
字符串字面量 (String literals)

字符串字面量是由双引号(")对或单引号(")括起来的零个或多个字符。字符串被限定在同种引号之间;也即,必须是成对单引号或成对双引号。下面的例子都是字符串字面值:

"foo"
"bar"
"1234"
"one line 
 another line"
"John"s cat"

你可以在字符串字面值上使用字符串对象的所有方法——JavaScript会自动将字符串字面值转换为一个临时字符串对象,调用该方法,然后废弃掉那个临时的字符串对象。你也能用对字符串字面值使用类似String.length的属性:

console.log("John"s cat".length) 
// 将打印字符串中的字符个数(包括空格) 
// 结果为:10

在ES2015中,还提供了一种模板字符串(template literals),模板字符串提供了一些语法糖来帮你构造字符串。这与Perl、Python还有其他语言中的字符串插值(string interpolation)的特性非常相似。除此之外,你可以在通过模板字符串前添加一个tag来自定义模板字符串的解析过程,这可以用来防止注入攻击,或者用来建立基于字符串的高级数据抽象。

// Basic literal string creation
`In JavaScript "
" is a line-feed.`

// Multiline strings
`In JavaScript this is
 not legal.`

// String interpolation
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Construct an HTTP request prefix is used to interpret the replacements and construction
POST`http://foo.org/bar?a=${a}&b=${b}
     Content-Type: application/json
     X-Credentials: ${credentials}
     { "foo": ${foo},
       "bar": ${bar}}`(myOnReadyStateChangeHandler);

除非有特别需要使用字符串对象,否则,你应当始终使用字符串字面值。

js流程控制与错误处理 条件判断语句

条件判断语句指的是根据指定的条件所返回的结果(真或假或其它预定义的),来执行特定的语句。JavaScript 支持两种条件判断语句:if...else和switch。

let iceCream = "chocolate";
if (iceCream === "chocolate") {
  alert("我最喜欢巧克力冰激淋了。");    
} else {
  alert("但是巧克力才是我的最爱呀……");    
}
异常处理语句

你可以用 throw 语句抛出一个异常并且用 try...catch 语句捕获处理它。

throw 语句

try...catch 语句

Promises

从 ECMAScript 6 开始,JavaScript 增加了对 Promise 对象的支持,它允许你对延时和异步操作流进行控制。

Promise 对象有以下几种状态:

pending:初始的状态,即正在执行,不处于 fulfilled 或 rejected 状态。

fulfilled:成功的完成了操作。

rejected:失败,没有完成操作。

settled:Promise 处于 fulfilled 或 rejected 二者中的任意一个状态, 不会是 pending。

展现了 Promise 的工作流

function imgLoad(url) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open("GET", url);
    request.responseType = "blob";
    request.onload = function() {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(Error("Image didn"t load successfully; error code:" 
                     + request.statusText));
      }
    };
    request.onerror = function() {
      reject(Error("There was a network error."));
    };
    request.send();
  });
}
循环和迭代

JavaScript中提供了这些循环语句:

for 语句

do...while 语句

while 语句

labeled 语句

break 语句

continue 语句

for...in 语句

for...of 语句

for 语句

一个for循环会一直重复执行,直到指定的循环条件为fasle。 JavaScript的for循环和Java与C的for循环是很相似的。

for ([initialExpression]; [condition]; [incrementExpression])
statement

当一个for循环执行的时候,会发生以下事件:

如果有初始化表达式initialExpression,它将被执行。这个表达式通常会初始化一个或多个循环计数器,但语法上是允许一个任意复杂度的表达式的。这个表达式也可以声明变量。

计算condition表达式的值。如果condition的值是true,循环中的statement会被执行。如果condition的值是false,for循环终止。如果condition表达式整个都被省略掉了,condition的值会被认为是true。

循环中的 statement被执行。如果需要执行多条语句,可以使用块 ({ ... })来包裹这些语句。

如果有更新表达式incrementExpression,执行它.

然后流程回到步骤2。

do...while

do...while 语句一直重复直到指定的条件求值得到假(false)。 一个 do...while 语句看起来像这样:

do
  statement
while (condition);

statement 在检查条件之前会执行一次。要执行多条语句(语句块),要使用块语句 ({ ... }) 包括起来。 如果 condition 为真(true),statement 将再次执行。 在每个执行的结尾会进行条件的检查。当 condition 为假(false),执行会停止并且把控制权交回给 do...while 后面的语句。

在下面的例子中, 这个 do 循环将至少重复一次并且一直重复直到 i 不再小于 5。

do {
  i += 1;
  console.log(i);
} while (i < 5);
while 语句

一个 while 语句只要指定的条件求值为真(true)就会一直执行它的语句块。一个 while 语句看起来像这样:

while (condition)

statement

如果这个条件变为假,循环里的 statement 将会停止执行并把控制权交回给 while 语句后面的代码。

条件检测会在每次 statement 执行之前发生。如果条件返回为真, statement 会被执行并紧接着再次测试条件。如果条件返回为假,执行将停止并把控制权交回给 while 后面的语句。

要执行多条语句(语句块),要使用块语句 ({ ... }) 包括起来。

例子 1
下面的 while 循环只要 n 小于 3就会一直执行:

var n = 0;
var x = 0;
while (n < 3) {
  n++;
  x += n;
}

在每次循环里, n 会增加1并被加到 x 上。所以, x 和 n 的变化是:

第一次完成后: n = 1 和 x = 1

第二次完成后: n = 2 和 x = 3

第三次完成后: n = 3 和 x = 6

在三次完成后, 条件 n < 3 结果不再为真,所以循环终止了。

例子 2
避免无穷循环(无限循环)。保证循环的条件结果最终会变成假;否则,循环永远不会停止。下面这个 while 循环会永远执行因为条件永远不会变成假:

while (true) {
  console.log("Hello, world");
}
函数

函数 用来封装可复用的功能。如果没有函数,一段特定的操作过程用几次就要重复写几次,而使用函数则只需写下函数名和一些简短的信息

浏览器内置函数和用户定义的函数

定义函数 函数声明

一个函数定义(也称为函数声明,或函数语句)由一系列的function关键字组成,依次为:

函数的名称。

函数参数列表,包围在括号中并由逗号分隔。

定义函数的 JavaScript 语句,用大括号{}括起来。

例如,以下的代码定义了一个简单的square函数:

function square(number) {
  return number * number;
}

函数square使用了一个参数,叫作number。这个函数只有一个语句,它说明该函数将函数的参数(即number)自乘后返回。函数的return语句确定了函数的返回值:

return number * number;

原始参数(比如一个具体的数字)被作为值传递给函数;值被传递给函数,如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用函数。

如果你传递一个对象(即一个非原始值,例如Array或用户自定义的对象)作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的,如下面的例子所示:

function myFunc(theObject) {
  theObject.make = "Toyota";
}

var mycar = {make: "Honda", model: "Accord", year: 1998};
var x, y;

x = mycar.make;     // x获取的值为 "Honda"

myFunc(mycar);
y = mycar.make;     // y获取的值为 "Toyota"
                    // (make属性被函数改变了)
函数表达式

虽然上面的函数声明在语法上是一个语句,但函数也可以由函数表达式创建。这样的函数可以是匿名的;它不必有一个名称。例如,函数square也可这样来定义:

var square = function(number) { return number * number; };
var x = square(4); // x gets the value 16

然而,函数表达式也可以提供函数名,并且可以用于在函数内部代指其本身,或者在调试器堆栈跟踪中识别该函数:

var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};

console.log(factorial(3));

当将函数作为参数传递给另一个函数时,函数表达式很方便。下面的例子演示了一个叫map的函数如何被定义,而后使用一个表达式函数作为其第一个参数进行调用:

function map(f,a) {
  var result = [],i; //创建一个新数组
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}

下面的代码:

function map(f, a) {
  var result = []; // 创建一个数组
  var i; // 声明一个值,用来循环
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
      return result;
}
var f = function(x) {
   return x * x * x; 
}
var numbers = [0,1, 2, 5,10];
var cube = map(f,numbers);
console.log(cube);

返回 [0, 1, 8, 125, 1000]。

在 JavaScript 中,可以根据条件来定义一个函数。比如下面的代码,当num 等于 0 的时候才会定义 myFunc :

var myFunc;
if (num == 0){
  myFunc = function(theObject) {
    theObject.make = "Toyota"
  }
}

除了上述的定义函数方法外,你也可以在运行时用 Function 构造器由一个字符串来创建一个函数 ,很像 eval() 函数。

当一个函数是一个对象的属性时,称之为方法。了解更多关于对象和方法的知识 使用对象。

调用函数

调用函数节
定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。调用函数才会以给定的参数真正执行这些动作。例如,一旦你定义了函数square,你可以如下这样调用它:

square(5);
上述语句通过提供参数 5 来调用函数。函数执行完它的语句会返回值25。

函数一定要处于调用它们的域中,但是函数的声明可以被提升(出现在调用语句之后),如下例:

console.log(square(5));
/* ... */
function square(n) { return n*n }

函数域是指函数声明时的所在的地方,或者函数在顶层被声明时指整个程序。

提示:注意只有使用如上的语法形式(即 function funcName(){})才可以。而下面的代码是无效的。就是说,函数提升仅适用于函数声明,而不适用于函数表达式。

console.log(square); // square is hoisted with an initial value undefined.
console.log(square(5)); // TypeError: square is not a function
var square = function (n) { 
  return n * n; 
}

函数的参数并不局限于字符串或数字。你也可以将整个对象传递给函数。函数 show_props(其定义参见 用对象编程)就是一个将对象作为参数的例子。

函数可以被递归,就是说函数可以调用其本身。例如,下面这个函数就是用递归计算阶乘:

function factorial(n){
  if ((n == 0) || (n == 1))
    return 1;
  else
    return (n * factorial(n - 1));
}

你可以计算1-5的阶乘如下:

var a, b, c, d, e;

a = factorial(1); // 1赋值给a
b = factorial(2); // 2赋值给b
c = factorial(3); // 6赋值给c
d = factorial(4); // 24赋值给d
e = factorial(5); // 120赋值给e

还有其它的方式来调用函数。常见的一些情形是某些地方需要动态调用函数,或者函数的实参数量是变化的,或者调用函数的上下文需要指定为在运行时确定的特定对象。显然,函数本身就是对象,因此这些对象也有方法(参考Function )。作为此中情形之一,apply()方法可以实现这些目的。

函数作用域

在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。换言之,定义在全局域中的函数可以访问所有定义在全局域中的变量。在另一个函数中定义的函数也可以访问在其父函数中定义的所有变量和父函数有权访问的任何其他变量。

// 下面的变量定义在全局作用域(global scope)中
var num1 = 20,
    num2 = 3,
    name = "Chamahk";

// 本函数定义在全局作用域
function multiply() {
  return num1 * num2;
}

multiply(); // 返回 60

// 嵌套函数的例子
function getScore() {
  var num1 = 2,
      num2 = 3;
  
  function add() {
    return name + " scored " + (num1 + num2);
  }
  
  return add();
}

getScore(); // 返回 "Chamahk scored 5"
作用域和函数堆栈 递归

一个函数可以指向并调用自身。有三种方法可以达到这个目的:

函数名

arguments.callee

作用域下的一个指向该函数的变量名

例如,思考一下如下的函数定义:

var foo = function bar() {
   // statements go here
};

在这个函数体内,以下的语句是等价的:

bar()

arguments.callee()

foo()

调用自身的函数我们称之为递归函数。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。例如以下的循环:

var x = 0;
while (x < 10) { // "x < 10" 是循环条件
   // do stuff
   x++;
}

可以被转化成一个递归函数和对其的调用:

function loop(x) {
  if (x >= 10) // "x >= 10" 是退出条件(等同于 "!(x < 10)")
    return;
  // 做些什么
  loop(x + 1); // 递归调用
}
loop(0);

不过,有些算法并不能简单的用迭代来实现。例如,获取树结构中所有的节点时,使用递归实现要容易得多:

function walkTree(node) {
  if (node == null) // 
    return;
  // do something with node
  for (var i = 0; i < node.childNodes.length; i++) {
    walkTree(node.childNodes[i]);
  }
}

跟loop函数相比,这里每个递归调用都产生了更多的递归。

将递归算法转换为非递归算法是可能的,不过逻辑上通常会更加复杂,而且需要使用堆栈。事实上,递归函数就使用了堆栈:函数堆栈。

这种类似堆栈的行为可以在下例中看到:

function foo(i) {
  if (i < 0)
    return;
  console.log("begin:" + i);
  foo(i - 1);
  console.log("end:" + i);
}
foo(3);

// 输出:

// begin:3
// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
// end:3
嵌套函数和闭包

你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。

既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。

可以总结如下:

内部函数只可以在外部函数中访问。

内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。

下面的例子展示了嵌套函数:

function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41

由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:

function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8
多层嵌套函数

函数可以被多层嵌套。例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为作用域链。(稍后会详细解释)

思考一下下面的例子:

function A(x) {
  function B(y) {
    function C(z) {
      console.log(x + y + z);
    }
    C(3);
  }
  B(2);
}
A(1); // logs 6 (1 + 2 + 3)

在这个例子里面,C可以访问B的y和A的x。这是因为:

B形成了一个包含A的闭包,B可以访问A的参数和变量
C形成了一个包含B的闭包
B包含A,所以C也包含A,C可以访问B和A的参数和变量。换言之,C用这个顺序链接了B和A的作用域
反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。

命名冲突

当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。

看以下的例子:

function outside() {
  var x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

outside()(10); // returns 20 instead of 10
命名冲突发生在return x上,inside的参数x和outside变量x发生了冲突。这里的作用链域是{inside, outside, 全局对象}。因此inside的x具有最高优先权,返回了20(inside的x)而不是10(outside的x)。

闭包

闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数将的生存周期比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。

var pet = function(name) {          //外部函数定义了一个变量"name"
  var getName = function() {            
    //内部函数可以访问 外部函数定义的"name"
    return name; 
  }
  //返回这个内部函数,从而将其暴露在外部函数作用域
  return getName;               
};
myPet = pet("Vivie");
    
myPet();                            // 返回结果 "Vivie"

实际上可能会比上面的代码复杂的多。在下面这种情形中,返回了一个包含可以操作外部函数的内部变量方法的对象。

var createPet = function(name) {
  var sex;
  
  return {
    setName: function(newName) {
      name = newName;
    },
    
    getName: function() {
      return name;
    },
    
    getSex: function() {
      return sex;
    },
    
    setSex: function(newSex) {
      if(typeof newSex == "string" 
        && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
pet.getName();                  // Vivie

pet.setName("Oliver");
pet.setSex("male");
pet.getSex();                   // male
pet.getName();                  // Oliver

在上面的代码中,外部函数的name变量对内嵌函数来说是可取得的,而除了通过内嵌函数本身,没有其它任何方法可以取得内嵌的变量。内嵌函数的内嵌变量就像内嵌函数的保险柜。它们会为内嵌函数保留“稳定”——而又安全——的数据参与运行。而这些内嵌函数甚至不会被分配给一个变量,或者不必一定要有名字。

var getCode = (function(){
  var secureCode = "0]Eal(eh&2";    // A code we do not want outsiders to be able to modify...
  
  return function () {
    return secureCode;
  };
})();

getCode();    // Returns the secret code

尽管有上述优点,使用闭包时仍然要小心避免一些陷阱。如果一个闭包的函数用外部函数的变量名定义了同样的变量,那在外部函数域将再也无法指向该变量。

var createPet = function(name) {  // Outer function defines a variable called "name"
  return {
    setName: function(name) {    // Enclosed function also defines a variable called "name"
      name = name;               // ??? How do we access the "name" defined by the outer function ???
    }
  }
}
箭头函数 this 的词法

在箭头函数出现之前,每一个新函数都重新定义了自己的 this 值(在严格模式下,一个新的对象在构造函数里是未定义的,以“对象方法”的方式调用的函数是上下文对象等)。以面向对象的编程风格,这样着实有点恼人。

function Person() {
  // The Person() constructor defines `this` as itself.
  this.age = 0;

  setInterval(function growUp() {
    // In nonstrict mode, the growUp() function defines `this` 
    // as the global object, which is different from the `this`
    // defined by the Person() constructor.
    this.age++;
  }, 1000);
}

var p = new Person();

在ECMAScript 3/5里,通过把this的值赋值给一个变量可以修复这个问题。

function Person() {
  var self = this; // Some choose `that` instead of `self`. 
                   // Choose one and be consistent.
  self.age = 0;

  setInterval(function growUp() {
    // The callback refers to the `self` variable of which
    // the value is the expected object.
    self.age++;
  }, 1000);
}

另外,创建一个约束函数可以使得 this值被正确传递给 growUp() 函数。

箭头函数捕捉闭包上下文的this值,所以下面的代码工作正常。

function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // |this| properly refers to the person object
  }, 1000);
}

var p = new Person();
预定义函数

JavaScript语言有好些个顶级的内建函数:

eval()

eval()方法会对一串字符串形式的JavaScript代码字符求值。

uneval()

uneval()方法创建的一个Object的源代码的字符串表示。

isFinite()

isFinite()函数判断传入的值是否是有限的数值。 如果需要的话,其参数首先被转换为一个数值。

isNaN()

isNaN()函数判断一个值是否是NaN。注意:isNaN函数内部的强制转换规则十分有趣; 另一个可供选择的是ECMAScript 6 中定义Number.isNaN() , 或者使用 typeof来判断数值类型。

parseFloat()

parseFloat() 函数解析字符串参数,并返回一个浮点数。

parseInt()

parseInt() 函数解析字符串参数,并返回指定的基数(基础数学中的数制)的整数。

decodeURI()

decodeURI() 函数对先前经过encodeURI函数或者其他类似方法编码过的字符串进行解码。

decodeURIComponent()

decodeURIComponent()方法对先前经过encodeURIComponent函数或者其他类似方法编码过的字符串进行解码。

encodeURI()

encodeURI()方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的某些字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。

encodeURIComponent()

encodeURIComponent() 方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的每个字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/110263.html

相关文章

  • 复习Javascript专题(二):闭包,内存,以及垃圾回收机制

    摘要:一个闭包就是当一个函数返回时,一个没有释放资源的栈区所以参数和变量不会被垃圾回收机制回收。使用不当会很容易造成内存泄露。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 1.什么是闭包?闭包有啥特性以及存在什么问题? 概念:闭包是指有权访问另一个函数作用域中的变量的函数。下面的outer就形成了一个闭包: function outer(){ co...

    hankkin 评论0 收藏0
  • 【重温基础】1.语法和数据类型

    摘要:语法和数据类型正文开始本章节复习的是中的基本语法,变量声明,数据类型和字面量。声明一个块作用域的局部变量,可赋一个初始值。变量声明有三种方式如,声明局部变量和全局变量。 最近开始把精力放在重新复习JavaScript的基础知识上面,不再太追求各种花枝招展的前端框架,框架再多,适合实际项目才是最重要。 上星期在掘金发布了几篇文章,其中最大块算是 【复习资料】ES6/ES7/ES8/ES...

    Darkgel 评论0 收藏0
  • 一次爬虫实践记录

    摘要:在和伟锋探讨的时候,我突然想到了可以用爬虫的手段,来将数据中心的数据全部爬取出来。这里做一个技术记录,中间涉及到的知识点包括如何通过中的,以及维持登陆状态,以爬取不同的需要登陆以后才能查看的网页。 前言说明: 公司在2017年3月的时候开发过一个「数据中心」,是将有赞的用户和订单信息通过API拉取到自己开发的网站上,再结合我们自己和用户沟通的信息,组成一个简单的用户管理中心。数据中心虽...

    hzx 评论0 收藏0
  • 学不动了?可能方法不太对-Grid 网格布局

    摘要:前情提要本人是一个学渣非科班入行年了吧前端东西真的好多啊又不断更新需要不断的学学学在去年年底开始我就开始不断的寻找学习的方法如何更加高效的学习如何才能学的又快又好在这半年来不断的总结慢慢找到了一些方法和诀窍此文章不是网格布局的教学文章只前情提要   本人是一个学渣,非科班入行2年了吧,前端东西真的好多啊,又不断更新.需要不断的学学学, showImg(https://user-gold-c...

    happyfish 评论0 收藏0

发表评论

0条评论

xuexiangjys

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<