默认分类
只是一个默认分类

装饰器模式与ES7中的装饰器

装饰器能动态地给一个对象添加一些额外的职责,换句话说就像Chrome的插件,能够扩展浏览器的功能,想加什么功能下载一个插件来“装饰”浏览器就可以了。
所以装饰器的目的就是功能的复用,因为在传统的面向对象语言中,为对象添加功能通常会用继承的方式,但继承真的就那么完美吗,其实不然。当超类改变,子类也随之改变,耦合了;超类内部细节对子类可见,破坏封装了;功能复用,子类=对象*功能.....

使用AOP装饰函数

//前置装饰
Function.prototype.before = function (beforefn) {
    var __self = this; // 保存原函数的引用
    // 返回包含了原函数和新函数的"代理"函数
    return function () {
        // 执行新函数,且保证this 不被劫持,新函数接受的参数
        // 也会被原封不动地传入原函数,新函数在原函数之前执行
        beforefn.apply(this, arguments);
      
        // 执行原函数并返回原函数的执行结果,并且保证this 不被劫持
        return __self.apply(this, arguments);
    }
};

//A = A.before( B );

当然装饰器也不是完美的,多层装饰比较复杂,也可能会导致性能问题。

可以这么理解:继承是自上而下,装饰器是由内而外包裹的关系。

ES7中的装饰器

装饰器由一个@紧接一个函数名称,ES7 中的 decorator 其实就是一种语法糖,它依赖于 ES5 的 Object.defineProperty 方法 。

Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

Object.defineProperty(obj, "key", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "static"    //属性对应的值
});
\\Object.defineProperty(obj, prop, descriptor)
\\obj: 目标对象
\\prop: 属性名
\\descriptor: 针对该属性的描述符

实现一个 decorator

function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor
}

class Dog {
  @readonly
  bark () {
    return 'wang!wang!'
  }
}

let dog = new Dog()
dog.bark = 'bark!bark!'
// Cannot assign to read only property 'bark' of [object Object]

编译之后的代码:

function Dog () {}

Object.defineProperty(Dog.prototype, 'bark', {
  value: function () { return 'wang!wang!' },
  enumerable: false,
  configurable: true,
  writable: true
})

ES7 的 decorator,作用就是返回一个新的 descriptor,并把这个新返回的 descriptor 应用到目标方法上。

更多的装饰器,可以参考core-decorator,它提供了一些常用的 decorator。

参考阅读:
1.Object.defineProperty()
2.Decorators in ES7

throttle 和 debounce 函数

一、throttle 函数节流

节流就是控制函数执行的频率,比如mousemove、window对象的resize和scroll 事件等,这些事件出发频率都很高。所以我们会用节流函数进行封装,以提高性能。
function throttle(func, wait, options) {

  var leading = true,
      trailing = true;

  if (typeof func != 'function') {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  if (isObject(options)) {
    leading = 'leading' in options ? !!options.leading : leading;
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }
  return debounce(func, wait, {
    'leading': leading,
    'maxWait': wait,
    'trailing': trailing
  });
}

二、debounce 函数去抖

debounce和throttle很像,debounce是空闲时间必须大于或等于一定值的时候,才会执行调用方法。

 function debounce(func, wait, options) {
      var lastArgs,
          lastThis,
          maxWait,
          result,
          timerId,
          lastCallTime,
          lastInvokeTime = 0,
          leading = false,
          maxing = false,
          trailing = true;
  if (typeof func != 'function') {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  wait = toNumber(wait) || 0;
  if (isObject(options)) {
    leading = !!options.leading;
    maxing = 'maxWait' in options;
    maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }

  function invokeFunc(time) {
    var args = lastArgs,
        thisArg = lastThis;

    lastArgs = lastThis = undefined;
    lastInvokeTime = time;
    result = func.apply(thisArg, args);
    return result;
  }

  function leadingEdge(time) {
    // Reset any `maxWait` timer.
    lastInvokeTime = time;
    // Start the timer for the trailing edge.
    timerId = setTimeout(timerExpired, wait);
    // Invoke the leading edge.
    return leading ? invokeFunc(time) : result;
  }

  function remainingWait(time) {
    var timeSinceLastCall = time - lastCallTime,
        timeSinceLastInvoke = time - lastInvokeTime,
        result = wait - timeSinceLastCall;

    return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
  }

  function shouldInvoke(time) {
    var timeSinceLastCall = time - lastCallTime,
        timeSinceLastInvoke = time - lastInvokeTime;

    // Either this is the first call, activity has stopped and we're at the
    // trailing edge, the system time has gone backwards and we're treating
    // it as the trailing edge, or we've hit the `maxWait` limit.
    return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
      (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
  }

  function timerExpired() {
    var time = now();
    if (shouldInvoke(time)) {
      return trailingEdge(time);
    }
    // Restart the timer.
    timerId = setTimeout(timerExpired, remainingWait(time));
  }

  function trailingEdge(time) {
    timerId = undefined;

    // Only invoke if we have `lastArgs` which means `func` has been
    // debounced at least once.
    if (trailing && lastArgs) {
      return invokeFunc(time);
    }
    lastArgs = lastThis = undefined;
    return result;
  }

  function cancel() {
    if (timerId !== undefined) {
      clearTimeout(timerId);
    }
    lastInvokeTime = 0;
    lastArgs = lastCallTime = lastThis = timerId = undefined;
  }

  function flush() {
    return timerId === undefined ? result : trailingEdge(now());
  }

  function debounced() {
    var time = now(),
        isInvoking = shouldInvoke(time);

    lastArgs = arguments;
    lastThis = this;
    lastCallTime = time;

    if (isInvoking) {
      if (timerId === undefined) {
        return leadingEdge(lastCallTime);
      }
      if (maxing) {
        // Handle invocations in a tight loop.
        timerId = setTimeout(timerExpired, wait);
        return invokeFunc(lastCallTime);
      }
    }
    if (timerId === undefined) {
      timerId = setTimeout(timerExpired, wait);
    }
    return result;
  }
  debounced.cancel = cancel;
  debounced.flush = flush;
  return debounced;
}

三、区别

我们用一个输入框的例子来解释一下这两个函数的区别,节流函数可以运用在保存按钮上,如果你连续点击保存按钮时,可以控制最短保存间隔为一秒,这样就能去掉重复请求;而去抖函数可以用在自动保存上,只有停止编辑一秒之后才会触发保存功能,如果这一秒内又重新开始编辑,就会取消计时器。所以再涉及重复调用的问题上都可用这两个函数进行优化。

JS中的一些类型判断

一、判断Array

一般来说我们判断类型会使用 typeof 操作符来判断就足够了,但是如果你想判断用typeof判断数组,他只会返回一个'object '。你要是问问什么,那就要好好温习一下基础知识了,最新的JS定义了七种数据类型:

六种 原型 数据类型:

  • Boolean. 布尔值,true 和 false.
    1. 一个表明 null 值的特殊关键字。 JavaScript 是大小写敏感的,因此 null 与 Null、NULL或其他变量完全不同。
    1. 变量未定义时的属性。
  • Number. 表示数字,例如: 42 或者 3.14159。
  • String. 表示字符串,例如:"Howdy"
  • Symbol ( 在 ECMAScript 6 中新添加的类型).。一种数据类型,它的实例是唯一且不可改变的。
  • Object

Array并不属于JS基本类型,所以对数组的判断ES5给出了一个单独的isArray()方法。
另外还可以使用Object.prototype.toString()来判断,下面给出了对应表格。

Value               toString   typeof
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object
new RegExp("meow")  RegExp     object
{}                  Object     object
new Object()        Object     object 

关于这种方法的原理不再过多叙述,可以参考这篇文章JavaScript:Object.prototype.toString方法的原理

二、判断空对象

function isEmptyObject(e) {  
    var t;  
    for (t in e)  
        return !1;  
    return !0  
}  

这是jQuery的源码,原理就是使用 for in 来遍历对象中的属性名.如果一个对象有属性名则return false,否则return true;

三、判断 Null

你也许会想到使用 typeof ,但是他只会返回一个 object

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是0。由于
null 代表的是空指针(大多数平台下值为0x00),因此,null的类型标签也成为了0,typeof
null就错误的返回了"object"

其实判断Null是最简单的,只需要用===即可

参考文章
1.typeof

Socket、SOCKS和WebSocket

一、socket

socket是一个网络概念,它起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作,而socket就是这个模式在网络编程中的实现,它通常称为套接字插口
当应用进程需要进行网络通信时,必须首先发出socket系统调用,然后操作系统会把进行网络通信的系统资源如:CPU时间、带宽等分配给这个应用进程;通信结束时,应用进程再掉用关闭socket方法,通知操作系统回收之前分配的资源。

二、sockes5

sockes5是一种代理上网的加密协议,注意不要与socket搞混了哦。现在最好用的翻墙工具ShadowSockes其实就是基于sockes5协议实现的。

这里简单介绍一下建立代理链接的流程:
1.验证:
客户端告诉代理服务器自已支持的验证方式
代理服务器收到上面的报文,选择自已所能支持的验证方式,然后客户端发送相应验证(如用户名密码),最后服务端返回验证状态。
2.建立代理连接:
客户端发送要建立的的代理连接的地址及端口
代理服务器收到客户端的请求后创建到目标机器的连接或创建监听目标机器的socket
3.数据包转发
TCP直接转发,UDP需要客户端先发起UDP ASSOCIATE请求,得到UDP中继地址和端口后,再在报文上加上请求头才能发送。

三、WebSocket

WebSocket一种在单个 TCP 连接上进行全双工通讯的协议。
意思就是浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并允许数据进行双向传送。
在之前要实现这种效果需要AJAX轮询或者HTTP持久连接(长链接),但是这种方法都会消耗很多服务器资源和带宽,所以HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。

最常见的例子,就是用WebSocket实现在线聊天和视频直播中的弹幕。我们可以使用Socket.IO框架,他会自动选择合适双向通信协议,对于不支持WebSocket,会回退到其他方法。

参考资料:
1.WebSocket