参考 学习网站
- 廖雪峰的JavaScript教程
- w3cshool
- 阮一峰的JavaScript全栈工程师培训教程
- W3C
- WebAIM
- 海棠学院 全栈Web前端工程师进阶班 入门到精通 ——付费
术语
- ECMAScript
European Computer Manufacturer's Association
欧洲计算机制造商协会 - DOM
Document Object Model
浏览器所开放的底层的文档对象模型 - CSS
Cascading Style Sheet
级联样式表
前言
本文着重JavaSript语言的两点特性,即函数和对象,其他如变量,运算符,语句
基本语法
- 分号 ;
JavaScript的语法和Java语言类似,每个语句以 ; 结束 ,
以//开头直到行末的字符被视为行注释 另一种块注释是用/.../把多行字符包裹起来
var x = 1;
"Hello world";
var x = 1; var y = 2; // 不建议一行写多个语句!
/*
语句块用{...}
{...}还可以嵌套,形成层级结构
*/
if (2 >1) {
x = 1;
y = 2;
z = 3;
if (x < y) {
z = 4;
}
if (x > y) {
z = 5;
}
}
变量
运算符
语句
函数
定义函数
在JavaScript中,定义函数的方式如下:
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
上述 abs()函数的定义如下:
- function 指出这是一个函数定义
- abs 是函数的名称
- (x) 括号内列出函数的参数, 多个参数以 , 分隔
- { ... }之间的代码是函数体,可以包含若干语句,甚至可以没有语句
请注意,函数体内部的语句在执行时,一旦执行到 return 时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。
如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。
由于JavaScript的函数也是一个对象,上述定义的 abs() 函数实际上是一个函数对象,而函数名 abs 可以视为指向该函数的变量。
因此,第二种定义函数的方式如下:
var abs = function (x) {
if ( x >= 0) {
return x;
}else {
return -x;
}
};
在这种方式下, function (x) { ... } 是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量 abs , 所以, 通过变量 abs 就可以调用该函数。
上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个; , 表示赋值语句结束。
调用函数
调用函数时,按顺序传入参数即可:
abs(10); // 返回10
abs(-9); // 返回9
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题
abs(10, 'hilohi'); // 返回10
abs(-9, 'hi','lo',null); //返回9
传入的参数比定义的少也没有问题
abs(); // 返回NaN
此时 abs(x) 函数的参数 x 将收到 undefined ,计算结果为 NaN。
要避免收到 undefined , 可以对参数进行检查:
function abs ( x ) {
if (typeof x !== 'number') {
throw 'Not a number'
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
arguments
JavsScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。 arguments 类似 Array 但它不是一个Array
function foo (x) {
alert (x); // 10
for (var i =0; i < arguments.length; i++) {
arert(arguments[i]); // 10,20,30
}
}
foo(10,20,30);
利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,也是可以拿到参数的值
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
实际上arguments最常用于判断传入参数的个数。你可能回看到这样的写法:
//foo(a [, b],c)
//接收2-3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo (a, b, c) {
if (arguments.length === 2) {
//实际拿到的参数是 a和b , c 为undefined
c = b; // 把 b 赋给 c
b = null; // b 变为默认值
}
// ...
}
要把中间的参数b变为'可选'参数,就只能通过arguments判断,然后重新调整参数并赋值。
rest参数
由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments来获取所有参数:
function foo (a , b) {
var i ,rest = [ ];
if (arguments.length > 2) {
for (i = 2; i < arguments.length ; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
为了获取除了已定义参数a b之外的参数,我们不得不用arguments,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest参数,有没有更好的方法?
ES6标准引入了rest参数,上面的函数可以改写为:
function foo (a,b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果"
// a = 1
// b = 2
// Array [ 3, 4, 5]
foo(1);
//结果:
//a = 1
//b = undefined
//Array [ ]
rest参数只能写在最后,前面用 ... 标识,从运行结果可知,传入的参数先绑定a 、b,多余的参数以数组形式交给变量rest , 所以,不再需要arguments我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组 (注意不是undefined)
小心你的return语句
前面我们讲到了JavaScript引擎有一个行末自动添加分号的机制,这可能让你栽到return语句的一个大坑
function foo() {
return { name : 'foo'};
}
foo(); //{name: 'foo'}
如果把return语句拆成两行
function foo() {
return
{ name : 'foo'};
}
foo(); // undefined
要小心了,由于JavaScript引擎在行末自动添加分号的机制,上面的代码实际变成了
function foo() {
return; //自动添加了分号,相当于return undefined;
{ name: 'foo'}; //这行语句已经没法执行到了
}
所以正确的多行写法是
function foo() {
return {
name : 'foo'
};
}
变量作用域
在JavaScript中,用 var 申明的变量实际上是有作用域的。
如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量
'use strict';
function foo() {
var x = 1;
x = x + 1;
}
x = x + 2; // ReferenceError! 无法在函数体外引用变量x
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内作用。换句话说,不同函数内部的同名变量互相独立,互不影响
'use strict'
function foo() {
var x = 1;
x = x + 1;
}
function bar() {
var x = 'A';
x = x + 'B';
}
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:
'user strict';
function foo() {
var x = 1;
function bar() {
var y = x + 1; //bar 可以访问foo的变量x!
}
var z = y + 1; //ReferenceError! foo不可以访问bar的变量y!
}
如果内部函数和外部函数的变量重名怎么办?
'use strict';
function foo( ) {
var x = 1;
function bar( ) {
var x = 'A';
alert('x in bar( ) = ' + x ); // 'A'
}
alert('x in foo( ) = ' + x); // 1
bar( );
}
这说明JavaScript的函数在查找变量时从自身函数定义开始,从"内"向"外"查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将"屏蔽"外部函数的变量。
变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量"提升"到函数顶部:
'use strict'
function foo( ) {
var x = 'Hello' + y;
alert(x);
var y = 'Bob';
}
foo( );
虽然是strict模式,但语句 var x = 'Hello' + y; 并不报错,原因是变量y在稍后申明了。但是alert显示Hello undefied,说明变量y的值为udefined。这正是因为JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。
对于上述 foo( )函数,JavaScipt引擎看到的代码相当于:
function foo ( ) {
var y; //提升变量y的申明
var x ='Hello, ' + y;
alert(x);
y = 'Bob';
}
由于JavaScipt的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个 var 申明函数内部用到的所有变量:
function foo ( ) {
var
x = 1, // x 初始化为1
y = x + 1,// y 初始化为2
z,i; //z 和 i 为undefined
//其他语句
foo (i =0; i < 100;i ++){
...
}
}
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:
'use strict';
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
因此,直接访问全局变量course和访问window.course是完全一样的。
你可能猜到了,由于函数定义有两种方式,以变量方式var foo = function ( ) { } 定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:
'use script'
function foo ( ) {
alert( 'foo' );
}
foo (); // 直接调用foo()
window.foo(); // 通过window.foo()调用
进一步大胆地猜测,我们每次直接调用的alert()函数其实也是 window的一个变量:
'use strict';
window.alert(' 调用window.alert( )');
//把alert保存到另一个变量:
var old_alert = window.alert;
//给alert赋一个新函数;
window.alert = function ( ) { }
alert( '无法用alert( )显示了!');
//恢复alert
window.alert = old_alert;
alert('又可以用alert( )了!');
这说明JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,会报ReferenceError错误。
名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法就是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
//唯一的全局变量MYAPP
var MYAPP = { };
//其他变量
MYAPP.name = ''myapp;
MYAPP.version = 1.0;
//其他函数;
MYAPP.foo = function ( ) {
return 'foo';
}
把自己的代码全部放入唯一的名字空间 MYAPP 中,会大大减少全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery, YUI, underscore等等。
局部作用域
由于JavaScript的变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作用域的变量的“
'use strict'
function foo ( ) {
for (var i=0; i<100; i++) {
//
}
i += 100; //仍然可以引用变量i
}
为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:
'use strict';
function foo( ) {
var sum = 0;
for (let i=0; i<100; i++){
sum += i;
}
i += 1; //SyntaxError
}
常量
由于var 和 let申明的是变量 ,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
var PI = 3.14;
ES6标准引入了新的关键字 const 来定义常量, const 与 let都具有块级作用域:
'use strict'
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; //3.14
方法
在一个对象中绑定函数,称为这个对象的方法。
在JavaScript中,对象的定义是这样的:
var xiaoming = {
name: '小明',
birth:1990
}
但是,如果我们给 xiaoming 绑定一个函数,就可以做更多的事情。比如,写个 age( ) 方法,返回 xiaoming 的年龄
var xiaoming = {
'name:'小明,
'birth':1990
age: function ( ) {
var y = new Date( ).getFullYear( )'
return y - this.birth;
}
}
xiaoming.age; //function xiaoming.age()
xiaoming.age(); //今年调用是25,明年调用就编程26了
绑定到对象上的函数成为方法,和普通函数也没啥区别,但是它在内部使用了一个 this 关键字,这个东东是什么?
在一个方法内部,this 是一个特殊变量,它始终指向当前对象,也就是 xiaoming 这个变量。所以,this.birth可以拿到 xiaoming 的 birth属性。
让我们拆开写
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name:'小明',
birth:1990;
age:getAge
}
xiaoming.age(); //25,正常结果
getAge(); // NaN
单独调用函数getAge()怎么返回了NaN?请注意,我们已经进入到了JavsScript的一个大坑里。
JavaSript的函数内部如果调用了this,那么这个this到底指向谁?
答案是,视情况而定!
如果以对象的方法形式调用,比如xiaoming.age(),该函数的 this 指向被调用的对象,也就是xiaoming,这是符合我们预期的。
如果单独调用函数,比如getAge(),此时,该函数的 this 指向全局对象,也就是 window.
坑爹啊!
更坑爹的是,如果这么写
var fn = xiaoming.age; //先拿到xiaoming的age函数
fn();// NaN
也是不行的,要保证this指向正确,必须用obj.xxx()的形式调用!
由于这是一个巨大的设计错误,要想纠正可以没那么简单。ECMA决定,在 strict 模式下让函数的 this 指向 undefined,因此,在strict模式下,你会得到一个错误:
'use scrict'
var xiaoming = {
name: '小明',
birth:'1990',
age: function(){
var y = new Date().getFullYear();
return y - this.birth;
}
}
var fn = xiaoming.age;
fn(); //Uncaught TypeError:Cannot read property 'birth' of undefined
这个决定只是让错误及时暴露出来,并没有解决 this 应该指向的正确位置。
有些时候,喜欢重构的你把方法重构了一下:
'use strict'
var xioaming = {
name:'小明',
birth:'1990'
age: function (){
function getAgeFromBirth(){
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
}
xiaoming.age();//Uncaught TypeError:Cannot read the property 'birth' of undefiend
结果又报错了!原因是 this 指针只在age方法的函数内指向xiaoming ,在函数内部定义的函数,this又指向 undefined 了,(在非strict模式下,它重新指向全局对象window!)
修复的办法也不是没有,我们用一个 that 变量首先捕获 this:
'use strcit'
var xiaoming = {
name:'小明',
birth:1990,
age:function (){
var that = this // 在方法内部一开始就捕获this
function getAgeFromBirth(){
var y = new Date().getFullYear();
retun y - that .birth ;// 用 that 而不是 this
}
retun getAgeFromBirth();
}
}
xiaoming.age(); //25
用var that = this;你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。
apply
虽然在一个独立的函数调用中,根据是否是strict模式,this指向undefined或window,不过,我们还是可以控制 this 的指向的。
要指定函数的 this 指向哪个对象,可以用函数本身的 apply方法,它接收两个参数,第一个参数就是需要绑定的 this 变量,第二个参数是 Array,表示函数本身的参数。
用apply修复getAge()调用:
function getAge() {
var y = new Date().getFulYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age:getAge
}
xiaoming.age(); //25
getAge.apply(xiaoming,[ ]); //25,this指向xiao'ming,参数为空
另一个与apply()类似的方法是call(),唯一的区别是:
- apply()把参数打包成Array再传入;
- call()把参数按顺序传入
比如调用Math.max(3,5,4),分别用apply()和call()实现如下:
Math.max.apply(null,[3,5,4]); //5
Math.max.call(null,3,5,4); //5
对普通函数调用,我们通常把 this 绑定为null.
装饰器
利用apply(),我们还可以动态改变函数的行为。
Javascript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次 parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():
var count = 0;
var oldParseInt = parseInt; //保存原函数
window.parseInt = function (){
count += 1;
return oldParseInt.apply(null, arguments); //调用原函数
};
//测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; //3
高阶函数
高阶函数英文叫Higher-order-function.那么什么是高阶函数?
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数
function add(x,y,f) {
return f(x) + f(y);
}
当我们调用 add(-5, 6, Math.abs)时,参数x, y和 f 分别接收-5 ,6和函数 Math.asb, 根据函数定义,我们可以推导计算过程为:
x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;
用代码验证下
add(-5, 6, Math.abs); //11
编写高阶函数,就是让函数的参数能够接收别的函数。
map/reduce
如果你多过Google的那篇大名鼎鼎的论文"MapReduce:Simplified Data Processing on Large Clusters",你就能大概明白map/reduce的概念。
map
举例说明,比如我们有一个函数f(x)=x2
要把这个函数作用在一个数组[1,2,3,4,5,6,7,8,9]上,就可以用map实现如下:
由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的array作为结果:
function pow(x){
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1,4,9,16,25,36,49,64,81]
map()传入的参数是pow,即函数对象本身。
你可能会想,不需要map(),写一个循坏,也可以计算出结果:
var f = function(x){
return x*x;
}
var arr = [1,2,3,4,5,6,7,8,9];
var result = [ ];
for (var i = 0; i < arr.length; i ++){
result.push(f(arr[i]));
}
的确可以,但是,从上面的循坏代码,我们无法一眼看明白“把f(x)作用在Array的每一个元素并把结果生成一个新的Array”。
所以,map() 作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串:
var arr = [1,2,3,4,5,6,7,8,9];
arr.map(String); //['1','2','3','4','5','6','7','8','9'];
只需一行代码
reduce
再看reduce的用法。Array的reduce()把一个函数作用在这个Array的[x1,x2,x3... ] 上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果是:
[x1,x2,x3,x4].reduce(f) = f(f(f(x1,x2),x3),x4)
比方说对一个Array求和,就可以用reduce实现:
var arr = [1,3,5,7,9];
arr.reduce(function (x ,y){
return x +y;
}); //25
要把[1,3,5,7,9]变换成整数13579,reduce()也能派上用场:
var arr = [1,3,5,7,9];
arr.reduce( function (x, y) {
return x*10 + y ;
} )
filter
filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。
和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
例如,在一个Array中,删掉偶数,只保留奇数,可以这么写;
var arr = [1,2,4,5,6,9,10,15];
var r = arr.filter(function (x){
return x % 2 != 0;
});
r; //[1,5,9,15]
把一个Array中的空字符串删掉,可以这么写:
var arr = ['A','','B',null,undefined,'C',' '];
var r = arr.filter(function (s) {
return s && s.trim( ); //注意:IE9以下的版本没有trim()方法
});
arr; //['A','B','C']
可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
回调函数
filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
var arr = ['A','B','C'];
var r = arr.filter(function(element,index,self){
console.log(element); //依次打印'A','B','C'
console.log(index); // 依次打印0,1,2
console.log(self); //self就是变量arr
return true;
});
利用 filter,可以巧妙地去除 Array 的重量元素
'use strict';
var arr = ['apple','starwberry','banana','pear','apple','orange','orange','strawberry'];
r = arr.filter(function(element,index,self){
return self.indexof(element) === index;
}
);
去除重复元素依靠的是indexof总是返回第一个元素的位置,后续的重复元素位置与indexof返回的位置不相等,因此被filter过滤掉了。
sort
排序算法
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者是两个对象呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素x和y,如果认为x
JavaScript的Array的sort()方法就是用于排序的,但是排序结果可能让你大吃一惊
//看上去正常的结果:
['Google','Apple','Microsoft'].sort( ); // ['Apple','Google','Microsoft'];
// apple排在了最后:
['Google','apple','Microsoft'].sort( ); //['Google','Micorsoft','apple'];
//无法理解的结果:
[10,20,1,2].sort( ); // [1, 10, 2, 20]
第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。
第三个排序结果是什么鬼?简单的数字排序都能错?
这是因为Array的sort()方法默认把所有元素先转换为String再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的ASCII码小。
如果不知道sort()方法的默认排序规则,直接对数字排序,绝对载进坑里!
幸运的是,sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
要按数字大小排序,我们可以这么写:
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y){
return -1;
}
if(x > y){
return 1;
}
return 0;
}); //[1, 2, 10 ,20]
如果要倒序排序,我们可以把大的数放前面:
var arr = [10,20,1,2];
arr.sort(function(x,y){
if (x < y){
return 1;
}
if (x > y){
return -1;
}
return 0;
}); // [20,10,2,1]
默认情况下,对字符串排序,是按照ASCII的大小比较的,现在,我们提出排序应该忽略大小写,按照字母序排序,要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以
var arr = ['Google','apple','Microsoft'];
arr.sort(function(s1,s2){
x1 = s1.toUpperCase();
x2 = s2.toUpperCase();
if(x1 < x2){
return -1;
}
if(x1 > x2){
return 1;
}
return 0;
}); //['apple','Google','Microsoft'];
忽略大小写比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。最后友情提示,sort()方法会直接对Array进行修改,它返回的结果仍是当前Array:
var a1 = ['B','A','C'];
var a2 = a1.sort();
a1; //['A','B','C']
a2; //['A','B','C']
a1 === a2; //true a1和a2是同一个对象
闭包
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。我们来实现一个对Array的求和。通常情况下,求和的函数式这样定义的:
functin sum(arr){
return arr.reduce(
function(x,y){
return x +y;
}
);
}
sum([1,2,3,4,5]);//15
但是,如果不需要立刻求和, 而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!
function lazy_sum(arr) {
var sum = function(){
return arr.reduce (function (x,y){
return x + y;
});
}
return sum;
}
当我们调用lazy_sum时,返回的并不是求和结果,而是求和函数
var f = lazy_sum([1,2,3,4,5]); //function sum()
调用函数f时,才真正计算求和的结果
f(); //5
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sumd的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum时,每次调用都会返回一个新的函数,即使传入相同的参数:
var f1 = lazy_sum([1,2,3,4,5]);
var f2 = lazy_sum([1,2,3,4,5]);
f1 === f2; // false
f1() 和f2()的调用结果互不影响
闭包
注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
function count() {
var arr = [ ];
for(var i = 1; i <=3; i++){
arr.push(function(){
return i *i;
}
);
return arr;
}
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中,每次循环,都创建了一个新的函数,然后,
把创建的3个函数都添加到一个Array中返回了。
你可能认为调用f1(), f2()和f3() 结果应该是1,4,9,但实际结果是:
f1( ); // 16
f2( ); // 16
f3( ); // 16
全部都是16! 原因就在于返回的函数引用了变量 i,但它并非立刻执行。等到三个函数都返回时,他们所引用的变量 i 已经变成了 4,因此最终结果为16.
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count ( ) {
var arr = [ ];
for (var i = 1; i <= 3; i++){
arr.push()
}
}
对象
对象
ECMA-262把对象(object)定义为“属性的无序集合,每个属性存放一个原始值、对象、或函数”。严格来说,这意味着对象是无特定顺序的值的数组。
尽管ECAMScript如此定义对象,但它更通用的定义是基于代码的名词(人、地点、或事物)的表示。
类
每个对象都由类定义,可以把类看做对象的配方。类不仅要定义对象的接口(interface)(开发者访问的属性和方法),还要定义对象的内部工作(使属性和方法发挥作用的代码)。编译器和解释程序都根据类的说明创建对象。
实例
程序使用类创建对象时,生成的对象叫做类的实例(instance)