1、比较运算“==”和“===”区别:
“==”比较,他会自动转换数据类型再比较,很多时候会得到非常诡异的结果。
“===”比较,他不会自动转换数据类型,如果数据类型不一致,返回false。如果类型一致,再比较。
“==”比较是JavaScrpit早期语言设计存在设计缺陷只是为了兼容而保留,所以,现在应该尽可能使用“===”,而尽量避免使用“==”。

2、NaN是特殊的数值。

NaN与所有其他值都不相等,包括它自己,所以判断是否为NaN的方法是通过isNaN()函数:

NaN === NaN; //false
var a = NaN;
a === NaN;   //false
isNaN(a);   //true

3、数组索引以0为起始

4、JavaScript对象

var person = {
    name: 'Bob',
    age: 20,
    tags: ['js', 'web', 'mobile'],
    city: 'Beijing',
    hasCar: true,
    zipcode: null
};

访问对象属性使用“对象变量名.属性名”。

5、strict模式:强制要求使用var 申明变量。

在代码第一行写上:

'use strict';

严格模式为ES5后出现,所以,IE6,7,8,9 均不支持严格模式。

6、字符串可以使用'或者"括起来,如果字符串内同时包含'和"可以用转义字符\来标识。

转义字符\本身在字符串中也要转移所以,字符串中\\表示\,描述windows中的路径时要注意。
可以使用\x##形式表示ASCII字符,还可以用\u####表示一个Unicode字符:

'use strict';
var i = '\u4e2d\u6587\x41';
console.log(i);

ES6标准新增:
可以用反单引号括起可实现多行字符串

var a = `这是第一行
这是第二行
这是第三行`;
console.log(a);
/*
这是第一行
这是第二行
这是第三行
*/

模板字符串

var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`; //注意是反单引号
console.log(message);

7、操作字符串

.length

var s = 'Hello, world!';
s.length;

要获取字符串某个位置的支付可以使用类似数组下标的操作,索引号从0开始:

var s = 'Hello, world!';
s[0];

但是,这种方式不能用来修改字符串。

.toUpperCase()

字符串变大写字符串

var s = 'Hello';
s = s.toUpperCase(); // 返回'HELLO'
console.log(s);

.toLowerCase()

字符串变小写字符串

var s = 'Hello';
s = s.toLowerCase(); // 返回'hello'
console.log(s);

.indexOf()

指定字符串出现的位置,以0为起始,没找到返回-1;

var s = 'hello, world';
i1 = s.indexOf('world'); // 返回7
i2 = s.indexOf('World'); // 没有找到指定的子串,返回-1
console.log(`第一位置:${i1},第二位置:${i2}`);

.substring()

根据索引获取子串

var s = 'hello, world'
sub1 = s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
sub2 = s.substring(7); // 从索引7开始到结束,返回'world'
console.log(`第一:${sub1},第二:${sub2}`);

8、数组

数组可以包含任意数据类型,并通过索引来访问每个元素。
要取得数组的长度,直接访问length属性:

var arr = [1, 2, 3.14, 'Hello', null, ture];
arr.length; //6

注意,直接给数组的length赋一个新的值会导致数组大小的变化:

var arr = [1, 2, 3];
arr.length;
arr.length = 6;
arr;  // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; //arr变为[1, 2]

大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的Array却不会有任何错误。在编写代码时,不建议直接修改数组的大小,访问索引时要确保索引不会越界。

.indexOf()

与String类似,数组也可以通过indexOf()来搜索一个指定的元素的位置:

var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); //元素10的索引为0
arr.indexOf(20); //元素20的索引为1
arr.indexOf(30); //元素30的没有找到,返回-1
arr.indexOf('30'); //元素'30'的索引为2

注意了,数字30和字符串'30'是不同的元素

.slice()

slice()就是对应String的substring()版本,它截取数组的部分元素,然后返回一个新的数组:

var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3:['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束:['D', 'E', 'F', 'G']

如果不给slice()传递参数,他会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个数组。

var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();
aCopy; //['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; //false 

.push()和.pop()

push向数组的末尾添加一到多个元素,pop则把数组的最后一个元素删除掉:

var arr = [1, 2];
arr.push('A', 'B'); // 返回数组新的长度:4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop() // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []

.unshift()和.shift()

如果要往数组的头部添加若干元素,使用unshift()方法,shift()方法则把数组的第一个元素删掉。

var arr = [1, 2];
arr.unshift('A', 'B'); // 返回 Array新的长度:4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1,  2]
arr.shift(); arr.shift(); arr.shift();  // 连续shift 3次
arr; //[]
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []

.sort()

sort可以对当前数组进行排序,它会直接修改当前数组的元素位置,直接调用时,按照默认顺序排序。

var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']

.reverse()

reverse把整个数组的元素反转

var arr = [1, 2, 3];
arr.reverse();
arr; //[3, 2, 1]

.splice()

splice方法是修改数组的万能方法,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素

var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后在索引2开始添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除,相当于插入:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 在数组尾部追加
arr.splice(5, 0, 'Yahoo', 'AOL'); // 返回[]
arr; // ['Microsoft',  'Apple',  'Google',  'Facebook',  'Oracle', 'Yahoo', 'AOL']

.concat()

concat方法把当前的数组和另外一个数据包括数组链接起来,并返回一个新的数组

var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]

concat方法并没有修改当前数组, 而是返回了一个新的数组。

.join()

join方法是一个非常实用的方法,它把当前数组的每个元素都用指定的字符串连接起来,然后返回连接后的字符串

var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3' 可见数组元素不是字符串将转换为字符串后再连接

9、多维数组

如果数组的某个元素又是一个数组,则可以形成多维数组

var arr = [[1, 2, 3], [400, 500, 600], '-'];
var x = arr[1][1];
x; // 500

10、对象

var xiaoming = {
    name: '小明',
    birth: 1990,
    school:  'No.1 Middle School',
    height: 1.70,
    weight: 65,
    score: null
};

注意,最末尾的键值对后面不要加','号。

JS用花括号定义一个对象,属性以键值对形式声明,用逗号隔开。可以动态的以xxx.newname = value的形式给对象添加属性。
属性名称推荐以变量名规则命名,不要包含特殊字符,'-'号也是特殊字符。访问不存在的属性,会返回undefined。

删除属性,delete
判断对象中是否有某个属性,可以用in,但是,in判断的属性不一定是属于该对象,也可能是继承得到的。如果判断属性是否是对象自身拥有的,可以使用hasOwnProperty()方法。

xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

11、条件判断

if(){
    ......
}else{
    ......
}

建议使用花括号,即使其中只有一条语句。

多行条件判断

if () {
    ......
} else if () {
    ......
} else {
    ......
}

if的判断表达式通常是以布尔值true或false来判断,但是也有用其他值得情况。
JavaScript把null、undefined、0、NaN和空字符串''视为false,其他值一概视为true。

12、循环

传统for循环

for(let i=1; i<=1000; i++){
    ......
}

如果for循环中的三个表达式都省略,则可以实现无限循环for(;;){...}。无限循环要用break语句才能退出。

for ... in循环

把一个对象的所有属性一次循环出来。

var o ={
    name: 'Jack',
    age: 20,
    city: 'Beijing'
};
for (var key in o){
    console.log(key); // 'name', 'age', 'city'
}

数组也是对象,所以当用for...in对数组使用的时候,每个元素的索引就是属性

var a = ['A', 'B', 'C'];
for (var i in a){
    console.log(i);
    console.log(a[i]);
}

注意,for...in 对数组的循环得到的是String而不是Number。

while循环和do...while循环
没事什么特别的,略。

13、Map和Set

ES6规范引入的数据类型。

Map是一组键值对,具有极快的查找速度。

var m=new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
m.set('Bob', 77); // 一个键只对应一个值,当对已有键设置值时,新值会换掉旧值,Bob 的值变为 77
m.has('Adam'); // false,检查是否有键'Adam'
m.set('Adam',65); //不存在的键,自动增加
m.delete('Adam'); //删除键'Adam'
m.get('Adam'); // undefined

Set是一组键的集合,没有value。键不能重复,如果有重复会自动过滤。

var s1 = new Set([1, 2, 3, 4, 3, '3']);
s1; // [1, 2, 3, 4, '3'] 重复的3被过滤掉,注意数字3和字符串3不是重复项
s1.add(6); //增加新key
s1.delete(6);//删除key

14、iterable

ES6标准一如的新类型,Array、Map和Set都属于iterable类型。具有iterable类型的集合可以通过for ... of循环来遍历。

'use strict';
var a = [1, 2, 3];
for (var x of a){
    console.log(x);
}

遍历iterable类型还可以使用内置的forEach方法。

var a = ['A', 'B', 'C'];
a.forEach(function(element, index, array) {
    // element: 指向当前元素的值
    // index: 指向当前索引
    // array: 指向Array对象本身
    console.log(element + ', index = ' + index);
}

15、函数

函数的形式 function 函数名(参数列表){......}
函数也是对象,可以赋值给变量,然后通过变量调用。
函数允许接收任意个参数。
关键字 arguments,是一个参数数组,在函数内部起作用,指向传入的所有参数,有length属性,可以通过下标访问。

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 []

注意,函数中return语句要只占一行,否则可能会出现意想不到的错误。

function foo(){
    if(2 > 1){
        return  // JavaScript引擎有一个在行末自动添加分号的机制,这里相当于return;
                2;
    }
}
foo();
//结果为undefined

16、变量作用域

如果一个变量在函数内部进行var声明,则改变量作用域为整个函数体,在函数体外不可引用改变量:

'use strict';
function foo() {
    var x =1;
    x = x + 1;
}

x = x + 2; // ReferenceError 无法在函数体外引用变量x

JavaScript的函数是可以嵌套的,因此,内部函数可以访问外部函数定义的变量,反过来不行:

'use strict';
function foo() {
    var x = 1;
    function bar() {
        var y = x+1; // bar 可以访问foo的变量x
    }
    var z = y + 1; // ReferenceError foo不可以访问bar的变量y
}

如果内部函数和外部函数定义了同名变量,则内部函数中变量会屏蔽外部函数变量。

变量提升

JavaScript的函数在使用时,会先扫描整个函数体的语句,把所有声明的变量“提升”到函数顶部:

'use strict';

function foo() {
    var x = 'Hello, '+y; //  因为经过扫描,JavaScript知道y已经声明了,所以不会报错,但是此时y是没有值,是undefined
    console.log(x);
    var y = 'Bob';
}
foo();

所以上述的foo函数,相当于:

function foo() {
    var x,y;
    x = 'Hello, '+y;
    console.log(x);
    y = 'Bob';
}

因为JavaScript有这样的特性,所以我们在函数内部定义变量时,应该模仿pascal在函数中先声明所有的变量,在进行使用。

全局作用域

不在任何函数内定义的变量就具有全局作用域。JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 与上一句是一样的

顶层函数实际上也是一个全局变量并绑定到window对象:

'use strict';

function foo() {
    alert('foo');
}
foo();
window.foo();

JavaScript默认的函数其实也是window的一个变量,我们可以通过修改变量达到重新定义默认函数的目的:

'use strict';

var old_alert = window.alert;
window.alert = function() {}
alert('无法用alert()显示了');
//恢复alert;
window.alert = old_alert;
alert('又可以用alert()了');

名字空间

全局变量会绑定到window上,但是我们在网页中常常使用多个JavaScript文件,如果这些文件中使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,而且很难被发现。

所以,为了减少冲突,我们可以吧自己的所有变量和函数全部绑定到一个全局变量中。例如:

var MYAPP = {};

MYAPP.name = 'myapp';
MYAPP.version = 1.0;

MYAPP.foo = function() {
    return 'foo';
};

这样可以大大减少全局变量冲突的可能性。

局部作用域

局部作用域上是整个函数内部,而有时需要声明块级作用域的变量。ES6引入了新关键字let来实现:

'use strict';

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    i += 1; // SyntaxError: i再for块声明,所以此处错误。如果是var则不会出错。
}

常量

由于var和lett声明的是变量,如果要声明一个常量,在ES6之前是不行的,以往通常用全部大写的变量来表示这是常量。
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:

'use strict';

const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果
PI; // 3.14

17、解构赋值

从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
解构赋值就是直接对多个变量同时赋值:

'use strict';

// 如果浏览器支持解构赋值就不会报错:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分别被赋值为数组对应元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);

如果数组本身还有嵌套,也可以通过以下的形式进行解构赋值。

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

解构赋值还可以忽略某些元素:

let [ , ,z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素, 只对z赋值第三个元素
z; // 'ES6'

如果需要从一个对象中取出若干属性, 也可以使用解构赋值,便于快速获取对象的指定属性:

'use strict';

var person ={
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, age, address: {city, zip}} = person;
var {passport: id} = person; // 将属性passport赋值给变量id
// 分别被赋值为对应属性,变量名与属性名相同
// address 不是变量,而是为了让city和zip获得嵌套的address对象的属性
// passport 不是变量,而是为了让变量id获得passport属性
name; // '小明'
age; // 20
city;  // 'Beijing'
zip; // undefined,因为属性名是zipcode而不是zip
address; // Uncaught ReferenceError: address is not defined
id; // 'G-12345678'
passport; // Uncaught ReferenceError: passport is not defined

解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题:

var {name, single=true} = person;
name; // '小明'
single; // true

当变量已经声明后再进行解构赋值时要注意,应该使用小括号括起来,否则花括号会被当做块处理而引发错误。

var x, y;
{x, y} = {name: '小明', x: 100, y: 200}; // 引发语法错误: Uncaught SyntaxError: Unexpected token =
({x, y} = {name: '小明', x: 100, y: 200}); // 正确

使用场景

解构赋值在很多时候可以简化代码。例如,交换两个变量x和y的值,可以这么写, 不再需要临时变量:

var x=1, y=2;
[x, y] = [y, x];

快速获取当前页面的域名和路径:

var {hostname: domain, pathname: path} = location;

如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

buildDate({year: 2017, month: 1, day: 1});

18、方法

在对象中绑定的函数,被称为这个对象的方法。

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age;  // [Function: age] 这样不会调用要加上小括号
xiaoming.age(); // 根据调用时的日期计算当前年龄

方法和普通函数没有太多区别。

this 关键字

在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,可以使用tihs获取当前对象的属性,或者调用方法。
但是,当方法函数放在对象外部,或者是嵌套在方法函数中时,this根据调用情况会有所不同。看下面示例:

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 获得正常结果
getAge(); // NaN, 此时,this指向window对象,window此时没有birth属性、
var fn = xiaoming.age();
fn(); // NaN,只有用“对象.方法()”调用时,this才会指向正确的对象

注意,ES5之后在strict模式下,函数中的this不在指向window而是指向undefined。此时,直接调用使用了this的函数时,会引发TypeError: this is undefined 错误。当方法中嵌套的函数中要使用对象的属性时,应该用一个that变量先捕获this,然后再在嵌套函数中使用that变量:

'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var that = this; // 在方法内部一开始就捕获this
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - that.birth;
        }
        return getAgeFromBirth();
    }
};

xiaoming.age() // 28

apply方法

函数的apply方法可以使函数中的this绑定到指定的对象。

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
};

getAge.apply(xiaoming, []); // 28, this 指向xiaoming,  参数为空

另一个与apply()类似的方法是call(),区别是:
apply()把参数打包成数组再传入;
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()函数。

'use strict';

var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function() {
    count +=1;
    return oldParseInt.apply(null, arguments); // 调用原函数
};

parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); //3

19、高阶函数(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.abs,根据函数定义,我们可以推导计算过程为:

x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;

map/reduce

map用于对数组的每一个元素进行处理

举例,比如我们有一个函数f(x) = x^2,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9] 上, 就可以用map实现如下:

'use strict';

function pow(x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow);
console.log(results); // [1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce

reduce接收一个函数作为参数,函数有两个参数,效果如下:


function f(x,y){
    ......
}
[x1, x2, x3, x4 ,x5].reduce(f) = f(f(f(f(x1,x2),x3),x4),x5);

filter

用于把数组中的某些元素过滤掉,然后返回剩下的元素。
和map()类似,filter()也接收一个函数。filter()把传入的函数一次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃改元素。
例如,在一个数组中删除掉偶数,只保留奇数,可以这样写:

var arr = [1, 2, 3, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function(x){
    return x % 2 !== 0;
});
r; // [1, 5, 9, 15]

把一个数组中的空字符串删掉, 可以这么写:

var arr = ['A', '', 'B', null, undefined, 'C', '  '];
var r = arr.filter(function(s) {
    return s && s.trim(); //注意,IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']

回调函数

filter() 接收的回调函数,其实可以有多个参数。通常我们仅适用第一个参数,表示数组的某个元素。
回调函数汉可以接收另外两个参数,表示元素的位置和数组本身:

//去掉重复元素
var arr = ['A', 'B', 'C', 'A', 'D', 'C', 'E'];
var r = arr.filter(function (element, index, self) {
    return self.indexOf(element) === index; //查询当前元素位置,如果位置和当前位置相等则表示非重复元素
});

sort排序

数组的sort()方法排序,可能会得到意想不到的结果:

['Goolge', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft']
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft', 'apple']
[10, 20, 1, 2].sort();  // [1, 10, 2, 20]

可以看出,字符串时按ASCII顺序进行排序的,而数字排序也都先转换成字符串后再进行排序。

sort也是高阶函数,所以我们可以传递一个比较函数来实现自定义的排序。
例如,按数字大小排序:

'use strict';

var  arr = [10, 20, 1, 2];
arr.sort(function(x, y) {
    if (x < y) {
        return -1; //小于返回-1
    }
    if (x > y) {
        return 1; //大于返回1
    }
    return 0; // 等于返回0
});
console.log(arr); // [1, 2, 10, 20]

注意,sort()方法会直接对调用的数组进行修改,它返回结果仍然是当前数组。

20、闭包

函数作为返回值

09-01 12:30