JavaScript基础知识总结

243 阅读16分钟

1 数据类型& 判断方法

  1. javaScript 有两种数据类型:
  • 基本(值)类型
    • string: 任意字符串

    • number: 任意的数字

    • boolean: true/false

    • undefined: undefined

    • null: null

        undefinednull 的区别:
        
        undefined是 未赋值
        null是 赋值了就是null,以后可以对此变量赋值,另外,变量 = null 也是一种垃圾处理机制。
      
  • 对象(引用)类型
    • Object: 任意对象
    • Function: 一种特别的对象(可以执行)
    • Array: 一种特别的对象(数值下标, 内部数据是有序的)
  1. 判断
  • typeof:
    • 可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
    • 不能判断: null与object object与array
  • ===
    • 不可以判断: undefined, null
var a,b

a = null
console.log(typeof a,a==='null')// 'object'  false

b = undefined
console.log(typeof b,a==='undefined')// undefined  false

  • instanceof:
    • 判断对象的具体类型,用来判断 A 是否为 B 的实例
  var b1 = {
    b2: [1, 'abc', console.log],
    b3: function () {
      console.log('b3')
      return function () {
        return 'fangyuan'
      }
    }
  }
  console.log(b1 instanceof Object, b1 instanceof Array) // true  false
  console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
  console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true

  console.log(typeof b1.b2, '-------') // 'object'

  console.log(typeof b1.b3==='function') // true

  console.log(typeof b1.b2[2]==='function')//false
  console.log(b1.b3()()) //b3 fangyuan
  • Object.prototype.toString
    • toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型,基本上所有对象的类型都可以通过这个方法获取到。
console.log(Object.prototype.toString.call(''))//[object String]

console.log(Object.prototype.toString.call(123))//[object Number]

console.log(Object.prototype.toString.call(true)) //[object Boolean]

console.log(Object.prototype.toString.call(undefined))//[object Undefined]

console.log(Object.prototype.toString.call(null))//[object Null]

console.log(Object.prototype.toString.call(new Function()))//[object Function]

console.log(Object.prototype.toString.call([1,2,4]))//[object Array]

变量 - 内存

  1. 2个引用变量指向同一个对象,通过一个变量修改对象内部的数据,其它变量看到的是修改后的数据。

注意下例中obj2.age = 12修改对象内部的数据

var obj1 = {name:'tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age)//12

function fn(obj){
    obj.name = 'lily'
}
fn(obj1)
console.log(obj2.name)//lily
  1. 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个变量依然指向前一个对象 注意下例中obj = {age:15}引用变量指向另一个对象
var a = {age:12}
var b = a
a = {name:`sam`,age: 18}
console.log(b.age,a.name,a.age)//12 sam 18

function fn2(obj) {
  obj = {age:15}
}
fn2(a)
console.log(a.age) //18
//函数也是对象
var a = 3
function fn(b) {
  b = b + 1
}
fn(a)
console.log(a)//3

2 对象

对象:多个数据的封装体,一个对象代表现实中的一个事物,属性值是函数叫方法

函数 :具有特定功能的封装体,只有函数可以执行; 提高代码复用;便于阅读 函数声明

test()

obj.test()

new test()

test.call/apply(obj) //可以让一个函数称为指定任意对象的方法进行调用

var obj = {}
function test2() {
  this.xxx = 'july'
}
//obj.test2()  不能直接调用,更本就没有
test2.call(obj) //可以让一个函数称为指定任意对象的方法进行调用
console.log(obj.xxx)

(1) new

1 以ctor.prototype为原型创建一个对象。

2 执行构造函数并将this绑定到新创建的对象上。

3 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。

function newOperator(ctor, ...args) {
  if (typeof ctor !== 'function') {
    throw new TypeError('Type Error');
  }
  const obj = Object.create(ctor.prototype);
  const res = ctor.apply(obj, args);

  const isObject = typeof res === 'object' && res !== null;
  const isFunction = typeof res === 'function';
  return isObject || isFunction ? res : obj;
}

(2) 构造函数创建对象

(1)使用工厂方法创建对象:

  通过该方法可以大批量的创建对象,使用的构造函数都是Object。 
  所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象

(2)因此使用构造函数创建对象:

 * 构造函数和普通函数的区别就是调用方式的不同
 * 普通函数是直接调用,而构造函数需要使用new关键字来调用
 * 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
 * 我们将通过一个构造函数创建的对象,称为是该类的实例
  • 构造函数的执行流程:

        * 	1.一旦出现new,立刻创建一个新的对象
        * 	2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
        * 	3.逐行执行函数中的代码
        * 	4.将新建的对象作为返回值返回   
    

3 this

* 解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是thisthis指向的是一个对象,这个对象我们称为函数执行的 上下文对象,
根据函数的调用方式的不同,this会指向不同的对象:
*1.以函数的形式调用时,this永远都是window
*2.以方法的形式调用时,this就是调用方法的那个对象
 *3 以构造函数的形式调用时,this是新创建的对象
 *4 使用call 和 apply 调用时,this是指定的那个对象		
var name = '全局'

function fun() {
console.log(this.name)

}

var obj = {
name:'tom',
sayName:fun
}
var obj2 = {
name:'lily',
sayName:fun
}


//根据函数的调用方式的不同,this会指向不同的对象
//1.以函数的形式调用时,this永远都是window
fun()//	全局;	

//2.以方法的形式调用时,this就是调用方法的那个对象
obj.sayName()//tom; 
obj2.sayName()//lily

如何确定this的值:

test(): window

p.test(): p

new test(): 新创建的对象

p.call(obj): obj

    //  call()和apply() 方法
  function fun() {
      alert(this.name)
    }
 

    var obj = {name:'obj',
    sayName:function () {
      alert(this.name)
    }}
    var obj2 = {name:'obj2'}
    // fun.call(obj) //obj
    // fun.apply(obj2.name)//obj2
    // fun()//window
    fun.call(obj,2,3)
    fun.apply(obj,[2,3])

    // obj.sayName.apply(obj2)

4 原型、原型链,继承

(1) 函数的prototype属性

原型就是一个对象,和其他对象没有任何区别,可以通过构造 函数来获取原型对象。 – 构造函数. prototype

截屏2021-04-13 12.49.52.png

// 原型prototype
console.log(Date.prototype, typeof Date.prototype)

function Fun() {
}

console.log(Fun.prototype)//原型对象,默认指向Object空对象
//给原型对象添加方法,给实例对象用
Fun.prototype.test = function () {
  console.log('test()')
}
var fun = new Fun()
fun.test()

console.log(Fun.prototype)//{ test: [Function (anonymous)] }
console.log(Date.prototype.constructor === Date)//true
console.log(Fun.prototype.constructor === Fun)//true
//显式原型(Funtion的prototype) 和隐式原型(实例对象fun的__proto__)
//1 定义构造函数
function Fn() {//内部语句:this.prototype={}
}
console.log(Fn.prototype)
// 2 创建实例对象
var fn = new Fn()//内部语句: this.__proto__ =Fn.prototype
console.log(fn.__proto__)
// 对应的构造函数的显式原型 的值 = 对象的隐式原型 的值
console.log(Fn.prototype === fn.__proto__)//true,由于他们都保存相同的地址值
// 3 给构造函数的显式原型添加方法
Fn.prototype.test = function () {
  console.log('test()')
}
// 4 通过实例调用原型的方法
fn.test()

(2) 原型链(隐式原型链)

  • 访问一个对象的属性时,
    • 先在自身属性中查找,找到返回

    • 如果没有, 再沿着__proto__这条链向上查找, 找到返回

    • 如果最终没找到, 返回undefined

         原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时,会现在自身中寻找,自身中如果有,则直接使用,如果没有则去原型对象中寻找,如果原型对象中有,则使用,如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined
      
  • 作用: 查找对象的属性(方法) 简单一点:就是利用原型让一个引用类型继承另一个引用类型的属性和方法;
function MyClass() {

}
MyClass.prototype.name = '我是原型中的名字'

var mc = new MyClass()
mc.age = 12
console.log(mc.name)//我是原型中的名字

console.log('name' in mc)//true

console.log(mc.hasOwnProperty('age'))//true
console.log(mc.hasOwnProperty('hasOwnProperty'))//false
console.log(mc.__proto__.hasOwnProperty('hasOwnProperty'))//false
console.log(mc.__proto__.__proto__.hasOwnProperty('hasOwnProperty'))//true

截屏2021-04-13 12.52.20.png

//原型链:本质上是隐式原型链
console.log(Object.prototype.__proto__)
function Fn() {
  this.test1 = function () {
    console.log('test1')
  }
}
Fn.prototype.test2 = function () {
  console.log('test2()')
}
var fn = new Fn()
fn.test1()
fn.test2()
console.log(fn.toString())
console.log(fn.test3)//undefined
//fn.test3()//TypeError: fn.test3 is not a function
//1 函数的显式原型默认指向空的Object的实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object)//true
console.log(Object.prototype instanceof Object)//false
console.log(Function.prototype instanceof Object)//true
//2 所有函数都是Function的实例(Function也是Function的实例)
console.log(Function.__proto__ === Function.prototype)
//3 Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__)//null
//练习1
function Fn(){
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a)//xxx

var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a,fn2.a)//xxx yyy
//练习1.5 注意:读取对象的属性值会查找原型链,但设置对象的属性值时,不会查找原型链
function Person(name,age){
  this.name = name
  this.age = age
}
Person.prototype.setName = function(name){
  this.name = name
}
var p1 = new Person('tom',12)
p1.setName('bob')
console.log(p1)//Person { name: 'bob', age: 12 }

var p2 = new Person('jack',12)
p2.setName('lily')
console.log(p2)//Person { name: 'lily', age: 12 }

console.log(p1.__proto__ === p2.__proto__)//true
//练习 2
Object.prototype.foo = 'Object'
Function.prototype.foo = 'Function'
function Animal() {
}
var cat = new Animal()
console.log(cat.foo)//Object
console.log(Animal.foo)//Function
//练习 3
var  b ={x:4}
function fn2(o) {
  this.x = o.x
}
fn2.prototype = {
  init:function () {
    return this.x
  }
}
var fn3 = new fn2({x:5})
var  c = fn3.init()
console.log(fn3.init.call(b))//4
 console.log(c)//5
// A(实例对象)instanceof B(构造函数)
function Foo() {}
var f1 = new Foo()
console.log(f1 instanceof Foo)//true
console.log(f1 instanceof Object)//true

console.log(Object instanceof Function)//true
console.log(Object instanceof Object)//true
console.log(Function instanceof Function)//True
console.log(Function instanceof Object)//true

function Foo() {}
console.log(Object instanceof Foo)//false

截屏2021-09-22 17.50.55.png

function P() {
  this.name = 'A'
}
P.prototype.friend = 'B'
function F() {
 this.from = 'C'
}
F.prototype = new  P()
const p1 = new P()
p1.friend = 'D'
const p2 = new F()
console.log(p1.friend)//D
console.log(p2.name)//A
/*测试题1 */
  var A = function() {
  }
  A.prototype.n = 1
  var b = new A()
//b 到此为止

  A.prototype = {
    n: 2,
    m: 3
  }
  var c = new A()
  console.log(b.n, b.m, c.n, c.m)
  //答案为1,undefined,2,3。
  /* 测试题2 */
  var F = function(){};
  Object.prototype.a = function(){
    console.log('a()')
  };
  Function.prototype.b = function(){
    console.log('b()')
  };
  var f = new F();
  f.a() //a()
  f.b()// 报错f.b is not a function
  F.a()//a()
  F.b()//b()

(3) 原型继承

//继承
class father {
  constructor() {
    this.name = 'jack'
    this.age = 30
  }
  sayName(){
    return this.name
  }
  static saySex(){
    return 'male'
  }
}
class  child  extends father{
  constructor() {
    super();
    this.name = 'tony'
    this.age =2
  }
  sayAge(){
    console.log(this.age)//undefined
  }
}
let cc = new child()
let ff = new father()
console.log(cc.sayName())//tony
console.log(cc.sayAge())//2
console.log(father.saySex()) //male
// console.log(cc.saySex())
// console.log(ff.saySex())

person类继承的时候,怎么实现原型链的

function Parent() {
  this.name = 'parent';
}
function Child() {
  Parent.call(this);
  this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

5 执行上下文

(1) 变量提升

var a = 3
function fn() {
  console.log(a)//undefined
  var a = 4
}
fn()

console.log(b)//undefined
fn2()//可以调用,函数提升
fn3()//不可调用,变量提升

var b =3
function fn2() {
  console.log('fn2()')//fn2()
}

var fn3 = function () {
  console.log('fn3()')
}

(2) 全局执行上下文

console.log(a1,window.a1)
window.a2()
console.log(this)

var a1 =2
function a2() {
  console.log('a2()')
}

(3) 函数执行上下文(N+1原则)

//函数执行上下文(N+1原则),是封闭的,虚拟的,存在与栈中,只有执行函数体,才有函数执行上下文,但提前准备好了;函数调用结束后就自动释放

function fn(a1) {
  console.log(a1)//2
  console.log(a2)//undefine
  a3()//a3()
  console.log(this)//window
  console.log(arguments)//伪数组(2,3)

  var a2 =3
  function a3() {
    console.log('a3()')
  }
}
fn(2,3)

执行顺序

// 1 进入window全局执行上下文
var a = 10
var bar = function (x) {
  var b = 5
  foo(x+b)//3.进入foo函数执行上下文
}
var foo = function (y) {
  var c = 5
  console.log(a+c+y)
}
bar(10)//2 进入bar函数执行上下文
console.log('global'+i)//undefined
var i = 1
foo(1)
function foo(i){
  if(i==4){
    return
  }
  console.log('foo() begin'+i)//1 2 3
  foo(i+1)//递归调用:在函数内部调用自己
  console.log('foo() end'+i)//3 2 1
}
console.log('global'+i) //1
//test1:先变量提升,再函数提升
function a() {}
var a
console.log(typeof a)//function

//test2
if(!(b in window)){
  var b = 1
}
console.log(b)//undefined

//test3
var c= 1
function c(c) {
  console.log(c)
}
c(2)// 报错 ,c不是函数,变量提升了

(4) 作用域(N+1 原则)与作用域链

首先来几题热热身,搞清楚全局作用域、函数作用域

var a = 123
function fun1() {
  alert(a)//123; 函数作用域没有a,到全局作用域找到a
}
fun1()
var b = 123
function fun2() {
  alert(b) // undefined; 所以函数作用域中 b 是undefined  (2)
  var b = 456 //首先,var b = 456 提前声明了 b   (1)
}
fun2()
alert(b) //123;这里是全局作用域中找b  (3)

var c = 123
function fun3() {
  alert(c) // 123; 首先,函数作用域里,目前没有c,到全局作用域找c (1)
  c = 456 // 这里c是全局的,因为没有 var 所以是 window.c ;也就是给全局的c重新赋值456  (2)
}
fun3()
alert(c)// 456; 由于 c 重新赋值了456 (3)
var d = 123
function fun4(d) {// 首先,这里有形参d,相对于函数作用域中 var d (1)
  alert(d) // undefined; 函数作用域里,目前d没有赋值  (2)
  d = 456 // 这里d 赋值为456 (3)
}
fun4()
alert(d)// 123; 这里是全局作用域中的d (4)
var e = 123
function fun5(e) {// 首先,这里有形参e,相对于函数作用域中 var e(1)
  alert(e) // 123; 函数作用域中 var e = 123(3)
  e = 456 // 这里e是函数的,给函数的e重新赋值456(4)

}
fun5(123)//然后,这里实参123,赋值 e = 123(2)
alert(e) //123;这里是全局作用域中找e(5)

作用域是静态的,(全局作用域、函数作用域、块作用域),用来隔离变量,不同作用域下同门变量不会有冲突

var a = 10,
  b = 20
function fn(x) {
  var a = 100
  c = 300
  console.log('fn()',a,b,c,x)//fn() 100 20 300 10
  function bar(x) {
    var a = 1000,
      d = 400
    console.log('bar()',a,b,c,d,x)//bar() 1000 20 300 400 100 //bar() 1000 20 300 400 200

  }
  bar(100)
  bar(200)
}
fn(10)

//作用域链

var a = 1
function fn1() {
  var b = 2
  function fn2() {
    var c = 3
    console.log(c)//3
    console.log(b)//2
    console.log(a)//1
    //console.log(d)// 保存,未定义
  }
  fn2()
}
fn1()
//test1
var x= 10
function fn() {
  console.log(x)
}
function show(f) {
  var x = 20
  f()
}
show(fn)

var fn =function () {
  console.log(fn)//[Function: fn]
}
fn()
//test2
var obj ={
  fn2:function () {
    console.log(fn2)//保存
    console.log(this.fn2)//[Function: fn2]
  }
}
obj.fn2()

6 闭包

闭包:函数嵌套,内部函数引用外部函数的数据,并执行外部函数

通俗的讲就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。

什么情况下会用到闭包吗? 最常见的是函数封装的时候,再就是在使用定时器的时候,会经常用到...

function fn1() {
  var a = 2
  var b = 3
  function fn2() {//执行函数定义,就会产生闭包(不用调用内部函数)
    console.log(a)
  }
}
fn1()//执行外部函数

(1) 闭包的应用

1将函数作为另一个函数的返回值

function fn1() {
  var a = 2//此时闭包就已经产生了,函数定义执行和函数执行是两回事(由于函数提升,内部函数对象已经创建了)
  function fn2() {//创建闭包函数
    a++
    console.log(a)

  }
  return fn2
}
var f = fn1()
f()//3 执行函数
f()//4
f = null //闭包死亡,(包含闭包的函数对象称为垃圾对象)

2将函数作为实参传递给另一个函数调用

function showDelay(meg,time) {
  setTimeout(function () {
    alert(msg)//闭包
  },time)
}
showDelay('july',2000)

3.定义JS模块,将所有的函数功能封装到一起

function myModule() {
  var msg ='July'
  //操作数据的行为
  function doSomething() {
    console.log('doSomething()'+msg.toUpperCase())

  }
  function doOtherthing() {
    console.log('doOtherthing()'+msg.toLowerCase())
  }
  //向外暴露对象
  return {
    doSomething:doSomething,
    doOtherthing:doOtherthing
  }
}

  //way2 更好,可以直接加载到window上。可以直接使用
  (function (window) {
    var msg = 'July'
  //操作数据的行为
    function doSomething() {
      console.log('doSomething()'+msg.toUpperCase())

    }
    function doOtherthing() {
      console.log('doOtherthing()'+msg.toLowerCase())
    }
    //   //向外暴露对象

    window.myModule2 =  {
      doSomething:doSomething,
      doOtherthing:doOtherthing
    }
  })(window)
  
//test1
var name = 'the window'
var object = {
  name:'my object',
  getNameFunc:function () {
    return function () {//无闭包
      return this.name
    };
  }
};
console.log(object.getNameFunc()())//the window
//test2
var name2 = 'the window'
var object2 = {
  name2:'my object',
  getNameFunc:function () {
    var that = this
    return function () {//有闭包
      return that.name2
    }
  }
};
console.log(object2.getNameFunc()())//my object
//test3
function fun(n,o) {
  console.log(o)
  return{
    fun:function (m) {
      return fun(m,n)//调用的是fun(n,o),有闭包,使用了n,传给o
    }
  };
}
var a = fun(0);a.fun(1);a.fun(2);a.fun(3);//undefined ,0,0,0
var b = fun(0).fun(1).fun(2).fun(3)//undefined ,0,1,2
var c = fun(0).fun(1);c.fun(2);c.fun(3)//undefined ,0,1,1

简单写一个闭包吧:

function a(){
     var i=0;
     function b(){
         i++;
         alert(i);
     }
     return b;
}

var c = a();
c();//?
c();//?
c();//?应聘者:应该是会依次弹出1,2,3。

原理: i是函数a中的一个变量,它的值在函数b中被改变,函数b每执行一次,i的值就在原来的基础上累加 1 。因此,函数a中的i变量会一直保存在内存中。当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。

闭包的用处: 它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。


//创建数组元素
var num = new Array();
for(var i=0; i<4; i++){
    //num[i] = 闭包;//闭包被调用了4次,就会生成4个独立的函数
    //每个函数内部有自己可以访问的个性化(差异)的信息
    num[i] = f1(i);
}
function f1(n){
     function f2(){
         alert(n);
     }
     return f2;
}
num[2]();  //2
num[1]();  //1
num[0]();  //0
num[3]();  //3

(2) 闭包的优缺点:

优点:

① 减少全局变量;
② 减少传递函数的参数量;
③ 封装;

缺点:

① 使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等
解决方法:
 简单的说就是把那些不需要的变量,但是垃圾回收又收不走的的那些赋值为null,然后让垃圾回收走;

7 事件循环机制

截屏2021-04-13 13.19.29.png

当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。

当我们调用一个方法的时候,js会生成一个与这个方法对应的执行环境(context),又叫执行上下文。 这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,变量以及这个作用域的this对象。

而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是一系列的方法被排队在执行栈中。

当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。

如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。

这个过程反复进行,直到执行栈中的代码全部执行完毕。

一个方法执行会向执行栈中加入这个方法的执行环境,在这个执行环境中还可以调用其他方法,甚至是自己,其结果不过是在执行栈中再添加一个执行环境。这个过程可以是无限进行下去的,除非发生了栈溢出。以上的过程说的都是同步代码的执行。

js引擎遇到一个异步事件后,js会将这个事件加入事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈执行完毕,它会立刻先处理微任务队列中的事件,然后再去宏任务队列中取出一个事件。

主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

2.macro task与micro task

不同的异步任务被分为两类:先: 微任务(micro task):new Promise() 和宏任务(macro task): setInterval() ,setTimeout()

以下事件属于微任务

new Promise()
 new MutaionObserver()
]


setTimeout(function () {
    console.log(1);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})
结果为:
2
3
1