JavaScript学习笔记(二)

来源:互联网 发布:龙魂时刻数据解析失败 编辑:程序博客网 时间:2024/06/04 17:46

函数

定义函数

有两种方式定义函数:
第一种,

function abs(x) {    if (x >= 0) {        return x;    } else {        return -x;    }}

函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。

由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。

因此,第二种定义函数的方式如下:

var abs = function (x) {    if (x >= 0) {        return x;    } else {        return -x;    }};

注意:第二种方式按照完整语法需要在函数体末尾加一个 ;,表示赋值语句结束。
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:

abs(10); // 返回10abs(10, 'blablabla'); // 返回10abs(-9, 'haha', 'hehe', null); // 返回9abs(); // 此时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

关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。

function foo(x) {    alert(x); // 10    for (var i=0; i<arguments.length; i++) {        alert(arguments[i]); // 10, 20, 30    }}foo(10, 20, 30);

利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

function abs() {    if (arguments.length === 0) {        return 0;    }    var x = arguments[0];//arguments类似Array但它不是一个Array    return x >= 0 ? x : -x;}abs(); // 0abs(10); // 10abs(-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参数

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)。

函数变量

变量作用域

阅读: 4778
在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的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:

'use 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, undefined,说明变量y的值为undefined。这正是因为JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。
注意:遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量:

function foo() {    var        x = 1, // x初始化为1        y = x + 1, // y初始化为2        z, i; // z和i为undefined    // 其他语句:    for (i=0; i<100; i++) {        ...    }}

名字空间

// 唯一的全局变量MYAPP:var MYAPP = {};// 其他变量:MYAPP.name = 'myapp';MYAPP.version = 1.0;// 其他函数:MYAPP.foo = function () {    return 'foo';};

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。

局部作用域

由于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}

常量

ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:

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

方法

在一个对象中绑定函数,称为这个对象的方法。
写个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了

apply

要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。

用apply修复getAge()调用:

function getAge() {    var y = new Date().getFullYear();    return y - this.birth;}var xiaoming = {    name: '小明',    birth: 1990,    age: getAge};xiaoming.age(); // 25getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

另一个与apply()类似的方法是call(),唯一区别是:

apply()把参数打包成Array再传入;

call()把参数按顺序传入。

比如调用Math.max(3, 5, 4),分别用apply()和call()实现如下:

Math.max.apply(null, [3, 5, 4]); // 5Math.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

高阶函数

map

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

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]

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

filter函数

filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。

例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:

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

sort函数

JavaScript的Array的sort()方法就是用于排序的,Array的sort()方法默认把所有元素先转换为String再排序,字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。
要按数字大小排序,我们可以这么写:

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

闭包

箭头函数

generator跟函数很像,定义如下:

function* foo(x) {    yield x + 1;    yield x + 2;    return x + 3;}

generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
我们以一个著名的斐波那契数列为例,它由0,1开头:

0 1 1 2 3 5 8 13 21 34 …
要编写一个产生斐波那契数列的函数,可以这么写:

function fib(max) {    var        t,        a = 0,        b = 1,        arr = [0, 1];    while (arr.length < max) {        t = a + b;        a = b;        b = t;        arr.push(t);    }    return arr;}// 测试:fib(5); // [0, 1, 1, 2, 3]fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

函数只能返回一次,所以必须返回一个Array。但是,如果换成generator,就可以一次返回一个数,不断返回多次。用generator改写如下:

function* fib(max) {    var        t,        a = 0,        b = 1,        n = 1;    while (n < max) {        yield a;        t = a + b;        a = b;        b = t;        n ++;    }    return a;}

直接调用试试:

fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

直接调用一个generator和调用函数不一样,fib(5)仅仅是创建了一个generator对象,还没有去执行它。

调用generator对象有两个方法,一是不断地调用generator对象的next()方法:

var f = fib(5);f.next(); // {value: 0, done: false}f.next(); // {value: 1, done: false}f.next(); // {value: 1, done: false}f.next(); // {value: 2, done: false}f.next(); // {value: 3, done: true}

正则表达式

在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:

“`
‘00\d’可以匹配’007’,但无法匹配’00A’;

'\d\d\d'可以匹配'010';'\w\w'可以匹配'js'


. “`可以匹配任意字符,所以:

‘js.’可以匹配’jsp’、’jss’、’js!’等等。

要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:

来看一个复杂的例子:\d{3}\s+\d{3,8}。
我们来从左到右解读一下:

  1. \d{3}表示匹配3个数字,例如’010’;
  2. \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配’ ‘,’\t\t’等;
  3. \d{3,8}表示3-8个数字,例如’1234567’。

如果要匹配’010-12345’这样的号码呢?由于’-‘是特殊字符,在正则表达式中,要用’\’转义,所以,上面的正则是\d{3}-\d{3,8}。

进阶

要做更精确地匹配,可以用[]表示范围,比如:
- [0-9a-zA-Z_]可以匹配一个数字、字母或者下划线;

  • [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如’a100’,’0_Z’,’js2015’等等;

  • [a-zA-Z_$][0-9a-zA-Z_$]*可以匹配由字母或下划线、线组成的字符串,也就是JavaScript允许的变量名;

  • [a-zA-Z_$][0-9a-zA-Z_$]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

A|B可以匹配A或B,所以[J|j]ava[S|s]cript可以匹配’JavaScript’、’Javascript’、’javaScript’或者’javascript’。

^表示行的开头,^\d表示必须以数字开头。

\d表示必须以数字结束。

你可能注意到了,js也可以匹配’jsp’,但是加上^js$就变成了整行匹配,就只能匹配’js’了。

JSON

JSON是JavaScript Object Notation的缩写,它是一种数据交换格式。

在JSON中,一共就这么几种数据类型:

    number:和JavaScript的number完全一致;    boolean:就是JavaScript的true或false;    string:就是JavaScript的string;    null:就是JavaScript的null;    array:就是JavaScript的Array表示方式——[];    object:就是JavaScript的{ ... }表示方式。

以及上面的任意组合。

并且,JSON还定死了字符集必须是UTF-8,表示多语言就没有问题了。为了统一解析,JSON的字符串规定必须用双引号”“,Object的键也必须用双引号”“。

由于JSON非常简单,很快就风靡Web世界,并且成为ECMA标准。几乎所有编程语言都有解析JSON的库,而在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。

把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。

如果我们收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象,就可以在JavaScript中直接使用这个对象了。

序列化

让我们先把小明这个对象序列化成JSON格式的字符串:

var xiaoming = {    name: '小明',    age: 14,    gender: true,    height: 1.65,    grade: null,    'middle-school': '\"W3C\" Middle School',    skills: ['JavaScript', 'Java', 'Python', 'Lisp']};JSON.stringify(xiaoming); // '{"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"middle-school":"\"W3C\" Middle School","skills":["JavaScript","Java","Python","Lisp"]}'

要输出得好看一些,可以加上参数,按缩进输出:

JSON.stringify(xiaoming, null, '  ');

结果:

{  "name": "小明",  "age": 14,  "gender": true,  "height": 1.65,  "grade": null,  "middle-school": "\"W3C\" Middle School",  "skills": [    "JavaScript",    "Java",    "Python",    "Lisp"  ]}

第二个参数用于控制如何筛选对象的键值,如果我们只想输出指定的属性,可以传入Array:

JSON.stringify(xiaoming, ['name', 'skills'], '  ');

结果:

{  "name": "小明",  "skills": [    "JavaScript",    "Java",    "Python",    "Lisp"  ]}

还可以传入一个函数,这样对象的每个键值对都会被函数先处理:

function convert(key, value) {    if (typeof value === 'string') {        return value.toUpperCase();    }    return value;}JSON.stringify(xiaoming, convert, '  ');

上面的代码把所有属性值都变成大写:

{  "name": "小明",  "age": 14,  "gender": true,  "height": 1.65,  "grade": null,  "middle-school": "\"W3C\" MIDDLE SCHOOL",  "skills": [    "JAVASCRIPT",    "JAVA",    "PYTHON",    "LISP"  ]}

如果我们还想要精确控制如何序列化小明,可以给xiaoming定义一个toJSON()的方法,直接返回JSON应该序列化的数据:

var xiaoming = {    name: '小明',    age: 14,    gender: true,    height: 1.65,    grade: null,    'middle-school': '\"W3C\" Middle School',    skills: ['JavaScript', 'Java', 'Python', 'Lisp'],    toJSON: function () {        return { // 只输出name和age,并且改变了key:            'Name': this.name,            'Age': this.age        };    }};JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'

反序列化

拿到一个JSON格式的字符串,我们直接用JSON.parse()把它变成一个JavaScript对象:

JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}JSON.parse('true'); // trueJSON.parse('123.45'); // 123.45JSON.parse()还可以接收一个函数,用来转换解析出的属性:JSON.parse('{"name":"小明","age":14}', function (key, value) {    // 把number * 2:    if (key === 'name') {        return value + '同学';    }    return value;}); // Object {name: '小明同学', age: 14}

在JavaScript中使用JSON,就是这么简单!

0 0
原创粉丝点击