1

1、ES5面向对象编程:

request.js: 一个文件一个模块

function Service() {
  this.request = function(opts) {
    this.opts = {...Service.DEFAULTS, ...opts}

    if (window.aladdin && window.aladdin.http && window.aladdin.http.request) {
      return this._aladdinRequest()
    } else if (window.$ && window.$.ajax) {
      return this._ajaxRequest()
    } else { // 原生请求
      return this._xhrRequest()
    }
  }

  this.get = function (opts) {
    return this.request((opts.method = "GET") && opts)
  }

  this.post = function (opts) {
    return this.request((opts.method = "POST") && opts)
  }
}


Service.DEFAULTS = {
  url: '',
  method: 'GET',
  headers: { // HTTP信息头对象
    contentType: 'application/x-www-form-urlencoded;charset=utf-8'
  },
  timeout: 15000, // 请求超时设置,毫秒
  data: {}
}

/**
 * aladdin发送请求
 */
Service.prototype._aladdinRequest = function () {
  const config = {
    url: this.opts.url,
    method: this.opts.method,
    headers: this.opts.headers,
    timeout: this.opts.timeout,
    xhrFields: {
      withCredentials: true
    }
  }
  if (config.method.toUpperCase() === 'POST') {
    config.body = this.opts.data
  } else {
    config.qs = this.opts.data
  }

  return new Promise(function(resolve, reject) {
    window.aladdin.http.request(config, function (error, res) {
      // 请求失败
      if (error) { 
        reject({
          status: error.code, //错误代码
          statusText: error.message //具体错误信息
        })
        return
      }
      // 请求成功
      if (res.status === 200) {
        isJsonStr(res.body) ? resolve(JSON.parse(res.body)) : resolve(res.body) //res.body 响应原始内容
      } else {
        reject({
          status: res.status, //响应状态码,如 401
          statusText: res.statusText //响应状态码对应的文本,如 'Unauthorized'
        })
      }
    })
  })
}

/**
 * ajax发送请求
 */
Service.prototype._ajaxRequest = function () {
  let config = {
    url: this.opts.url,
    type: this.opts.method,
    data: this.opts.data, // 如果是GET请求,它会自动被作为参数拼接到url上
    headers: this.opts.headers,
    // dataType: 'json', // 默认none。假若设置json,请求成功200但返回类型为text,会造成解析错误走error回调
    timeout: this.opts.timeout,
    xhrFields: {
      withCredentials: true
    }
  }

  return new Promise(function(resolve, reject) {
    // 请求成功
    config.success = function(data, status, xhr) {
      if (xhr.status === 200) {
        isJsonStr(data) ? resolve(JSON.parse(data)) : resolve(data) //data 响应原始内容
      } else {
        reject({
          status: xhr.status, // 响应状态码
          statusText: xhr.statusText // 状态码对应的文本
        })
      }
    }
    // 请求失败 (超时,解析错误,或者状态码不在HTTP 2xx)
    config.error = function(xhr) {
      reject({
        status: xhr.status, // 响应状态码
        statusText: xhr.statusText // 状态码对应的文本
      })
    }
    window.$.ajax(config)
  })
}

/**
 * XMLHttpRequest发送请求
 */
Service.prototype._xhrRequest = function () {
  let url = this.opts.url
  const method = this.opts.method.toUpperCase()
  const data = this.opts.data
  // 若是 GET 请求,需要处理 data 里的数据并且以一定的规则拼接到 url 后
  if (method === 'GET') {
    var formData = []
    for(let key in this.opts.data) {
      formData.push(''.concat(key, '=', this.opts.data[key]))
    }
    let params = formData.join('&')
    if (params.length > 0) {
      url += url.indexOf('?') >= 0 ? ''.concat('&', params) : ''.concat('?', params)
    }
  }

  let xhr = null
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest()
  } else if (window.ActiveXObject) {
    xhr=new window.ActiveXObject("Microsoft.XMLHTTP")
  }
  xhr.timeout = this.opts.timeout
  xhr.withCredentials = true
  xhr.open(method, url, true)
  for(let key in this.opts.headers) {
    xhr.setRequestHeader(key, this.opts.headers[key])
  }

  return new Promise(function(resolve, reject) {
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        // 请求成功
        if (xhr.status === 200) {
          isJsonStr(xhr.response) ? resolve(JSON.parse(xhr.response)) : resolve(xhr.response) // xhr.response,整个响应体
        } else {
          reject({
            status: xhr.status, // 响应状态码
            statusText: xhr.statusText // 状态码对应的文本
          })
        }
      }
    }
    xhr.onerror = xhr.ontimeout = function() {
      reject({
        status: xhr.status, // 响应状态码
        statusText: xhr.statusText // 状态码对应的文本
      })
    }
    
    xhr.send(method === 'POST' ? data : null)
  })
}


/**
 * 检查字符串是否JSON文本
 * @param {String} value 字符串
 * @return {Boolean} 是否JSON文本
 */
var isJsonStr = function isJsonStr(value) {
  try {
    eval("(" + value + ")");
    return true;
  } catch (er) {
    return false;
  }
}


var req = new Service()

// var p = req.request({
//   url: '/hget',
//   method: 'GET',
//   data: { name: 'zhangsan' }
// })

var p = req.request({
  url: '/hput',
  method: 'PUT',
  data: { name: 'zhangsan' }
})

// var p = req.request({
//   url: '/hpost',
//   method: 'POST',
//   data: { name: 'zhangsan' }
// })
p.then(res => console.log('resolved', res)).catch(err => console.log('rejected', err))
// module.exports = {
//  service: new Service()
// }

2、JavaScript的this指向
透彻认识function的this在不同调用环境下的指向

  • 事件调用环境: 谁触发事件,函数里面的this指向的就是谁。
var element = document.querySelector('.app')
element.onclick = function() { 
  this.style.left = '100px' // this指向element
}
例外:
事件加在标签上,<div onclick="fn"/>,则this指向全局对象。
  • 全局环境: window(浏览器环境) module.exports(node环境)
console.log(this === module.exports) // true
module.exports  = {
    num: 10,
    fn: function() {
        console.log(this.num) // 10
        console.log(this === module.exports) // true
    }
}
module.exports.fn()

在node中执行输出:
true
10
true
  • 函数内部环境: this最终指向的是调用它的对象。当函数被多层对象包含,如果函数被最外层对象调用,this指向的也只是它上一层的对象。
var obj = {
    'sub': {
         fn: function() {
            console.log(this)
         }
    }
}
obj.sub.fn() // this指向sub对象
var test = obj.sub.fn
test() // this.指向window对象
  • 构造函数: 如果构造函数中有return,如果return的值是对象,this指向返回的对象,如果不是对象,则this指向保持原来的规则。在这里,null比较特殊。
function fn() { 
  this.num = 10; 
  return { num: 20 }  // { num: 20 } 
  return [1,2,3]      // [1,2,3]
  return 0      // {num: 10}
  return 'abc'  // {num: 10}
  return true   // {num: 10}
  return null   // {num: 10}
}
console.log(new fn()) 
  • 箭头函数: 箭头函数本身是没有this和arguments的,在箭头函数中引用this实际上调用的是上一层作用于的this,且箭头函数的this指向在声明时就已经决定了。这里强调一下上一层作用域,因为对象是不能形成对立的作用域的。
var obj = {
  fn: () => {
    console.log(this)
  },
  fn1: function() {
    console.log(this)
  }
}
obj.fn() // this指向Window,因为对象不形成作用域
obj.fn1() // this指向obj对象

解决事件函数里面内层函数取不到触发事件对象办法:
function move() {
    setTimeout(function() { 
      console.log(this) // this指向Window,setTimeout方法省略了Window.setTimeout  
    }, 1000)
    setTimeout(() => { 
      console.log(this) // this指向触发事件对象element
    }, 1000)
}
element.onclick = move 
  • 修改this指向

* 无法修改箭头函数this指向

var box = document.querySelector('.box')
var obj = {
    fn: () => {
      console.log(this)
    }
}
obj.fn.call(box) // Window,证明箭头函数的this指向不可修改,在定义时已经确定。

* 修改普通函数this指向:call apply bind 用法大同小异
var obj = {
  name: '小王',
  age: 20,
  fn: function(fm, t) {
    console.log(this.name + '年龄 ' + this.age, '来自' + fm + '去往' + t)
  }
}
var db = {
  name: '德玛',
  age: 99
}

obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);      // 德玛 年龄 99  来自 成都去往上海  
obj.myFun.bind(db,'成都','上海')();       // 德玛 年龄 99  来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])();   // 德玛 年龄 99  来自 成都, 上海去往 undefined

从上面四个结果不难看出:
call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:
call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔。
apply 的所有参数都必须放在一个数组里面传进去。
bind 除了返回是函数以外,它 的参数和 call 一样。

JohnsonGH
32 声望1 粉丝