前端之旅——JavaScript篇

 上一篇对ES6做了一个笔录,相对新特性,JS目前主要遵循的ES5不算复杂,也就没必要做那么详细的笔录。有必要可以详细阅读阮一峰老师的《JavaScript 教程》,再次感谢阮老师的付出。

JavaScript的核心是面向对象的,它提供了强大的OOP语言能力。ES标准库里面提供了Object、Array、Boolean、Number、String、Date、RegExp、JSON、Error等本地对象以及Math、Global等内置对象。在阮老师的教程中将Object的一些属性和方法归在了属性描述对象,将Number、String、Boolean归为包装对象。再加上W3C标准的DOM和不标准的BOM的宿主对象,构成了JavaScript。

闭包

闭包是JavaScript中最抽象的概念之一,也是最容易造成困惑的。闭包就是一个函数与其被创建时所带有的作用域对象的组合,简单地理解就是定义在一个函数内部的函数。

function F() {
  var name = 'OSCHINA';
  function displayName() {
    console.log(name);
  }
  return displayName;
}
var f = F();
f();

闭包比较难理解的是它的执行上下文,最终指向谁,须根据当时的执行环境决定。

let name = 'Window';
let object = {
  name: 'Object',
  getNameByES5() {
    return function() {
      return this.name;
    };
  },
  getNameByES6() {
    return () => {
      return this.name;
    };
  }
};

object.getNameByES5()()  // Window
object.getNameByES6()()  // Object

闭包常用来模拟私有方法,但注意闭包会在函数外部改变函数内部的值。

let Counter = (function() {
  let co = 0;

  function change(v) {
    co += v;
  }

  return {
    increment() {
      change(1);
    },
    decrement() {
      change(-1);
    },
    value() {
      return co;
    }
  }
})();

闭包常见问题

在let之前,在循环中创建闭包会出现一个作用域的问题,如下:

function help() {
  var arr = []
  for(var i = 0; i < 10; i++) {
    arr[i] = function() {
      console.log(i)
    }
  }
  return arr
}
var h = help()
h[0]()  // 10
h[1]()  // 10

出现上面结果的原因:数组中的闭包是由他们的函数定义和函数help作用域中捕获的环境所组成。三个闭包共享了同一个词法作用域,i的值是被共享的,所以最终都打印10。解决办法就是使用一个工厂函数或者IIFE函数,创造一个新的作用域,或者使用ES6的let关键字。

另外一问题就是闭包的内存消耗。如闭包第一个例子所见,外部函数F被调用赋值给f保持在内存,f依赖与F,于是F不会被清除掉。而且在IE中还可能导致内存泄漏。所以最好是退出函数之前,将不用的局部变量全部删除。

另外在创建类或新对象时,方法关联到原型,而不是构造器中。在构造器中每次调用构造函数,方法都会被重新赋值。

function F(name) {
  this.name = name;
  this.getName = function() {
    return this.name;
  }
}

// 没必要用到闭包
function F(name) {
  this.name = name;
}
F.prototype = {
  getName() {
    return this.name;
  }
};

// prototype不建议重新定义,这会改变原有的某些定义
function F(name) {
 this.name = name;
}
F.prototype.getName = function() {
 return this.name;
}

原型和原型链

JS中万物都是对象,有人认为它的根源就是Object,真的是这样吗?先来看一段代码。

function f1() {}
var f2 = function() {}
var f3 = new Function('str')

var o1 = {}
var o2 = new Object()
var o3 = new f1()

typeof Object  // function
typeof Function  // function

typeof f1  // function
typeof f2  // function
typeof f3  // function

typeof o1  // object
typeof o2  // object
typeof o3  // object

这里有个很有意思的东西,Object和Function都是构造函数,也就是Function的实例。那么JS万物皆对象又怎么讲?

// 构造函数都是Function的实例对象
Object.__proto__ === Function.prototype
Function.__proto__ === Function.prototype

// Function.prototype是Object的实例对象
Function.prototype.__proto__ === Object.prototype

// 将Function.prototype等式替换
Object.__proto__.__proto__ === Object.prototype
Function.__proto__.__proto__ === Object.prototype

// Object.prototype再往上找
Object.prototype.__proto__ === null

由上面可以得出,Object和Fucntion就是由Function创建的对象。JS设计将继承发生在原型和对象上,明面上和Function没有关系。Function对象相当于加工厂,constructor最终指向它,function负责代言,Object负责发言。

// instanceof 用于测试构造函数prototype属性是否出现在对象的原型链上
Function instanceof Object  // true
Object instanceof Function  // true

那么,__proto__和prototype又是什么呢?__proto__并非ES标准属性,但是被大多现代浏览器实现,用来指向原型对象(prototype)。每个对象实例都有一个私有属性__proto__,包括原型对象,层层向上直到null(原型链顶端)。只有函数才有原型对象,原型对象指向原型的属性和方法,函数创建的时候默认或创建一个原型对象。

function F() {}
var f = new F()
var o = new Object()

// Function
Function  // function Function() { [navtive code] }
Function.constructor  // function Function() { [navtive code] }
Function.__proto__  // function() { [navtive code] }
Function.prototype  // function() { [navtive code] }

Function.constructor === Function  // true
Function.prototype === Function.__proto__  // true

// function Function.prototype
Function.prototype.constructor  // function Function() { [navtive code] }
Function.prototype.__proto__  // { constructor: function Object() {...}, ... }
Function.prototype.prototype  // undefined

Function.prototype.constructor === Function  // true
Function.prototype.__proto__ === Object.prototype  // true

// function F
F  // function F() {}
F.constructor  // function Function() { [navtive code] }
F.__proto__  // function() { [navtive code] }
F.prototype  // { constructor: function F() {...}, __proto__: {...} }

F.constructor === Function  // true
F.prototype.constructor === F  // true
F.__proto__ === Function.prototype  // true
F.__proto__ === Function.__proto__  // true

// Object f
f  // {}
f.constructor  // function F() {}
f.prototype  // undefined
f.__proto__  // { constructor: function F() {...}, __proto__: {...} }

f.constructor === F  // true
f.__proto__ === F.prototype  // true

// function Object
Object  // function Object() { [navtive code] }
Object.constructor  // function Function() { [navtive code] }
Object.prototype  // { constructor: function Object() {...}, ... }
Object.__proto__  // function() { [navtive code] }

Object.constructor === Function  // true
Object.prototype.constructor === Object  // true
Object.__proto__ === Function.prototype  // true
Object.__proto__ === Function.__proto__  // true

// object o
o  // {}
o.constructor  // function Object() { [navtive code] }
o.prototype  // undefined
o.__proto__  // { constructor: function Object() {...}, ... }

o.constructor === Object  // true
o.__proto__ === Object.prototype  // true

通过上面代码可以知道,function和Object通过new创建的对象的__proto__都指向Function.prototype,Function.prototype的__proto__指向Object.prototype。Function.prototype的构造函数指向的是function和Object自身,那么new出来的对象__proto__则指向它们的原型对象,于是形成了原型链。

f.__proto__ -> F.prototype.__proto__ -> Object.prototype.__proto__ -> null

最让人误解的是Function的构造函数是自己,让人以为Function自己创建了一个自己。因为没有类的概念,所以Function自己都很难撇清楚。

Object.prototype.a = function() {}
Function.prototype.b = function() {}
let F = new Function()
let f = new F()
f.a  // function () {}
f.b  // undefined

封装和继承

JavaScript的封装很简单,但是封装出好的库还是不容易。

// 生成实例对象的原始模式
let Persion = {
  name: '',
  greeting(str) {
    alert(str)
  }
}

// 原始模式的改进-工厂模式
function Persion(name) {
  return {
    name,
    greeting(str) {
      alert(str)
    }
  }
}

// 构造函数模式
function Person(name) {
  this.name = name
  this.greeting = function(str) {
    alert(str)
  }
}

// Prototype模式
function Person(name) {
  this.name = name
}
Person.prototype = {
  constructor: Person,
  greeting(str) {
    alert(str)
  }
}

// 类jQuery封装
let Person = (function(w) {
  let Person = function(name) {
    return new Person.fn.init(name)
  }
  Person.fn = Person.protype = {
    constructor: Person,
    init(name) {
      this.name = name
      this.greeting = function(str) {
        alert(str)
      }
    }
  }

  Person.fn.init.prototype = Person.fn

  return Person
})()

封装好了,如何进行继承扩展呢?类似jQuery的扩展是通过内置extend()函数来进行的,它事实上就是在合并,最简单的方式就是直接挂在jQuery对象上,有兴趣的可以去看下jQuery源码。

// 构造函数绑定
function Male(name, lan) {
  Person.apply(this, arguments)
  this.name = name
  this.lan = lan
}

// prototype模式
Male.prototype = new Person()
Male.prototype.constructor = Male

// 直接继承prototype
Male.prototype = Person.prototype
Male.prototype.constructor = Person

// 使用空对象作为中介
let F = function() {}
F.prototype = Person.prototype
Male.prototype = new F()
Male.prototype.constructor = Male

// 拷贝继承
function extend(child, Parent) {
  let p = Parent.prototype
  let c = Child.prototype

  for(let i in p) {
    c[i] = p[i]
  }
  c.uper = p
}
extend(Male, Person)

上面都是基于构造函数,如果是对象怎么实现继承呢?

// object()方法
function object(o) {
  function F() {}
  F.prototype = o
  return new F()
}

// 浅拷贝
function extend(p) {
  let c = {}

  for(let i in p) {
    c[i] = p[i]
  }
  c.uper = p
  return c
}

// 深拷贝
function deep(p, c) {
  let c = c || {}

  for(let i in p) {
    if(typeof p[i] === 'object') {
      c[i] = (p[i].constructor === Array) ? [] : {}
      deep(p[i], c[i])
    } else {
      c[i] = p[i]
    }
  }

  return c
}

在ES6标准中已经提出了新的解决方案class和extend来进行封装继承,所涉及的相关内容可参阅ES6。

需要提一下new关键字的原理:新建一个空对象,将构造函数的作用域赋给新对象,this指向新对象,执行构造函数中的代码,返回新对象。

并发模型和事件循环

在事件循环期间,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个消息会被移除队列并作为输出参数调用与之关联的函数。调用一个函数总是会为其创造新的栈帧,函数的处理会一直进行到执行栈再次为空为止,然后事件循环将会处理队列中的下一个消息。

JS的并发模型基于事件循环,之所以称之为事件循环,是因为它经常按照如下方式来被实现。

while(queue.waitForMessage) {
  queue.processNextMessage()
}

如果当前没有任何消息,queue.waitFroMessage会同步地等待消息到达。当一个事件发生且绑定监听器,消息会被加入队列。如果没有事件监听器,事件会丢失。

setTimeout的延迟时间代表消息被加入队列的最小延迟时间,如果队列中没有其他消息,延迟时间过去消息会被马上处理。但是如果有其他消息,须等待其他消息处理完。所以,setTimeout的延迟时间并非确切的等待时间,包括值设为0的时候。

一个web work或者一个跨域的iframe都有自己的栈、堆和消息队列,两个不同的运行时只能通过postMessage方法进行通信。如果另一个运行时侦听message事件,则此方法会向该运行时添加消息。

最后

JS三个部分API众多,不大可能也没必要全部都记住,知道有这个东西就好,在实战中用多了就熟了。加油,咬着苹果的狗!

转载于:https://my.oschina.net/u/3830333/blog/3095785

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值