javascript 杂记

说实话,再你熟悉了C C++ java python后再学js,老是会有无数错觉,总以为它的语法像什么,又不太像。做为动态语言又没python那么简洁,里面不少语法借鉴了c和java的那套,可是又不像c那么严格。js是弱类型语言,并且仍是有一些动态语言的灵活性在里面。javascript

题记

若是带着以前学C++/java的思路去学js,总以为十分别扭,特别是它的object和function,真是让人摸不着头脑。php

因此说啊,js真是个磨人的小妖精。要征服这个小妖精还真不是一时半会能实现的,因此,仍是慢慢来吧,把征服它的过程都记录一下。java

注意:本文是一个本人的总结记录文,并非一个js教程,请不要抱着《从入门到精通》的思路来看本文。若是你有必定的编程基础,或者正在学习js,这个笔记可能对你有点用。(如下笔记是廖雪峰javascript教程,还有freecodecamp教程笔记)python

对了,本文持续更新ing...es6

语法

目前大多语言都是类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参数,上面的函数能够改写为:json

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咱们就获取了所有参数。windows

若是传入的参数连正常定义的参数都没填满,也没关系,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']
相关文章
相关标签/搜索