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}。
我们来从左到右解读一下:
- \d{3}表示匹配3个数字,例如’010’;
- \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配’ ‘,’\t\t’等;
- \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表示必须以数字开头。
你可能注意到了,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,就是这么简单!
- JavaScript 学习笔记(二)
- JavaScript学习笔记(二)
- javascript 学习笔记(二)
- JavaScript学习笔记(二)
- JavaScript学习笔记(二)
- javascript学习笔记(二)
- Javascript学习笔记(二)
- JavaScript学习笔记(二)
- javascript学习笔记(二)
- JavaScript学习笔记(二)
- JavaScript学习笔记(二)
- JavaScript学习笔记(二)
- javaScript学习笔记(二)
- JavaScript学习笔记(二)
- javaScript学习笔记(二)
- javascript学习笔记(二)
- javascript学习笔记(二)
- javascript学习笔记(二)
- 提高
- x86, x86-64, i386, IA32, IA64 区别
- JavaScript-函数,类,数组
- android aapt
- AtomicReference原子类
- JavaScript学习笔记(二)
- VC解析64位时间DWORD64 dw64TimeMS
- [C++]模板与泛型编程(3)
- 音乐播放器-DAO
- 第二周项目1-C/C++语言中函数参数传递的三种方式
- 1012. The Best Rank (25)
- 编写高质量代码(6)
- 【NOIP2014 Day2 T2】寻找道路
- 华为HG8245C光猫破解用户连接数限制