Javascript教程

Javascript教程
laiqun@msn.cn

Contents

1. javascript嵌入方式

  • 放入head <script> </script>
  • 放入js文件 <script src="./xx.js"> </script>

2. javascript语法

  • ;为结尾,每行后面会自动加;最好不要省略,压缩取空白换行字符后,代码会出问题。
  • 为语句块
  • //为行注释 /**/为块注释

3. 数据类型

数值类: Number:包含浮点数,负数 

NAN:不是数 使用isNan函数来判断是否为NAN 

Infinity无限大 

字符串:以单引号或者双引号括起来的任意文本 

布尔值: || && 

比较运算符: == 比较时自动进行类型转换,比较诡异,最好不要用 === 比较时不会进行类型转换 

常识:

浮点数的比较因为精度问题不会相等 

(1/3) === (1-2/3) //false 

原因:浮点数的精度表示,应用Math.abs(1/3-(1-2/3))<0.0000001 

null: 空 注意不是数字0 也不是空字符串“” 

undefine: 未定义,在判断是否传入了参数的时候用 数组: 

[]或者new Array(1,2,3);可通过[索引号]来访问 用Array.isArray来判断是否为数组 

对象:一组键值对构成想无序集合,可以.或者[索引名]来访问属性

  • 例如: var Person={name:‘Bob’,zipcode:null}

4. javascript变量

变量名的规则:大小写英文字母,$ 与_构成的集合,不能以数字开头,不能与关键字冲突。

赋值:= 声明一个变量用var,不用var表示全局,在不同的js文件中冲突后会造成覆盖影响。

  • 一般使用use strict,将变量范围限制在被声明的函数体内。

5. 字符串

  • 转义字符 ‘I ' m Ok’ \n换行 \t制表符 \字符 \x41 ANSI字符 \u4e2d unicode字符
  • 多行字符 ` `里面可以用${变量名}访问变量
  • 字符串的索引操作 s[0],由于字符串不可变,s[0]=‘a’执行正确但不会影响原字符串
  • 常见方法
    1. toUpperCase toLowerCase
    2. indexOf
    3. substring substring(1)获得去掉第一个字符以后的全部字符
    4. join 我比较喜欢称它为手拉手函数 [“abc”,“def”].join(“”)
  • 属性:length

6. 数组 多维数组

属性:length 直接给length赋值会导致Array的大小发生变化。

索引赋值:var arr=[1,2,3]; arr[5]=‘x’; 此时arr变成[1,2,3,undefine,undefine,‘x’]

常见方法:

  1. indexOf 指定元素的索引
  2. slice 与字符串的substring类似 slice(0,3) slice(1)获得去掉第一个元素的以后全部的元素,可以用来复制Array
  3. push pop 在尾部添加/删除元素 push可以添加多个,pop删除一个
  4. unshift 在头部加若干元素 shift将Array的第一个元素删除掉
  5. sort 排序
  6. reverse 反转
  7. splice 俗称万能方法,从制定位置删除若干元素后,再从该位置插入若干元素,返回删除的若干元素的数组。
  8. concat 将当前Array与另一个Array连接,可连接任意个Array,会把Array中的Array拆开
  9. join 两两之间手拉手,指定中间部分,会先将元素转换为字符串

7. 运算

  • || 短路求值 左为真,取左,否则取右
  • && 短路求值 左为假,取左,否则取右
  • !! 转换为boolean型

8. Map和set

对象可以视为Map或者dictionary的一种表示,即键值对。 对象中的属性key必须为字符串,实际上为Number或其他类型也可以,故引入map

方法:

  1. set 添加新的key-value对,一个key放多值,会冲掉前面的
  2. get 获取指定key的value
  3. has 是否存在指定key
  4. delete 删除键值对

set为不可重复的key的集合,用add添加,delete删除

  • iterable 适用于Array map set

    1.可用for of来遍历//此方法只遍历属于集合或字典本身的元素

    例子:
    1. 遍历set: for (var x of a ){alert(a)};
    2. 遍历Array:for(var x of a){alert(x[0]+'='x[1])}
  • for (var x in arr)的缺点,for of 对此的改进

    例如:var a=[1,2,3]; var a.name=‘hello’; for (var x in a){alert(x)};//会打印 ‘0’ ‘1’ ‘2’ ‘hello’
  • forEach方法—iterable内置的方法,传入一个函数,每次迭代都执行该回调函数

    例如:iterable对象.forEach(function(element,index,this){})//对于set element和index为同一个元素,因为它没有索引。一个函数参数为函数,此函数为高阶函数。

9. 流程控制

  • 条件判断 if(){}else{}
    • null undefine 0 ‘’ “” NaN 都认为是flase,其他为true
  • 循环
    • for(初始;调节;控制){}
    • while
    • do while();//注意后面有;号 至少会执行一次
    • for in 例如:var a =['A','B','C']; for (var i in a ){alert(i); alert(a[i]);//过滤到继承的属性用hasOwnProperty
      • Array的每一个索引项视为对象的属性。
    • break continue

10. 函数

  • 值传递与引用传递

对于数字、字符串等是将它们的值传递给了函数参数,函数参数的改变不会影响函数外部的变量。

对于数组和对象等则是将对象(数组)的变量的值传递给了函数参数,这个变量保存的指向对象(数组)的地址。当函数改变这个地址指向的对象(数组)的内容时,同时也改变了函数外部变量指向的对象(数组)的内容;当函数改变的是变量的地址时,实际就与函数外部的变量失去了联系,变成了完全不同的对象了,不会对函数外部对象造成改变。

var v1 = []
var v2 = {};
var v3 = {};
function foo(v1, v2, v3)//这里分别是给v1 v2 v3 赋值新对象,对外部的对象不会有改变
{
    v1 = [1];
    v2 = [2];
    v3 = {a:3}
}

foo(v1, v2, v3);
alert (v1); // 空白 
alert (v2); // [object Object] 
alert (v3.a); // undefined
var v1 = []
var v2 = {};
var v3 = {a:0};
function foo(v1, v2, v3)//这里不是赋值新对象,而是直接操作它,会造成函数外部对象改变
{
    v1.push (1);
    v2.a = 2;
    v3.a = 3;
}

foo(v1, v2, v3);
alert (v1); // 1 
alert (v2.a); // 2 
alert (v3.a); // 3

定义: 
1. function fun_name(prama1,prama2){…} 
2. var var_name = function (prama1,prama2){};//注意最后有一个分号 
方法2存在变量提升问题: 
例如: 
1. alarm(1); function alarm(){} 
2. alarm(1); var alarm= function (){}//此处因为变量提升,实际顺序为 var alarm; alarm(1); alarm = function(){};//会在alarm(1)那句报错,alarm is undefine

函数结束的两种情况: 
1. return 
2. 执行到末尾,无return,返回undefine 
参数传递:

  1. arguments关键字 像Array,实际不是Array。其属性length存着参数个数。

  2. ..rest关键字 如果参数过多,用for循环一个个取的话比较麻烦

    例如:

    1. function (a,b ){ var i,var rest=[]; if(arguments.length>2) { for(i=2;i<arguments.length;i++) rest.push(arguments[i]) } }
    2. function(a,b,...rest){}

返回值坑:

  1. return 多行
    return {

    name:‘foo’; }
    1. return //由于javascript的默认加;机制,实际执行的为return ; 故返回undefine
    {name:'foo'}

11. 变量提升、变量的作用域

  • 查找变量的机制:从自身函数开始,由内向外查找,如有同名,优先使用从内层找到的。 故内部函数可以访问外部函数的变量 function foo()

{

var x = 1;

function bar()

{

 var y = x+1;

}

var z = y+1;//reference Error

} * 变量提升,是由变量的查找机制引起的。 ‘use strict’

function foo()

{

var x = ‘hello ’+y;

alert(x);//打印为 hello undefine

var y = ‘bob’;

}

  • 由于变量的查找机制,现在函数内查找,找到了,于是相当于这样的顺序 var y ; var x =“hello”+y; y=‘bob’。

  • 如何简单理解? 将内部所有var 定义都放在函数体前面。

  • 名字空间:全局变量会绑定到window上,相同的全局变量会冲突

    • 减少冲突的方法把自己用的所有变量和函数全部绑定到一个全局变量中

      例如:

      var MYAPP={};//MYAPP在这里叫名字空间 JQUERY YUI underscore都是这么干的

      MYAPP.name =“myapp”;

      MYAPP.version = 1.0;

      MYAPP.foo = function (){};

  • 局部作用域:默认作用域为函数内部

    ‘use strict’

    function()

    {

    for(var i =0;i<100;i++){;}

    i+=100;//仍然可以引用该变量

    }

    • 如何实现块级作用域? 将上面的var 改为let
  • 常量

    曾经:var PI = 3.14 ;//用大写变量名表示不要修改它

    现在可用const,和let一样具有块级作用域。

    例如:const P1=3.14; 修改不报错,但是没有效果


12. 高阶函数

  • 什么是高阶函数?

    一个函数的参数为一个函数。例如:

    function add(x,y,f) {
    return f(x)+f(y);
    }
  • map

    有一个函数f(x)= x2,作用在[1,2,3,4,5,6,7,8,9]上,map的实现如下:

     map

    实现:

    function pow(x) {
     return x*x;
    }
    var arr=[1,2,3,4,5,6,7,8,9];
    arr.map(pow);

    等价形式:

    for(var i =0 ;i<arr.length;i++){//此代码缺点:不易读懂,而map可以一行搞定
     result.push(pow(arr[i]));
    }
  • reduce

    Array.reduce方法在[x1,x2,x3,x4]上,效果是:

    [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;
    });
  • filter

    filter与map类似,但其是根据return 的false true来决定每个元素的去留。map reduce filter 对undefine的处理?对map来说undefine还是undefine 对filter不理不理undefine

  • sort:对Array直接修改,返回的是同一个Array

    • 默认是按照ASCII码排序

      用例:

      ['Google','Apple','Microsoft'].sort();//['Apple','Google','Microsoft']
      ['Google','Apple','Microsoft'].sort();//['Google','Microsoft','apple']
      [10,20,1,2].sort();//[1,10,2,20]为什么?先转化为字符串再string排序

      如何实现对数字排序?

      传入排序方法函数

      var arr=[10,20,1,2];
      arr.sort(
      function(x,y)
      {
      if(x>y)
        return 1;//将x后移  实现升序排列if(x<y)
        return -1;
       return 0;
      }
      );

13. 闭包

  • 什么是闭包,与高阶函数相似的概念

    高阶函数指接受一个函数作为参数的函数。同样函数也可以作为返回值,就叫闭包。

    function sum(arr) {
    return arr.reduce(
     function(x,y)
     {
       return x+y;
     }
    );
    }
    var f=sum([1,2,3,4,5]);//f接受一个函数
    f();//调用,才是真正计算结果。

    内部函数sum可以引用外部函数的参数和局部变量,相关参数和变量都般存在返回的函数中,称为“闭包”。

    每次调用都产生一个新函数,即使参数相同。

  • 闭包的用途

    • 私藏变量:

      用例:

      function count() {
      var arr =[];
      for(var i=1;i<=3;i++)
      {
      arr.push(function(){return i*i;});
      }
      return arr;
      }
      var result = count();
      result.map(funciton(x){console.log(x());})
      //会打印16 16 16 为什么不是1 4 9 

      由于变量的查找机制,arr中function没有找不到i的定义,于是向外层函数找,找到时此变量值为4。

      返回函数不要引用任何循环变量,或者后续会发生变化的量

      一定要引用循环变量怎么办,用闭包私藏起来,方法:创建一个匿名函数并立即执行,匿名函数引用变量并返回内部函数

      function count() {
      var arr =[];
      for(var i=1;i<=3;i++)
      {
      arr.push(
       (function (n){
         return function(){return n*n;};
       })(i);//创建匿名函数并立即执行,匿名函数参数为i
       );
      }
      return arr;
      }

      创建匿名函数名立即执行:

      function (x) {return x*x;}(x)
      //理论上的写法;但与函数体的定义冲突,Syntax Error
      (function (x) {return x*x;})(x)
      //给函数的定义加上括号

      私藏的变量,即时是外部也无法访问,并不与函数对象的原变量并不是同一个。例子:

      '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

      对于每个返回的函数,其外部函数参数和局部变量,都是独立的,并且相对于create_count.x也是独立的

    • 封装函数

      用例:经常使用x2和x3,可生成两个函数,可以简化后期的参数输入

      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

14. 箭头函数

相当于匿名函数,当仅有一个表达式的时候,连{}和return 都省略了;有多条语句,就不能省略{}和return了。

f= x=> x*x;
x=>{
  if(x>0)
    return x*x;
  elsereturn -x*x*x;
}
(x,y)=>x*x+y*y;//两个参数
()=>3.14;//无参数
(x,y,...rest)=>{};//可变参数
 x=>{foo:x};//返回一个对象//因为和函数体的定义冲突,需要改为
 x=>({foo:x})
  • 箭头函数可以解决函数内部定义函数this指向问题

    var obj={
    birth:1990,
    getAge: function () {
         var b = this.birth; // 1990
        var fn = function () {
             return new Date().getFullYear() - this.birth; // this指向window或undefined
         };
         return fn();
     }
    } 
    改进方法1 bind
    getAge: function () {
         var b = this.birth; // 1990
        var fn = function () {
             return new Date().getFullYear() - this.birth; // this指向window或undefined
         };
         return fn().bind(this);
     }  
    改进方法2 => :此时that就不需要了
     var fn=()=>new Date().getFullYear()-this.birth;
    //this此时指向window或undefine(strict模式下);

15. 生成器

  • 什么是生成器? 标志是 函数名前面有* 里面有yield

    可在执行过程中返回多次,像一个可以记住状态的函数。生成器函数返回的是一个生成器对象。

    function * fab(max){
    var t,a=0,b=1,n=1;
    while(n<max)
    {
      yield a;
      t=a+b;
      a=b;
      b=t;
      n++;
    }
    return a;
    }
  • 调用生成器对象的两个方法: next for of
    var f = fab(5);
    f.next();//{value:0,done:false}
    f.next();//{value:0,done:false}
    f.next();//{value:0,done:true}//next方法需要我们根据返回对象的done属性判断是否完成。
    for (var x of fab(3))//用该方法不用判断返回对象的done属性
  • 生成器的用途 解决回调地狱

    ajax('http://url-1', data1, function (err, result) {
     if (err) {
         return handle(err);
     }
     ajax('http://url-2', data2, function (err, result) {
         if (err) {
             return handle(err);
         }
         ajax('http://url-3', data3, function (err, result) {
             if (err) {
                 return handle(err);
             }
             return success(result);
         });
     });
    });
    上面的写法回调越多,越难看
    try {
     r1 = yield ajax('http://url-1', data1);
     r2 = yield ajax('http://url-2', data2);
     r3 = yield ajax('http://url-3', data3);
     success(r3);
    }
    catch (err) {
     handle(err);
    }
    //看上去同步的代码,实际上是异步的。只是写法上的改变而已。
    yield与generator(*)结构,结合Promise的then catch,改善了异步回调的写法,Async await生成方法被称为改善回调地狱的最后亮光。

16. 对象

  • 无序的集合类型,由若干的键值对构成
var xiaoming = { name:"小明",'middle-school':'No.1 middle school'};//最后一个属性不要加,

访问属性用.或者[‘属性名’] 注意middle-school属性并非变量名,只能用xiaoming['middle-school]的方式来访问。

检测是否有某属性/方法用in 。 ‘name’ in xiaoming ; ‘toString’ in xiaoming; toString定义在Object中,xiaoming的原型链指向Object。

检测时自身拥有的属性,还是继承来的用hasOwnProperty方法


17. 方法

什么叫方法?将一个函数绑定到一个对象

  • this 指向问题

例1:

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

执行xiaoming.age()//里面的this指向xiaoming

例2://分开写

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

执行xiaoming.getAge()//里面的this指向xiaoming 执行getAge() //返回NAN this指向window

例3:

'use strict'var xiaoming ={
  name:"小明",
  birth:1990;
  age:function(){
    var y = new Date().getFullYear();
    return y -this.birth;
   }
}
var fn = xiaoming.age();
fn();//非strict下指向window,strict下指向undefine

例4:

var xiaoming ={
  name:"小明",
  birth:1990;
  age:function(){//函数内部定义函数this的问题function getAgeFromBirth()
    {
      var y = new Date().getFullYear();
      return y-this.birth;
    }
    return getAgeFromBirth();  
   }
}

执行xiaoming.age()//里面的this非strict下指向window,strict下指向undefine。

修正:

var xiaoming ={
  name:"小明",
  birth:1990;
  age:function(){//函数内部定义函数this的问题
     that = this;    
     function getAgeFromBirth()
    {
      var y = new Date().getFullYear();
      return y-that.birth;
    } 
    return getAgeFromBirth();
   }
}
  • 对于函数内定义的函数,this非strict下指向window,strict下指向undefine。

  • 如何控制this的指向? call apply bind

    使用方式:

    1. 函数名.apply(对象,打包成Array的参数)
    2. 函数名.call(对象,依次传入的参数)
  • 装饰器模式的实现 使用apply实现

    例如:统计一下使用了多少次parseInt

    var count = 0;
    var oldparseInt = pareseInt;//保存原函数
    window.parseInt = function ()
    {
      count+=1;
      return oldparseInt.apply(null,arguments);
    }
    parseInt('10');
    parseInt('20');
    parseInt('30');

18. 标准对象

  • typeof typeof获取对象的类型,是操作符,它返回一个字符串
    typeof 123;//'number'
    typeof NaN;//'number'
    typeof 'str';//'string'
    typeof true;//'boolean'
    typeof undefine;//'undefined'
    typeof Math.abs;//'function'
    typeof null;//'object'
    typeof [];//'object'
    typeof {};//'object'
  • 包装对象 存在的问题:用了new Number/Boolean/String,类型变成了‘object’
    typeof new String('str');//'object'new String('str') ==='str'//false
    不要使用包装对象
  • 类型转换 Number() Boolean() String 转换后卫相应的类型,非‘object’

总结:

  • 不要使用new Number()、new Boolean()、new String()创建包装对象;

  • 用parseInt()或parseFloat()来转换任意类型到number;

  • 用String()来转换任意类型到string,或者直接调用某个对象的toString()方法;

  • 通常不必把任意类型转换为boolean再判断,因为可以直接写if (myVar) {…};

  • typeof操作符可以判断出number、boolean、string、function和undefined;

  • 判断Array要使用Array.isArray(arr);

  • 判断null请使用myVar === null;

  • 判断某个全局变量是否存在用typeof window.myVar === ‘undefined’;

  • 函数内部判断某个变量是否存在用typeof myVar === ‘undefined’。

  • 最后有细心的同学指出,任何对象都有toString()方法吗?null和undefined就没有!虽然null还伪装成了object类型。

  • 更细心的同学指出,number对象调用toString()报SyntaxError:

    123.toString(); // SyntaxError

    遇到这种情况,要特殊处理一下: 123..toString(); // ‘123’, 注意是两个点! 因为Number类型有浮点数 (123).toString(); // ‘123’


19. Date对象

var now = new Date();
now;//var now = Date();
now;//wed jun 24 2015 19:49:22 GMT +800(CST)
now.getFullYear();//2015
now.getMonth();//0-11
now.getDate();//24 24号
now.getDay();//星期     这个比较特别  其他的都是年月日时分秒毫秒
now.getHours();//24小时制
now.getMinutes();//49分钟
now.getSeconds();//秒
now.getMilliseconds();//毫秒数
now.getTime();//时间戳 13位 以Number形式,表示1970年1月1日0点整经过的毫秒数,在GMT时区下
new Date(2015,5,19,20,3,3,123);//年月日时分秒毫秒时区
var d = Date.parse('2015-06-24T19:24:22.876+0800');//T后面接时间  +/-hh:mm表示相对于UTC超前/滞后的时间
var time = Date(d);//将时间戳转化为人可以读懂的时间
now.toLocaleString();//转换为当地时间
now.toTimeString();//UTC+8为中国时间

时区知识:GMT 格林威治时间 UTC世界协调时间 DST夏日节约时间 CST同时表示4个时区,用+hh:mm表示区别


20. 正则表达式

匹配字符串强有力的武器,凡是符号规则的字符串。

入门:

直接给出某字符为精确匹配

\d 匹配一个数字

\w一个字母或数字或与[a-zA-Z0-9]等效

. 任意一个字符

  • 变长任意个字符

  • 至少一个字符

? 0个或1个字符

{n} n个字符

{n,m} n-m个字符

例子: \d{3}\S+\d{3,8} 

\d{3}表示匹配3个数字 

\S+ 表示至少一个空白字符(空格 tab等)

\d{3,8}表示3-8个数字

 

\d{3}\-\d{3,8} 可以匹配010-1235 

 

-是特殊字符需要转义

进阶:

[]表示字符范围,如[0-9a-z]

[]可用A|B,表示匹配A或者B

表示行头,在[]中表示非 ^\d必须以数字开头

$表示行尾 \d$必须以数字结尾

正则表达式的创建

  1. /正则表达式/ 去掉里面的正则表达式,则变成了行注释的符号

  2. new RegExp(‘正则表达式’); ‘ABC\-001’ \实际为一个,需要转义 用途: 1.测试给定的字符串是否符合条件 2.用于切分字符串 split

    'ab  c'.split('');//['a','b',' ',' ','c']存在多个空格符怎么办
    'ab  c'.split(/\s+/);//['a','b',' ',' ','c']  存在","怎么办?
    'a,b,  c'.split(/[\s,]+/);//['a','b',' ',' ','c'] 存在";"呢?
    'a,b;  c'.split(/[\s,;]+/);//['a','b',' ',' ','c']

    相关函数: match一个或多个,返回数组 search 第一个 replace split

  3. 分组和提取子串 子表达式的反向引用\2 \1

    var re = /^(\d{3}-(\d{3,8}))/;
    re.exec('010-12345');//['010-12345','010','12345'];
    re.exec('010 12345');//null
    /(\d)(\d)\2\1/;//可以匹配5775 1221 之类的字符串  \2 反向引用子式2 \1反向引用子式1
  4. 全局搜索 g(可执行多次匹配,不加指匹配一次) i(忽略大小写) m多行匹配(影响多行中^$的匹配) 

    var r1=/test/g;//或者 var r1=new RegExp(‘test’,‘g’); 

    可执行多次exec,每次exec会更新lastIndex属性 lastIndex leftcontent rightcontent

    var s = 'JavaScript, VBScript, JScript and ECMAScript';
    var re=/[a-zA-Z]+Script/g;
    // 使用全局匹配:
    re.exec(s); // ['JavaScript']
    re.lastIndex; // 10
    re.exec(s); // ['VBScript']
    re.lastIndex; // 20
    re.exec(s); // ['JScript']
    re.lastIndex; // 29
    re.exec(s); // ['ECMAScript']
    re.lastIndex; // 44
    re.exec(s); // null,直到结束仍没有匹配到

正则表达式的局限:像‘2-30’ ‘4-31’这样的非法日期,用正则表达式是识别不了或者实现困难,要配合程序来处理

正则表达式的贪婪匹配:

var re=/^(\d+)(0*)/;
re.exec('102300');//["102300","102300",""]//加个?表示非贪婪匹配
re=/^(\d+?)(0*)/;
re.exec('102300');//["102300","1023","00"]
re=/1{3}/;//匹配11111111 会得到 111 111 第一次匹配会更新lastIndex属性

元字符的分类:1.限定次数符 {n} {n,m} + * ? 2.选择匹配符 3.分组组合与反向引用 4.特殊字符 5.字符匹配符 6.定位符


21. JSON

JSON之前一直使用XML,犹豫XML规范众多,Douglas发明了JSON。

JSON中一共有number、boolean、string、null、array([])、object({…}),并规定死了字符集为UTF-8,规定字符串必须用“”,

object的键也必须用“”。

方法:

  1. 序列化—字符串化

    1. 使用JSON类的stringify(对象,筛选属性数组或一个函数,每个键值对都会被函数先处理,缩进)。

    2. 写出对象的TOJSON方法,返回JSON应该序列号的数据

  2. 反序列化—将其转换为对象

    1. 使用eval执行表达式,太过危险

    2. JSON.parse(字符串,函数) 传入函数会处理键值对


22. 面向对象

javascript不区分类和实例的概念,而是通过原型(prototype)来实现。

var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

var xiaoming = {
    name: '小明'
};

xiaoming.__proto__ = Student;
//把xiaoming的原型指向了对象Student,看上去xiaoming仿佛是从Student继承下来的

obj

javascript所有对象都是实例,继承是把一个对象的原型指向另一个对象。 实际中不要使用__proto__属性改变一个对象原型,

可通过编写函数配合Object.create(传入原型)的方法:

例如:

// 原型对象:var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

function createStudent(name) {
    // 基于Student原型创建一个新对象:
var s = Object.create(Student);
    // 初始化新对象:
    s.name = name;
    return s;
}

var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true

22.1. 创建对象

每创建一个对象都会设置一个原型

  • 属性/方法访问机制

    当访问obj.xxx属性时,现在当前对象查找属性,没找到,再到原型对象找,还未找到,再到Object.prototype对象,

    如果仍然没有找到,放回undefine

  • 原型链

    var arr=[1,2,3];
    //原型链为 arr->Array.prototype->object.prototype->nullfunction foo(){return 0;}
    //原型链为 foo->Function.prototype->object.prototype->null//Function中有Apply方法
  • 使用构造函数创建对象

    function Student(name) {//注意创建对象的函数首字母为大写,普通函数为小写this.name = name;
     this.hello = function () {
         alert('Hello, ' + this.name + '!');
     }
    }
    var xiaoming = new Student(' 小明');
    //写new的作用 函数会变成构造函数: 1. 内部this指向新创建的对象 2 默认返回this
    //不写new 返回undefine

    如果还有别的对象,其原型链为:

xiaoming ↘

xiaohong -→ Student.prototype —-> Object.prototype —-> null

xiaojun ↗

obj2

xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true

Object.getPrototypeOf(xiaoming) === Student.prototype; // true

xiaoming instanceof Student; // true
//函数Student恰好有个属性prototype指向xiaoming、xiaohong的原型对象
//但是xiaoming、xiaohong这些对象可没有prototype这个属性,
//不过可以用__proto__这个非标准用法来查看。
  • 如何共享同一个函数,而不是每个对象各有一份代码?

    xiaoming.hello === xiaohong.hello;//false

    将函数写入prototype,修改代码如下:

    function Student(name) {
      this.name = name;
    }
    Student.prototype.hello = function () {
      alert('Hello, ' + this.name + '!');
    };//注意此处有分号
  • 构造函数忘记写new会怎样?

    在strict模式下,this执行undefine,非stric模式下,this执行window,会创建全局变量;jslint可以检查出漏泄的new

  • 如何防止漏写new? 编写函数,将new封到函数里面

    function Student(props) {
      this.name = props.name || '匿名'; // 默认值为'匿名'
    this.grade = props.grade || 1; // 默认值为1
    }
    Student.prototype.hello = function () {
      alert('Hello, ' + this.name + '!');
    };
    function createStudent(props) {
      return new Student(props || {})
    }
    var xiaoming = createStudent({//传入参数
      name: '小明'
    });
    //好处 1.不用new来调用 2.传入参数灵活,由于参数是对象,我们可以把JSON转为对象直接传入。
    xiaoming.grade; // 1  修改参数

22.2. 原型继承

基于上街Student扩展出PrimaryStudent,可以先定义出PrimaryStudent:

function PrimaryStudent(props) {
    // 调用Student构造函数,绑定this变量:
    Student.call(this, props);
    this.grade = props.grade || 1;
}

调用了Student构造函数不等于继承了Student,PrimaryStudent创建的对象的原型是:

new PrimaryStudent() —> PrimaryStudent.prototype —> Object.prototype —> null

必须想办法把原型链修改为:

new PrimaryStudent() —> PrimaryStudent.prototype —> Student.prototype —>Object.prototype —> null

PrimaryStudent对象既可以调用PrimaryStdent.prototype定义得到方法,也可以调用原型链上的方法。

如果你想用最简单粗暴的方法这么干:

PrimaryStudent.prototype = Student.prototype;

是不行的!如果这样的话,PrimaryStudent和Student共享一个原型对象,那还要定义PrimaryStudent干啥?

我们必须借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向Student.prototype。

为了实现这一点,参考道爷(就是发明JSON的那个道格拉斯)的代码,中间对象可以用一个空函数F来实现:

pro

// PrimaryStudent构造函数:function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}

// 空函数F:function F() {
}

// 把F的原型指向Student.prototype:  1.先指明F对象创建模板为Student,F内才能找到Student对象的属性
F.prototype = Student.prototype;

// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();//2// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;//3
// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};

// 创建xiaoming:var xiaoming = new PrimaryStudent({
    name: '小明',
    grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2// 验证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true// 验证继承关系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
  • 进一步封装

    如果把继承这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码:

    function inherits(Child, Parent) {//此函数可以复用
    var F = function () {};
     F.prototype = Parent.prototype;
     Child.prototype = new F();
     Child.prototype.constructor = Child;
    }

    使用:

    function Student(props) {
     this.name = props.name || 'Unnamed';
    }
    Student.prototype.hello = function () {
     alert('Hello, ' + this.name + '!');
    }
    function PrimaryStudent(props) {
     Student.call(this, props);
     this.grade = props.grade || 1;
    }
    // 实现原型继承链:
    inherits(PrimaryStudent, Student);
    // 绑定其他方法到PrimaryStudent原型:
    PrimaryStudent.prototype.getGrade = function () {
     return this.grade;
    };

    JavaScript的原型继承实现方式就是:

    1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this;

    2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;

    3. 继续在新的构造函数的原型上定义新方法。

  • 新方法 使用class关键字来实现原型继承

在上面的章节中我们看到了JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,

最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。

有没有更简单的写法?有!

新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。

我们先回顾用函数实现Student的方法:

function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
}

如果用新的class关键字来编写Student,可以这样写:

class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}

比较一下就可以发现,class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字),

这样就避免了Student.prototype.hello = function () {…}这样分散的代码。

最后,创建一个Student对象代码和前面章节完全一样:

var xiaoming = new Student('小明');
xiaoming.hello();

class继承

用class定义对象的另一个巨大的好处是继承更方便了。想一想我们从Student派生一个PrimaryStudent需要编写的代码量。

现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends来实现:

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

注意PrimaryStudent的定义也是class关键字实现的,而extends则表示原型链对象来自Student。子类的构造函数可能会与父类不太相同,

例如,PrimaryStudent需要name和grade两个参数,并且需要通过super(name)来调用父类的构造函数,否则父类的name属性无法正常初始化。

PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法。

ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们

自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。

你一定会问,class这么好用,能不能现在就用上?

现在用还早了点,因为不是所有的主流浏览器都支持ES6的class。如果一定要现在就用上,就需要一个工具把class代码转换为传统的prototype代码,

可以试试Babel这个工具。


23. 浏览器对象

  • 现有的浏览器内核与javascript引擎

    • IE 6~11:IE10开始支持ES6标准。 trident引擎
    • Chrome:Webkit内核 JavaScript引擎——V8
    • Sarafi:Webkit内核
    • Firefox:Gecko内核 JavaScript引擎OdinMonkey。
    • 移动设备上目前iOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,都是Webkit核心
  • window 对象不但充当全局作用域,而且表示浏览器窗口。

    • innerWidth和innerHeight属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
    • 兼容性:IE<=8不支持。
    • 对应的,还有一个outerWidth和outerHeight属性,可以获取浏览器窗口的整个宽高。
  • navigator对象表示浏览器的信息,最常用的属性包括:

    • navigator.appName:浏览器名称;
    • navigator.appVersion:浏览器版本;
    • navigator.language:浏览器设置的语言;
    • navigator.platform:操作系统类型;
    • navigator.userAgent:浏览器设定的User-Agent字符串。 注意,navigator的信息可以很容易地被用户修改,用if判断浏览器版本很难维护,要善用短路运算符
      var width= window.innerWidth||document.body.clientWidth;
  • screen对象表示屏幕的信息,常用的属性有:

    • screen.width:屏幕宽度,以像素为单位;
    • screen.height:屏幕高度,以像素为单位;
    • screen.colorDepth:返回颜色位数,如8、16、24。
  • location对象表示当前页面的URL信息。例如,一个完整的URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP

    属性:

    • location.protocol; // ‘http’
    • location.host; // ‘www.example.com’
    • location.port; // ‘8080’
    • location.pathname; // ‘/path/index.html’
    • location.search; // ‘?a=1&b=2’
    • location.hash; // ‘TOP’
    • location.href //表示当前URL

    方法:

    • assign 加载一个新页面。例如:location.assign(‘/discuss’);
    • reload 重新加载当前页面
  • document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。

    属性:

    1. title 是从title标签内容中读取的,可以动态修改。

      document.title = ‘努力学习JavaScript!’;

    2. cookie

      • cookie是什么?

      Cookie是由服务器发送的key-value标示符。因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。

      当一个用户成功登录后,服务器发送一个Cookie给浏览器,例如user=ABC123XYZ(加密的字符串)…,此后,浏览器访问该网站时,会在请求头附上这个Cookie,服务器根据Cookie即可区分出用户。

      • cookie的安全

      javascript可通过document.cookie来读取到当前页面的cookie。如果浏览器嵌入了第三方的javascript,会造成巨大隐患。

      服务器在设置cookie时可以使用httponly这样的选项,这样的cookie就不能通过javascript代码读取了。

    获取节点的方法

    var menu = document.getElementById('');
    menu.tagName;//输出标签名var drinks = document.getElementByTagName('');
  • history

    方法:

    1. back 后退 在存在大量AJAX交互的页面里,粗暴的使用back方法会让用户感觉愤怒。
    2. forwar 前进

    实现无刷新更换URL地址

    window.history.pushState({},0,url);

    利用ajax配合pushState翻页无刷新的动作


24. 操作DOM

由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。

树形结构:

  1. 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;

  2. 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;

  3. 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;

  4. 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。

  5. 遍历获取DOM节点—在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。

    1. 通过document的方法和属性

      1. getElementById(‘ID名’)
      2. getElementsByTagName(‘标签名’)[索引号]
      3. getElementsByClassName(‘类名’)[索引号]

      获得某节点下的所有直属点

      通过属性:

      1. var cs= test.children;
      2. var cs= test.firstElementChild;
      3. var cs= test.lastElementChild;
    2. 通过选择器方法querySelector 和QuerySelectorAll() 需要了解select的用法,类似于JQUERY 的选择器。

node和element的区别?

node更为广义,包括了element、comment、CDATA_SECTION等多种,但绝大时候,我们只关心element,因为他是可以看到的,comment这种是不可见的。

  • 插入 如果dom节点是空的,可以通过innetHTML来写

    1. appendChild 插入到父节点的最后一个节点。例如:

      var d = document.createElement('style');
      d.setAttribute('type', 'text/css');
      d.innerHTML = 'p { color: red }';
      document.getElementsByTagName('head')[0].appendChild(d);
    2. parentElement.insertBefore(newElement,referenceElement) 插入到参照节点前

      haskell = document.createElement('p');
      haskell.id = 'haskell';
      haskell.innerText = 'Haskell';
      list.insertBefore(haskell, ref);
  • 删除—要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉:

    • removeChild

      删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。

      children属性是一个只读属性,并且它在子节点变化时会实时更新。
  • 更新—也就是修改

    1. innerHTML属性 隐患通过网络得到的还有标签的字符串,会造成XSS攻击
    2. innerText或TextContent属性 会自动对字符串进行HTML编码,‘<span>’被编码为‘&lt;span&gt;’保证其不会产生任何HTML标签。
      • innerText 读取时无法获得隐藏元素文本。

    修改CSS,通过节点的style属性,由于CSS允许font-size并非javascript有效的属性名,需要将其改为驼峰式,fontSize

    p.style.fontSize='20px';

25. 操作表单

表单为可以获得用户输入的内容,或者对输入框设置新的内容。

  • 表单控件

    • 文本框,对应的<input type="text">,用于输入文本;

    • 口令框,对应的<input type="password">,用于输入口令;

    • 单选框,对应的<input type="radio">,用于选择一项;同一组单选框name属性必须一致

    • 复选框,对应的<input type="checkbox">,用于选择多项;同一组复选框name必须一致

    • 下拉框,对应的<select><option value=""></option>,用于选择一项;

    • 隐藏文本,对应的<input type="hidden">,用户不可见,但表单提交时会把隐藏文本发送到服务器。

  • 获取表单值的方式

    为输入框设置label标签的函数,单击该处文本,焦点会自动跑到对应输入框上。增大了可单击区域。

    • 通过value属性来获得用户输入的值。
    • 单选框和复选框的特殊性,value是HTML预设的值,是否勾选,用checked来判断
    // <label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
    // <label><input type="radio" name="weekday" id="tuesday" value="2"> Tuesday</label>
    var mon = document.getElementById('monday');
    var tue = document.getElementById('tuesday');
    mon.value; // '1'
    tue.value; // '2'
    mon.checked; // true或者false
    tue.checked; // true或者false
  • 设置表单值的方式

    • 直接修改vaule属性的值
    • 单选框和复选框设置checked为ture或false
  • HTML5表单控件

    <input type="date" value="2015-07-01">日期

    <input type="datetime-local" value="2015-07-01T02:03:04">日期和本地时间

    <input type="color" value="#ff0000">颜色选择器

    对于不支持的浏览器会当作 type=“text”来显示。

  • 提交表单的两种方式

    1. 获得表单元素后调用它的submit();方法
    2. 浏览器默认点击<button type="submit">时提交表单,或者用户在最后一个输入框按回车键。需要使用form的onSubmit属性
      <!-- HTML -->
      <form id="test-form" onsubmit="return checkForm()">
      <input type="text" name="test">
      <button type="submit">Submit</button>
      </form>
      <script>
      function checkForm() {
      var form = document.getElementById('test-form');
      // 可以在此修改form的input...// 继续下一步:return true;  //return flase则不会提交
      }
      </script>
  • 防止提交表单时输入框闪烁 在检查和修改<input>时,要充分利用<input type="hidden">来传递数据。

例如,很多登录表单希望用户输入用户名和口令,但是,安全考虑,提交表单时不传输明文口令,而是口令的MD5。

普通JavaScript开发人员会直接修改<input>

这个做法看上去没啥问题,但用户输入了口令提交时,口令框的显示会突然从几个变成32个(因为MD5有32个字符)。

要想不改变用户看到这个现象,可以利用<input type="hidden">实现:

<form id="login-form" method="post" onsubmit="return checkForm()">
<input type="text" id="username" name="username">
<input type="password" id="input-password">
<input type="hidden" id="md5-password" name="password">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
    var input_pwd = document.getElementById('input-password');
    var md5_pwd = document.getElementById('md5-password');
    // 把用户输入的明文变为MD5:
    md5_pwd.value = toMD5(input_pwd.value);
    // 继续下一步:return true;
}
</script>

26. 操作文件

在HTML表单中,可以上传文件的唯一控件就是<input type="file">

注意:当一个表单包含<input type="file">时,表单的enctype必须指定为multipart/form-data,method必须指定为post,

浏览器才能正确编码并以multipart/form-data格式发送表单的数据。

出于安全考虑,浏览器只允许用户点击<input type="file">来选择本地文件,用JavaScript对<input type="file">的value赋值是没有任何效果的。

当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径。

通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:

var f = document.getElementById('test-file-upload');
var filename = f.value; // 'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') )) {
    alert('Can only upload image file.');
    return false;
}

由于JavaScript对用户上传的文件操作非常有限,以前都是用flash来上传,可以得到上传进度条等信息。

HTML5新增的File API允许JavaScript读取文件内容,获得更多的文件信息。 可通过name size lastModifiedDate获取信息。

  // 读取文件:var reader = new FileReader();
    reader.onload = function(e) {//回调函数,不知道什么时候读完,读完后自动调用我们设置的函数var
            data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'            
        preview.style.backgroundImage = 'url(' + data + ')';
    };
    // 以DataURL的形式读取文件:
    reader.readAsDataURL(file);//以Base64方式,常用于设置图像

27. js的调试

  • chrome的console:console.log console.dir —展开变量 console.trace()打印当前函数的调用堆栈

28. javascript事件  

  • 事件冒泡

 

  • 事件捕获

 

  • html事件

 

  • dom 0

 

  • dom 2
  • 取消事件冒泡

 

参考书籍《javascript高级程序设计第三版》 13章 事件部分  慕课网 DOM事件探秘

29. Ajax–Asynchronous javascript and xml

提交表单时会刷新页面,告诉你操作成功/失败,由于网络太慢或者其他原因,可能得到404。 

web的运作原理:一次HTTP请求对应一个页面 如何让用户停留在该页面,用javascript发送请求并接受数据,更新页面—Ajax

var request;
if (window.XMLHttpRequest) {//通过该属性确定是否支持标准XMLHttpRequest
// 不要判断navigator.userAgent 1.可以伪造 2.判断IE版本复杂
    request = new XMLHttpRequest();
} else {
    request = new ActiveXObject('Microsoft.XMLHTTP');
}
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成// 判断响应结果:
if (request.status === 200) {
            // 成功,通过responseText拿到响应的文本:
            return success(request.responseText);
        } else {
            // 失败,根据响应码判断失败原因:
            return fail(request.status);
        }
    } else {
        // HTTP请求还在继续...
    }
}
// 发送请求:
request.open('GET', '/api/categories');
//open方法有三个参数 第一个参数为请求的方法 ,
//第二个为请求的URL地址,第三个参数表明是否使用异步
//第三个参数默认为true 使用异步,如果为同步,浏览器停止响应知道Ajax完成。
request.send();
request.open('POST', '/api/categories');
request.setHeader//POST请求需要设置HEADER
senStr=
request.send();//POST请求需要在send中写上要发送的参数;GET不需要,因为参数在URL中

跨域访问—javascript的安全策略

javascript在发送请求时,URL域名必须和当前页面一致,一致的意思是:

  1. 域名要相同(www.example.com和example.com不同)
  2. 协议相同(http和https不同)
  3. 端口号相同(80和8080不同)

如果要访问外域(其他网站)的URL呢?

  1. 使用flash
  2. 设置一个代理服务器进行转发 proxy?url=‘http://www.sina.com’;由代理服务器返回结果,遵守了浏览器的同源策略,需要在服务器做开发。
  3. JSONP 只能使用GET请求,利用script标签的src属性,指明callback后,有外网生成js文件返回给客户端,并在js文件中调用我们写好的函数,把参数填好并调用。
    <script src="http://api.126.net/....?callback=refreshPrice"></script>
  4. HTM5跨域访问CORS coress origin resource shareing ajax

取决于sina.com是否愿意将你加入Access-Control-Allow-Origin:或者*

上面这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST(POST的Content-Type类型 

仅限application/x-www-form-urlencoded、multipart/form-data和text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345) CSS中的跨域请求

/* CSS */
@font-face {
  font-family: 'FontAwesome';
  src: url('http://cdn.com/fonts/fontawesome.ttf') format('truetype');
  /*CDN服务器的Access-Control=Allow-Origin不给权限,则无法加载字体资源*/
}

对于PUT、DELETE以及其他类型如application/json的POST请求,在发送AJAX请求之前,浏览器会先发送一个OPTIONS请求

(称为preflighted请求)到这个URL上,询问目标服务器是否接受:

OPTIONS /path/to/resourceHTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST

服务器必须响应并明确指出允许的Method,才会继续发送Ajax,否则抛出错误:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400

由于以POST、PUT方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POST和PUT请求,服务器端必须正确响应OPTIONS请求。


30. Promise

在javascript中所有代码都是单线程执行的。 所有javascript中所有的网络操作,浏览器事件都必须异步执行,异步执行可用回调函数实现。

异步函数会在将来的某个时间点出发一个函数调用。 如Ajax

request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成// 判断响应结果:
if (request.status === 200) {
            // 成功,通过responseText拿到响应的文本:
return success(request.responseText);
        } else {
            // 失败,根据响应码判断失败原因:
            return fail(request.status);
        }
    } else {
        // HTTP请求还在继续...
    }
}

假如有如下的写法,先统一执行ajax逻辑,不关心结果如何,

  var ajax=ajaxGet("http://...");

然后根据成功、失败调用sucess或者fail函数。 

ajax.ifSuccess(success); ajax.ifFail(fail);

 promise

Promise的then函数接受一个处理函数,此函数在处理成功时调用;catch接受一个处理函数,此函数在失败时调用。 例如:

  function test(resolve, reject) {
            //如果成功调用resolve
            resolve('200 OK');
            //如果失败,调用reject
            reject('timeout in ' + timeOut + ' seconds.');
}
var p1 = new Promise(test);//test为要执行的函数,接收两个函数作为参数,一个在成功时用,另一个在失败时
var p2 = p1.then(function (result) {
    console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
    console.log('失败:' + reason);
});
  var job1 = new Promise(function (resolve, reject) {
    log('start new Promise...');
    resolve(123);
});
job1.then(job2).then(job3).catch(handleError);//链式处理

同步

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

有一个返回即可收工

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
//只需要获得先返回的结果即可,多余是为了容错
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

Promise可以把很多异步任务以并行和串行的方式组合起来执行。


31. Canvas

没有canvas,绘图只能用flash插件,用javascript与flash进行交互。

  • 由于浏览器标准不一致,可在canvas标签中添加一些说明性HTML,如果支持,浏览器会忽略canvas标签的内容
    <canvas id="test-stock" width="300px" height="200px">
    <p>你的浏览器不支持canvas</p>
    </canvas> 
  • 判断浏览器是否支持canvas: 用javascript Canvas.getContext来测试浏览器是否支持Canvas

getContext(‘2d’)方法会让我们拿到CanvasRendringContext2D对象,所有绘图都要通过这个对象来完成。

3D图像要使用WebGL,getContext(‘Webgl’)

canvas坐标系统

can

Stroke 勾勒边框 反义词 fill来进行填充

canvas除了能绘制基本形状和文本,还可以实现动画、缩放、各种滤镜、像素变换等高级操作,如果要实现很复杂的操作,从以下几条优化:

  • 通过创建不可见的canvas来绘图,将最终结果复制到可见的canvas中
  • 使用整数坐标而不是浮点数
  • 创建多个canvas绘制不同的层,而不是在一个canvas中绘制特别复杂的图
  • 背景图片如果不变可以直接使用img标签并放到最底层


 





posted @ 2016-05-10 16:56  QQLQ  阅读(752)  评论(4编辑  收藏  举报