javascript 杂记
说实话,再你熟悉了C C++ java python后再学js,总是会有无数错觉,总觉得它的语法像什么,又不太像。作为动态语言又没python那么简洁,里面很多语法借鉴了c和java的那套,但是又不像c那么严格。js是弱类型语言,而且还是有一些动态语言的灵活性在里面。
题记
如果带着之前学C++/java的思路去学js,总觉得十分别扭,特别是它的object和function,真是让人摸不着头脑。
所以说啊,js真是个磨人的小妖精。要征服这个小妖精还真不是一时半会能实现的,所以,还是慢慢来吧,把征服它的过程都记录一下。
注意:本文是一个本人的总结记录文,并不是一个js教程,请不要抱着《从入门到精通》的思路来看本文。如果你有一定的编程基础,或者正在学习js,这个笔记可能对你有点用。(以下笔记是廖雪峰javascript教程,还有freecodecamp教程笔记)
对了,本文持续更新ing...
语法
目前大多语言都是类C的,js也一样(除了python ruby这些自成一派的)
- 区分大小写
- 字符串可以用''或“”和python一样
- 变量不区分类型,和python一样(python不用写var)
- 每条语句结尾可以省略分号
- 注释与C,C++,java,php相同
- 代码段要封闭与C,C++,java同(和python不一样)
- 运算符完全和C一致,有自加自减,有逗号表达式,有条件运算符!!(和python不一样)
- 逻辑运算符,条件,循环语句和C一致(和python不一样)
- 有switch-case语句(和python不一样)
函数相关
两种定义
- 普通定义
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
- 匿名函数
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}; //注意分号
函数传参可以任意个
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);
}
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)。
变量提升
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的赋值。
由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window的一个属性:
你可能猜到了,由于函数定义有两种方式,以变量方式var foo = function () {}
定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:
alert
也是windows
的一个变量
这说明JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。
名字空间,避免关键词冲突
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
局部作用域 let
由于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
}
闭包函数
js可以把函数名返回,只有再次调用的时候才返回结果。
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
f(); // 15
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
理论上讲,创建一个匿名函数并立刻执行可以这么写:
function (x) { return x * x } (3);
但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:
通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
(function (x) {
return x * x;
})(3);
闭包有非常强大的功能。举个栗子:
在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。
在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:
'use strict';
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2和pow3:
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);
pow2(5); // 25
pow3(7); // 343
箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
为什么叫Arrow Function?因为它的定义用的就是一个箭头:
x => x * x
上面的箭头函数相当于:
function (x) {
return x * x;
}
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return:
如果参数不是一个,就需要用括号()括起来:
// 两个参数:
(x, y) => x * x + y * y
// 无参数:
() => 3.14
// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
现在,箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj
:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25
Map和Set
map
- 创建map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
- Map具有以下方法:(set get has delete)
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);m.has('Adam'); // 是否存在key 'Adam':true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam' 如果删除失败返回false
m.get('Adam'); // undefined
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88
set
Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
要创建一个Set,需要提供一个Array
作为输入,或者直接创建一个`空
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3重复元素在Set中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
注意数字3和字符串'3'是不同的元素。
- add(key)方法
可以添加元素到Set中,可以重复添加,但不会有效果:
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
- delete(key)方法
可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
其他
常量const
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
var PI = 3.14;
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
``表示多行字符串
ES6提出了``表示多行字符串
`这是一个
多行
字符串`
null和undefined
null
表示一个“空”的值,它和0以及空字符串''不同,0是一个数值,''表示长度为0的字符串,而null表示“空”。
在其他语言中,也有类似JavaScript的null
的表示,例如Java也用null
,Swift用nil
,Python用None
表示。但是,在JavaScript中,还有一个和null类似的undefined,它表示“未定义”。
JavaScript的设计者希望用null表示一个空的值,而undefined表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用null。undefined仅仅在判断函数参数是否传递的情况下有用。
字符串转正则eval()
有人可能发现了,js的正则表达式并不是字符串,但是实际使用中,我们经常需要字符串去构造正则表达式,这个时候就可以用eval()去转化。eval()可以用来解析字符串
但是eval是一个很危险的操作,如果非必要,尽量少使用
var test = 'this is a test';
var exp = '/\w/ig';
test.match(eval(exp));
输出
console.log() //在控制台显示
alert(); //弹出对话框显示结果
或者直接输入变量,在浏览器的控制台也能显示结果
严格相等
严格相等(===
)不仅比较值,还比较类型
因为在js中,7 == “7”
number
JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型:
123; // 整数
1230.456; // 浮点数
0.4561.2345e3; // 科学计数法表示
1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
注意NaN
这个特殊的Number与所有其他值都不相等,包括它自己:
NaN === NaN; // false
唯一能判断NaN
的方法是通过isNaN()函数:
isNaN(NaN); // true
for in vs for of (ES6新加)
js中也有for in
用法,但是和python有些不一样的地方。
- js中的
for in
对数组也是返回的是键,比如“0”,“1”……(而且由于历史遗留问题,'name'也在遍历范围内) - python中直接返回的是数组中的值
- js中的
for of
和python中的for in
才是真实对应的哦~不仅可以用于数组也可以用于字符串
所以for in用在json中的遍历比较合适。
比如说你如果需要遍历数组,你可以使用普通for
循环做,也可以用for of
。
var arr = ["a","b","c","d"];
for(var i = 0;i < arr.length;i++)
{
console.log(arr[i]);
}
for(var i in arr)
{
//这里的i是字符串,打印出来可以发现是“0”,“1”,“2”……
console.log(arr[i]);
}
for(var i of arr)
{
console.log(i);
}
forEach
更好的方式是直接使用iterable,内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。以Array为例:
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
alert(element);
});
注意,forEach()方法是ES5.1标准引入的,你需要测试浏览器是否支持。
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
alert(element);
});
Map的回调函数参数依次为value、key和map本身:
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
alert(value);
});
如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array的element:
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
alert(element);
});
字符串相关
越界不报错
字符串越界不会报错,会返回undefine
字符串不可变
需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
var s = 'Test';
s[0] = 'X';
alert(s); // s仍然为'Test'
JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串
toUpperCase
toUpperCase()把一个字符串全部变为大写:
var s = 'Hello';
s.toUpperCase(); // 返回'HELLO'
toLowerCase
toLowerCase()把一个字符串全部变为小写:
var s = 'Hello';
var lower = s.toLowerCase(); // 返回'hello'并赋值给变量lower
lower; // 'hello'
indexOf
indexOf()会搜索指定字符串出现的位置:
var s = 'hello, world';
s.indexOf('world'); // 返回7
s.indexOf('World'); // 没有找到指定的子串,返回-1
字符串的slice()和substr()和substring()
str.slice(beginSlice[, endSlice])
str.substr(start[, length])
str.substring(indexStart[, indexEnd])
都是截断函数,但是有一些区别
- slice和substr能接负数,代表倒数。substring不行
- substr第二参数代表长度不是end
- substring的end如果大于start会自动交换,slice不会,会返回""
- 三者均支持越界
var str = "example";
console.log(str.slice(-3)); // "ple"
console.log(str.substr(-3)); // "ple"
console.log(str.substr(2,4)); // "ampl"
console.log(str.substring(2,4)); // "am"
console.log(str.slice(4,2)); // ""
console.log(str.substring(4,2)); // "am"
console.log(str.slice(2,-1)); // "ampl"
console.log(str.substring(0,100)); // "example"
console.log(str.slice(0,100)); // "example"
console.log(str.substr(0,100)); // "example"
数组相关
js的数组和python的list一样可以存不同类型不同维度个数据,除了可以用下标查看修改数据外,还有几个方法:
- push():加到最后
- pop(): 从最后取
- shift(): 从开头取
- unshift(): 加入开头
构造数组的方式还有如下:(除了特别说明的外,都不改变原数组)
用map创建数组
var oldArray = [1,2,3,4]
var timesFour = oldArray.map(function(val){
return val * 4;
});
用reduce压缩数组
reduce的第2个参数(初始值)可选,如果没有,就从数组第一个开始
var array = [4,5,6,7,8];
var sum = 0;
sum = array.reduce(function(pre,cur){
return pre+cur;
},0);
用fliter过滤数组
如果我们只需要数组中小于6的元素
var oldArray = [1,2,3,4,5,6,7,8,9,10];
var newArray = oldArray.fliter(function(val){
return val < 6;
});
数组排序sort
数组有排序的功能(会改变原数组
,并且也会返回),如果不带参数,默认是按字符串排序,如果要改变排序方式,可以在里面增加比较函数,规则如下
- 负数:a在b前
- 大于:b在a前
var array = [1, 12, 21, 2];
//降序排序
array.sort(function(a,b){
return b-a;
});
//升序排序
array.sort(function(a,b){
return a-b;
});
逆转数组reverse
改变原数组
var array = [1,2,3,4,5,6,7];
array.reverse();
数组拼接concat
var oldArray = [1,2,3];
var newArray = [];
var concatMe = [4,5,6];
newArray = oldArray.concat(concatMe);
字符串和数组转换
- 用split切割字符串
var string = "Split me into an array";
var array = [];
array = string.split(' ');
- 用joint把数组拼接成字符串
var joinMe = ["Split","me","into","an","array"];
var joinedString = '';
joinedString = joinMe.join(' ');
splice函数
array.splice(start, deleteCount[, item1[, item2[, ...]]])
js提供了一个splice函数,用来删除index
位置处的deleteCount
数目的元素,并且在index处加入item1,2,3……(可以不加入)
这个函数可以用来替换数组内的部分元素
var myFish = ['angel', 'clown', 'mandarin', 'surgeon'];
// removes 0 elements from index 2, and inserts 'drum'
var removed = myFish.splice(2, 0, 'drum');
// myFish is ['angel', 'clown', 'drum', 'mandarin', 'surgeon']
// removed is [], no elements removed
removed = myFish.splice(3, 1);
// myFish is ['angel', 'clown', 'drum', 'surgeon']
// removed is ['mandarin']
removed = myFish.splice(2, 1, 'trumpet');
// myFish is ['angel', 'clown', 'trumpet', 'surgeon']
// removed is ['drum']
slice函数
arr.slice([begin[, end]])
取出数组的从begin到end的元素,重新组成数组。
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3);// ['Orange','Lemon']