日度归档:2017年12月19日

原生JavaScript语法(一)

20171213-1218

1基本

  • js原型数据类型:Boolean,null,undefined,Number,String,Symbol(ECMAScript6) ;
//Symbols在for...in迭代中不可枚举;
var obj = {};
obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";
for (var i in obj) {
   console.log(i); // logs "c" and "d"
}
//当永JSON.stringify()时,以symbol作为键的属性辉被忽略;
JSON.stringify({[Symbol("foo")]: "foo"});                 
// '{}'
  • Object
  • js {},可视为其他语言中Map或者Dictionary的数据结构——一组键值对;
js中的 键 必须是  字符串(这是js中对象的一个小问题,实际上用Number或者其他数据类型做键也是合理的);
为了解决这个问题,最新的ES6规范引入了:Map;

  • Map-极快的查找速度;
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael');
或者
var m = new Map();
m.set("Adam",77);m.set("Bob",99);
m.get("Bob");
m.delete("Adam");
  • Set是一组key的集合,key不能重复
var s = new Set([1,2,3,'3']);
s.add(4);
s.delete(2);
  • iterable - Array,Map,Set都属于iterable类型
具有iterable类型的集合可以通过新的 for of来遍历;
var a = [1,2,3];
for(var x of a){}
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
    console.log(x);
}
for (var x of s) { // 遍历Set
    console.log(x);
}
for (var x of m) { // 遍历Map
    console.log(x[0] + '=' + x[1]);
}
  • for…of 和 for…in
//for ... in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
  • forEach() ES5.1
var s = new Set(['a','b','c']);
s.forEach(function(element){
    console.log(element);
})

2函数

  • 定义和调用
  • 变量作用域
function foo(){
    var x = 1;
    function bar(){
        var x = "A";
        console.log("x in bar() = "+x);//A
    }
    console.log("x in foo() = "+x);//1
    bar();
}
//调用foo()
foo();
//结果
x in foo()  = 1
x in bar() = A

  • 变量提升,值不提升
function foo(){
    var x = "hello,"+y;
    console.log(x);
    var y = "bob";
}
//调用foo()
foo();
//结果
hello,undefined
//它被提升成了
function foo(){
    var y;
    var x = "hello,"+y;
    console.log(x);
    y = "bob";
}

  • 由于JavaScript提升声明,不提升值;所以,在函数内部定义变量,严格遵守,在函数内部首先申明所有变量;
function foo() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句:
    for (i=0; i<100; i++) {
        ...
    }
}
  • 全局作用域
//JavaScript默认有一个全局对象window;
//全局作用域的变量,实际上是被绑定到window的一个属性;
var course = "learn";
alert(course);//learn
alert(window.course);//learn
  • 由于函数定义有两种方式,以变量方式var foo = function(){}定义的函数实际上也是一个全局变量;因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:
'use strict'
function foo(){
    alert("foo");
}
foo();//直接调用
window.foo();//通过window.foo()调用

//我们每次调用的alert()函数也是window的一个变量;
var old_alert = window.alert;
//给alert赋一个新函数
window.alert = function(){console.log("不alert了,打印");}
alert();
//结果
"不alert了,打印"
  • JavaScript只有一个全局作用域,任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找;最后如果在全局作用域中也没有找到,则报ReferenceError错误;
  • 名字空间;如果不同的JavaScript文件使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现;
//减少冲突的一个办法:把自己的所有变量和函数,全部绑定到一个全局变量中
var myapp = {};//唯一的全局变量
myapp.name = "myname";
myapp.version = 1.0;
myapp.foo = function(){
    return "lalala";
};
//许多JavaScript库都是这样做的:jQuery,YUI,underscore等

  • 局部作用域:函数内部;需要另外解决“块级作用域”的问题;
//ES6引入了新的关键字let,申明一个块级作用域
function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    // SyntaxError:
    i += 1;
}
  • 常量;ES6之前js只能申明变量;ES6引入新的关键字const定义常量;const和let都有块级作用域;
//ES6之前
var PI  =3.14;
//ES6
const PI = 3.14;
PI=3;//这样修改不了;某些浏览器不报错,但是不起作用
PI;//3.14

  • 解构赋值:ES6,同时对一组变量进行赋值;
var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];
//还可以嵌套赋值
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
//还可以忽略某些元素
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'
  • 若需要从一个对象中 取出 若干属性,也可以使用解构赋值;
var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name, age, passport分别被赋值为对应属性:
console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);
//name = 小明, age = 20, passport = G-12345678

//如果 要使用的变量名  和 属性名不一致,可以用这样获取:
var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // 'G-12345678'

var {hostname:domain, pathname:path} = location;
domain
"www.liaoxuefeng.com"
path
"/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014344993159773a464f34e1724700a6d5dd9e235ceb7c000"
hostname//VM642:1 Uncaught ReferenceError: hostname is not defined
    at <anonymous>:1:1

  • 可以利用解构赋值 传参:
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 });
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
  • 方法:在一个对象中 绑定函数,称为这个对象的方法
var xiaoming = {
    name:"小明",
    birth:1991,
    age:function(){
        var y = new Date().getFullYear();
        return y-this.birth;
    }
};
xiaoming.age;
xiaoming.age();
  • 方法,在内部使用了this关键字;在一个方法内部,this始终指向当前对象,也就是xiaoming这个变量;所以,this.birth,可以拿到xiaoming的birth属性;
//要保证,this 的指向正确,必须用obj.xxx()的形式调用!
var fn = xiaoming.age;
fn();//NaN
  • ECMA:在strict模式下让函数的this指向undefined;避免不容易发现的错误;
  • 下面的写法有错:this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,this又指向undefined了(在非strict模式下,它重新指向的全局对象window)
'use strict';

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

xiaoming.age(); 
  • 正确的:用一个that变量首先捕获this;用var that = this,就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中;
'use strict';
var xiaoming = {
    name:"小明",
    birth:1991,
    age:function(){
        var that = this;//在方法内部一开始就捕获this
        function getAgeFromBirth(){
            var y = new Date().getFullYear();
            return y-that.birth;
        }
        return getAgeFromBirth();
    }
}
xiaoming.age();//26
  • apply:可以人为指定函数的this指向哪个对象,它接收2个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,标识函数本身的参数;
function getAge(){
    var y = new Date().getFullYear();
    return y-this.birth;
}
var xiaoming = {
    name:'小明',
    birth:1991,
    age:getAge
};
xiaoming.age();//26
getAge.apply(xiaoming, []);//26
  • call: 类似于apply,区别:apply()把参数打包成Array再传入;call()把参数按顺序传入;
Math.max.apply(null,[2,5,4]);
Math.max.call(null,3,5,4);
//对普通函数调用,通常把this绑定为null
  • 装饰器:利用apply(),还可以动态改变函数的行为;JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的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

3 高阶函数

  • 一个函数可以接收另一个函数作为参数;
function add(x,y,f){
    return f(x)+f(y);
}
add(-5,6,Math.abs);//11

3.1 map/reduce

  • map()的方法定义在JavaScript的Array中;
'use strict';
function pow(x){
    return x * x;
}
var arr = [1,2,3,4,5,6,7,8,9];
var results = arr.map(pow);

var arr = [1,3,5,7,9];
arr.reduce(fucntion(x,y){
    return x+y;
});//25
  • 使用map/reduce
//将字符串'12468'变成整数12468,不使用parseInt
var ori = '12468';
function string2int(mystr){
    var result = mystr.split("").map(function(x){return +x;}).reduce(function(x,y){return x*10+y;});
    return result;
}
string2int(ori);//12468
  • 注意事项之一
var arr = ['1', '2', '3'];
var r;
r = arr.map(parseInt);
//结果r,是!:1,NaN,NaN
//因为,map()接收的回调函数可以有3各参数callback(currentValue,index,array),通常仅需要第一个参数,而忽略了传入的后面两个参数;但是,parseInt(string,radix),没有忽略第二个参数,导致实际上:
parseInt('0',0);//按十进制转换
parseInt('1',1);//NaN,没有一进制
parseInt('2',2);//NaN,按二进制转换不允许出现2
//可以改为
r = arr.map(Number);
//因为Number(value)函数仅接收一个参数
function normalize(arr){
    return arr.map(function(x){var a = x.toLowerCase();
    return a.slice(0,1).toUpperCase()+a.slice(1);})
}
//入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']。

3.2 filter-筛选作用

  • filter(),接收一个函数;把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素;
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
    return x % 2 !== 0;
});
r;//1,5,9,15
//去掉一个Array元素中的空字符串
var arr = ['A', '', 'B', null, undefined, 'C', '  '];
var r = arr.filter(function (s) {
    return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
});
r;//["A", "B", "C"]

  • filter接收的回调函数,可以有多个参数,通常,只使用第一个参数(Array的某个元素);还可以接收另外两个函数,表示元素的位置和数据本身;
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
    console.log(element); // 依次打印'A', 'B', 'C'
    console.log(index); // 依次打印0, 1, 2
    console.log(self); // self就是变量arr
    return true;
});
//筛选素数
function get_primes(arr){
    arr.filter(function(x){
        if(x==1){return false;}
        var i = 2;
        while(i < x){
           if(x % i == 0){return false;}
           i++;
        }
        return true;
    });
}

3.3 sort-排序-sort默认按字符串排序,按ASCII;

var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1和a2是同一对象
//sort()可接收函数,自定义大小比较方法
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]

3.4 闭包

  • 函数作为返回值
  • 注意,返回的函数不立即执行的问题;
  • 创建一个匿名函数让它立即执行;
function count(){
    var arr = [];
    for(var i=1;i<=3;i++){
        arr.push((function(n){
        return function(){
        return n*n;
        }})(i));
    }
    return arr;
}
var re = count();
var f1=re[0];
var f2=re[1];
var f3=re[2];
f1();//1
f2();//4
f3();//9
//由于JavaScript语法解析的问题,要用括号把整个函数定义括起来,否则会报SyntaxError错误;
(function (x) { return x * x }) (3);
  • 通过闭包,封装一个私有变量; 在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x;
  • 闭包,就是携带状态的函数,并且它的状态可以完全对外隐藏起来;
function create_count(initial){
    var x = initial || 0;
    return {
        inc:function(){
            x+=1;
            return x;
        }
    }
}
var c1 = create_count();
c1.inc();//1
c1.inc();//2
c1.inc();//3
var c2 = create_count(10);
c2.inc();//11
c2.inc();//12
c2.inc();//13
  • 闭包,还可以把多惨呼的函数,变成单参数的函数;比如 计算x的y次方,可以用Math.pow(x,y),考虑到经常用x平方和x三次方,可以利用闭包创建新的函数pow2和pow3;
function make_pow(n){
    return function(x){
        return Math.pow(x,n);
    }
}
//创建两个新函数
var pow2 = make_pow(2);
var pow3 = make_pow(3);

3.5 箭头函数

  • ES6标准新增了一种新的函数,Arrow Function箭头函数;
x => {
    if(x>0){
        return x*x;
    }
    else{
        return -x*x;
    }
}

//如果参数不是一个,就要用括号()括起来:
(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;
}

// 如果要返回一个对象
x => ({foo:x})
  • this; 使用箭头函数,this总是指向词法作用域,就是外层调用者obj;
var obj = {
    birth:1991,
    getAge:function(){
        var b = this.birth;
        var fn = () => new Date().getFullYear() - this.birth;//可以正确指向obj的birth
        return fn();
        //var fn = function () {
            //return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
    }
}
obj.getAge();//27
  • 由于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
'use strict'
var arr = [10, 20, 1, 2];
arr.sort((x, y) => {
   if(x<y){return -1;}
   if(x>y){return 1;}
   return 0;
});
console.log(arr); // [1, 2, 10, 20]

3.5 generator生成器ES6

function*  fib(max){
    var
        t,
        a=0,
        b=1,
        n=0;
    while(n<max){
        yield a;
        [a,b]=[b,a+b];
        n++;
    }
    return;
}
for(var x of fib(10)){
    console.log(x);
}
  • generator可以在执行过程中多次返回,所以看上去就像一个可以记住记住执行状态的函数;
  • 这样,写一个generator就可以实现需要用面向对象才能实现的功能;
  • 还有一个好处:把异步回调代码变成“同步”代码;回调越多,代码越难看;有了generator,用AJAX可以这么写:(看上去是同步的代码,实际上是异步的)
try{
    r1 = yield ajax("http://url-1",data);
    r2 = yield ajax("http://url-2",data);
    r3 = yield ajax("http://url-3",data);

}catch(err){
    handle(err);
}
// 自增函数
function* next_id(){
    var current_id=1;
    while(true){
        yield current_id;
        current_id++;
    }
}

var x;
var pass = true;
var g = next_id();
for(x=1;x<100;x++){
    console.log(g.next().value);
}

4 标准对象

  • typeof
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
  • 包装对象
number Number
boolean Boolean
string String

// 注意就行了
123..toString();//注意是2个点!
(123).toString();
var n = Number("456");
typeof n;
"number"
var m = new Number("333");
typeof m
"object"
//typeof操作符可以判断出number、boolean、string、function和undefined
//判断Array要使用Array.isArray(arr);
//判断某个全局变量是否存在用typeof window.myVar === 'undefined';
//函数内部判断某个变量是否存在用typeof myVar === 'undefined'。
//用parseInt()或parseFloat()来转换任意类型到number;
//用String()来转换任意类型到string,或者直接调用某个对象的toString()方法;
//通常不必把任意类型转换为boolean再判断,因为可以直接写if (myVar) {...};
  • Date

  • RegExp

// \d可以匹配一个数字,\w可以匹配一个字母或数字;
// . 可以匹配任意字符
// * 表示任意个字符(包括0个),
// + 表示至少一个字符
// ? 表示0个或1个字符
// {n}表示n个字符
// {n,m}表示n-m个字符
var reg = /\d{3}\s+\d{3,8}/;//可以匹配任意个空格隔开的带区号的电话号码
// \d{3},匹配3个数字
// \s匹配一个空格(可包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' ','\t\t'等 
// \d{3,8}表示3-8个数字,例如'12345'
// 如果要匹配'010-12345',
var reg2 = /\d{3}\-\d{3,8}/
  • JSON-JavaScript Object Notation,一种数据交换格式
  • 最开始设计的JSON是JavaScript的一个子集,在JSON中,一共就几种数据类型:number,boolean,string,null,array,object;
  • JSON定死了字符集必须是UTF-8,这样表示多语言就没有问题了;
  • 为了统一解析,JSON的字符串规定必须用双引号"",Object的键也必须用双引号""
  • 序列化 JSON.stringify()
'use strict'
var xiaoming = {
    name:'小明',
    age:16,
    gender:true,
    height:1.60,
    grade:null,
    'middle-school':'清苑',
    skill:['JavaScript','java','python','lisp']
};
var s = JSON.stringify(xiaoming);
console.log(s);
// 要输出好看一些,可以加上参数,按缩进输出:
JSON.stringify(xiaoming,null,' ');
// 上面第二个参数,用于控制如何筛选对象的键值,如果只想输出指定的属性,可以传入Array;
JSON.stringify(xiaoming,['name','skill'],' ');
// 还可以传入一个函数,这样,对象的每个键值对都会被函数先处理
function convert(key,value){
    if(typeof value === 'string'){
        return value.toUpperCase();
    }
    return value;
}
JSON.stringify(xiaoming,convert,' ');
// 如果还想【更精确控制】如何序列化,可以给xiaoming定义一个toJSON()方法,直接返回JSON应该序列化的数据:
var xiaoming = {
    name:'小明',
    age:16,
    gender:true,
    height:1.60,
    grade:null,
    'middle-school':'清苑',
    skills:['JavaScript','java','python','lisp'],
    toJSON:function(){
        return {
            'Name':this.name,
            'Age':this.age,
            'Height':this.height
        };
    }
}
JSON.stringify(xiaoming);
  • 反 序列化 JSON.parse()变成一个JavaScript对象
JSON.parse('[1,2,3,true]');//[1,2,3,true]
JSON.parse('{"name":"小明","age":16}');//Object {name: "小明", age: 16}
JSON.parse('true');//true
JSON.parse('123.45');//123.45

// JSON.parse()还可以接收一个函数,用来转换解析出的属性
var obj = JSON.parse('{"name":"小明","age":15}',
function(key,value){
    if(key === 'name'){
        return value+'同学';
    }
    return value;
});
console.log(JSON.stringify(obj));//{"name":"小明同学","age":15}
// 访问API获取JSON
var url = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20%3D%202151330&format=json';
$.getJSON(url,function(data){
    var city = data.query.results.channel.location.city;
    var forecast = data.query.results.channel.item.forecast;
    var result = {
        city:city,
        forecast:forecast
    };
    console.log(JSON.stringify(result,null,'  '));
})