JavaScript 内存泄漏教程

转自作者: 阮一峰    

原文地址:http://www.ruanyifeng.com/blog/2017/04/memory-leak.html

一、什么是内存泄漏?

程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。

对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

有些语言(比如 C 语言)必须手动释放内存,程序员负责内存管理。

char * buffer;
buffer = (char*) malloc(42);
// Do something with buffer
free(buffer);

上面是 C 语言代码,malloc方法用来申请内存,使用完毕之后,必须自己用free方法释放内存。

这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为”垃圾回收机制”(garbage collector)。

二、垃圾回收机制

垃圾回收机制怎么知道,哪些内存不再需要呢?

最常使用的方法叫做“引用计数”(reference counting):语言引擎有一张”引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。

上图中,左下角的两个值,没有任何引用,所以可以释放。

如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。

const arr = [1, 2, 3, 4];
console.log('hello world');

上面代码中,数组[1, 2, 3, 4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它还是会持续占用内存。

如果增加一行代码,解除arr对[1, 2, 3, 4]引用,这块内存就可以被垃圾回收机制释放了。

let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;

上面代码中,arr重置为null,就解除了对[1, 2, 3, 4]的引用,引用次数变成了0,内存就可以释放出来了。

因此,并不是说有了垃圾回收机制,程序员就轻松了。你还是需要关注内存占用:那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用。如果是的话,就必须手动解除引用。

三、内存泄漏的识别方法

怎样可以观察到内存泄漏呢?

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。这就要求实时查看内存占用。

3.1 浏览器

Chrome 浏览器查看内存占用,按照以下步骤操作。

  1. 打开开发者工具,选择 Timeline 面板
  2. 在顶部的Capture字段里面勾选 Memory
  3. 点击左上角的录制按钮。
  4. 在页面上进行各种操作,模拟用户的使用情况。
  5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。

如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。

反之,就是内存泄漏了。

3.2 命令行

命令行可以使用 Node 提供的process.memoryUsage方法。

console.log(process.memoryUsage());
// { rss: 27709440,
//  heapTotal: 5685248,
//  heapUsed: 3449392,
//  external: 8772 }

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。

  • rss(resident set size):所有内存占用,包括指令区和堆栈。
  • heapTotal:”堆”占用的内存,包括用到的和没用到的。
  • heapUsed:用到的堆的部分。
  • external: V8 引擎内部的 C++ 对象占用的内存。

判断内存泄漏,以heapUsed字段为准。

四、WeakMap

前面说过,及时清除引用非常重要。但是,你不可能记得那么多,有时候一疏忽就忘了,所以才有那么多内存泄漏。

最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻程序员的负担,你只要清除主要引用就可以了。

ES6 考虑到了这一点,推出了两种新的数据结构:WeakSetWeakMap。它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个”Weak”,表示这是弱引用。

下面以 WeakMap 为例,看看它是怎么解决内存泄漏的。

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

也就是说,DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

五、WeakMap 示例

WeakMap 的例子很难演示,因为无法观察它里面的引用会自动消失。此时,其他引用都解除了,已经没有引用指向 WeakMap 的键名了,导致无法证实那个键名是不是存在。

我一直想不出办法,直到有一天贺师俊老师提示,如果引用所指向的值占用特别多的内存,就可以通过process.memoryUsage方法看出来。

根据这个思路,网友 vtxf 补充了下面的例子

首先,打开 Node 命令行。

$ node --expose-gc

上面代码中,–expose-gc参数表示允许手动执行垃圾回收机制。

然后,执行下面的代码。


// 手动执行一次垃圾回收,保证获取的内存使用状态准确
> global.gc(); 
undefined

// 查看内存占用的初始状态,heapUsed 为 4M 左右
> process.memoryUsage(); 
{ rss: 21106688,
  heapTotal: 7376896,
  heapUsed: 4153936,
  external: 9059 }

> let wm = new WeakMap();
undefined

> let b = new Object();
undefined

> global.gc();
undefined

// 此时,heapUsed 仍然为 4M 左右
> process.memoryUsage(); 
{ rss: 20537344,
  heapTotal: 9474048,
  heapUsed: 3967272,
  external: 8993 }

// 在 WeakMap 中添加一个键值对,
// 键名为对象 b,键值为一个 5*1024*1024 的数组  
> wm.set(b, new Array(5*1024*1024));
WeakMap {}

// 手动执行一次垃圾回收
> global.gc();
undefined

// 此时,heapUsed 为 45M 左右
> process.memoryUsage(); 
{ rss: 62652416,
  heapTotal: 51437568,
  heapUsed: 45911664,
  external: 8951 }

// 解除对象 b 的引用  
> b = null;
null

// 再次执行垃圾回收
> global.gc();
undefined

// 解除 b 的引用以后,heapUsed 变回 4M 左右
// 说明 WeakMap 中的那个长度为 5*1024*1024 的数组被销毁了
> process.memoryUsage(); 
{ rss: 20639744,
  heapTotal: 8425472,
  heapUsed: 3979792,
  external: 8956 }

上面代码中,只要外部的引用消失,WeakMap 内部的引用,就会自动被垃圾回收清除。由此可见,有了它的帮助,解决内存泄漏就会简单很多。

六、参考链接

(完)

 

JavaScript数据结构和算法

转自:https://github.com/zoro-web/blog

前言

在过去的几年中,得益于Node.js的兴起,JavaScript越来越广泛地用于服务器端编程。鉴于JavaScript语言已经走出了浏览器,程序员发现他们需要更多传统语言(比如C++和Java)提供的工具。这些工具包括传统的数据结构(如链表,栈,队列,图等),也包括传统的排序和查找算法。本文主要是总结什么情况下使用何种数据结构较好,并没有细讲里面的原理和实现方式,仅仅提供给阅读过《数据结构和算法》的同学作为总结和参考笔记,如果未细究过数据结构和算法的同学,本文也可以作为一个方向,希望能引导你去深究数据结构和算法。

为什么要学习数据结构和算法

数据结构和算法对于很多前端工程师来说,一直觉得是可有可无的,但其实不然,个人觉得,前端工程师其实是最需要重视数据结构和算法的人,因为前端所做的东西是用户访问网站第一眼看到的东西,特别在移动浪潮到来之后,对用户体验越来越高,对前端提出了更高的要求,面对越来越复杂的产品,需要坚实的数据结构和算法基础才能驾驭。
如果没有学习过计算机科学的程序员,当我们在处理一些问题时,比较熟悉的数据结构就是数组,数组无疑是一个很好的选择。但很多时候,对于很多复杂的问题,数组就显得太过简陋了,当学习了数据结构和算法之后,对于很多编程问题,当想到一个合适的数据结构后,设计和实现解决这些问题的算法就手到擒来。

相关知识点——数据结构、排序算法和查找算法

相关讲解细分:
数据结构:列表、栈、队列、链表、字典、散列、图和二叉查找树
排序算法:冒牌排序、选择排序、插入排序、希尔排序、归并排序和快速排序
查找算法:顺序查找和二分查找

列表

在日常生活中,人们经常使用列表:待办事项列表、购物清单、最佳十名榜单等等。而计算机程序也在使用列表,在下面的条件下,选择列表作为数据结构就显得尤为有用:

  • 数据结构较为简单
  • 不需要在一个长序列中查找元素,或者对其进行排序

反之,如果数据结构非常复杂,列表的作用就没有那么大了。

栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。想象一下,我们平常在饭馆见到的一摞盘子就是现实世界常见的栈的例子,只能从最上面取盘子,盘子洗干净后,也只能放在最上面。栈被称为一种后入先出的数据结构。是一种高效的数据结构,因为数据只能在栈顶添加或删除,所以这样的操作很快。
使用条件:

  • 只要数据的保存满足后入先出或先进后出的原理,都优先考虑使用栈

images

队列

队列也是一种列表,不同的是队列只能在队尾插入元素,在队首删除元素。想象一下,我们在银行排队,排在最前面的人第一个办理业务,而后面来的人只能排在队伍的后面,直到轮到他们为止。
使用条件:

  • 只要数据的保存满足先进先出、后入后出的原理,都优先考虑使用队列

常见应用场景:

  • 队列主要用在和时间有关的地方,特别是操作系统中,队列是实现多任务的重要机制
  • 消息机制可以通过队列来实现,进程调度也是使用队列来实现

images

链表

链表也是一种列表,为什么需要出现链表,JavaScript中数组的主要问题时,它们被实现成了对象,与其他语言(比如C++和Java)的数组相对,效率很低。如果你发现数组在实际使用时很慢,就可以考虑使用链表来代替它。
使用条件:

  • 链表几乎可以用在任何可以使用一维数组的情况中。如果需要随机访问,数组仍然是更好的选择。

images

字典

字典是一种以键-值对行驶存储数据的数据结构,JavaScript中的Object类就是以字典的形式设计的。JavaScript可以通过实现字典类,让这种字典类型的对象使用起来更加简单,字典可以实现对象拥有的常见功能,并相应拓展自己想要的功能,而对象在JavaScript编写中随处可见,所以字典的作用也异常明显了。

散列

散列(也称为哈希表)是一种的常用的数组存储技术,散列后的数组可以快速地插入或取用。散列使用的数据结构叫做散列表。在散列表上插入、删除和取用数据都非常快,但对于查找操作来说却效率低下,比如查找一组数组中的最大值和最小值。这些操作需要求助于其他数据结构,比如下面介绍的二叉查找树。

散列表在JavaScript中可以基础数组去进行设计。数组的长度是预先设定的,所有元素根据和该元素对应的键,保存在数组的特定位置,这里的键和对象的键是类型的概念。使用散列表存储数组时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。

即使使用一个高效的散列函数,依然存在将两个键映射为同一个值得可能,这种现象叫做碰撞。常见碰撞的处理方法有:开链法和线性探测法(具体概念有兴趣的可以网上自信了解)

使用条件:

  • 可以用于数据的插入、删除和取用,不适用于查找数据

images

图由边的集合及顶点的集合组成。地图是我们身边很常见的现实场景,比如每两个城镇都由某种道路相连。上面的每个城镇可以看作一个顶点,连接城镇的道路便是边。边由顶点对(v1, v2)定义,v1和v2分别是图中的两个顶点。顶点也有权重,也成为成本。如果一个图的顶点对是有序的,则称之为有向图(例如常见的流程图),反之,称之为无序图。
使用场景(用图对现实中的系统建模):

  • 交通系统,可以用顶点表示街道的十字路口,边可以表示街道。加权的边可以表示限速或者车道的数量。可以用该系统判断最佳路线及最有可能堵车的街道。
  • 任何运输系统都可以用图来建模。比如,航空公司可以用图来为其飞行系统建模。将每个机场看成顶点,将经过两个顶点的每条航线看作一条边。加权的边可以表示从一个机场到另一个机场的航班成本,或两个机场间的距离,这取决于建模的对象是什么。

搜索图的算法主要有两种: 深度优先搜索和广度优先搜索。

二叉树和二叉查找树

树是计算机科学中经常用到的一种数据结构。树是一种非线性的数据结构,以分层的方式存储数据。
二叉树每个节点的子节点不允许超过两个。一个父节点的两个子节点分别称为左节点和右节点,通过将子节点的个数限定为2,可以写出高效的程序在树中插入、查找和删除数据。
二叉查找树(BST)是一种特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。这一特性使得查找的效率很高,对于数值型和非数值型的数据,比如单词和字符串,都是如此。
二叉查找树实现方法

function Node(data, left, right) { // 创建节点
  this.data = data;
  this.left = left;
  this.right = right;
  this.show = show
}

function show () { // 显示树的数据
  return this.data
}

function BST () { // 二叉查找树类
  this.root = null;
  this.insert = insert;
  this.inOrder = inOrder; // inOrder是遍历BST的方式
}

function insert (data) { // 向树中插入数据
  var n = new Node(data, null, null)
  if (this.root == null) {
    this.root = n;
  } else {
    var current = this.root;
    var parent;
    while (true) {
	  parent = current
	  if (data < current.data) {
		current = current.left;
		if (current == null) {
		  parent.left = n;
		  break;
		}
	  } else {
		current = current.right;
		if (current == null) {
		  parent.right = n;
		  break;
		}
	  }
    }
  }
}

images
遍历BST的方式有三种:中序遍历(以升序访问树中所有节点,先访问左节点,再访问根节点,最后访问右节点)、先序遍历(先访问根节点,再以同样的方式访问左节点和右节点)、后序遍历(先访问叶子节点,从左子树到右子树,再到根节点)

排序算法

基本排序算法

基本排序算法,其核心思想是指对一组数组按照一定的顺序重新排列。重新排列时用到的技术是一组嵌套的for循环。其中外循环会遍历数组的每一项,内循环则用于比较元素。

冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

function bubbleSort (arr) {
	var i = arr.length;
	while (i > 0) {
		var pos = 0
		for (var j = 0; j < i; j++) {
			if (arr[j] > arr[j+1]){
				pos = j
				var temp = arr[j]
				arr[j] = arr[j+1]
				arr[j+1] = temp
			}
		}
		i = pos
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(bubbleSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

images

选择排序

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

function selectionSort (arr) {
	var len = arr.length;
	var minIndex, temp;
	for (var i = 0; i < len-1; i++) {
		minIndex = i;
		for (var j = i+1; j < len; j++) {
			if (arr[j] < arr[minIndex]) {
				minIndex = j
			}
		}
		temp = arr[minIndex]
		arr[minIndex] = arr[i]
		arr[i] = temp
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(selectionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

images

插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

function insertSort (arr) {
	var len = arr.length
	for (i = 1; i < len; i++) {
		var key = arr[i]
		var j = i - 1
		while (j >= 0 && arr[j] > key) {
			arr[j+1] = arr[j]
			j--;
		}
		arr[j+1] = key
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(insertSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

images

高级排序算法

高级数据排序算法,通常用于处理大型数据集的最高效排序算法,它们处理的数据集可以达到上百万个元素,而不仅仅是几百个或者几千个,下面我们将介绍希尔排序、归并排序和快速排序。

希尔排序

1959年Shell发明,第一个突破O(n^2)的排序算法;是简单插入排序的改进版;它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。

function shellSort (arr) {
	var len = arr.length;
	var temp, gap = 1;
	while (gap < len /3 ) {
		gap = gap * 3 + 1
	}
	while (gap >= 1) {
		for (var i = gap; i < len; i++) {
			temp = arr[i]
			for (var j = i - gap; j >= 0 && arr[j] > temp; j-=gap) {
				arr[j+gap] = arr[j]
			}
			arr[j+gap] = temp
		}
		gap = (gap - 1) / 3
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(shellSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

5555

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

function mergeSort (arr) {
	var len = arr.length
	if (len < 2) {
		return arr
	}
	var middle = Math.floor(len / 2)
	var left = arr.slice(0, middle)
	var right = arr.slice(middle)
	return merge (mergeSort(left), mergeSort(right));
}
function merge (left, right) {
	var result = []
	while (left.length && right.length) {
		if (left[0] < right[0]) {
			result.push(left.shift())
		} else {
			result.push(right.shift())
		}
	}
	while (left.length) {
		result.push(left.shift())
	}
	while (right.length) {
		result.push(right.shift())
	}
	return result
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(mergeSort(arr));

images

快速排序

快速排序是处理 大数据集最快的排序算法之一。它是一种分而治之的算法,通过递归的方法将数据依次分解为包含较小元素和较大元素的不同子序列。该算法不断重复这个步骤知道所有数据都是有序的。
这个算法首先要在列表中选择一个元素作为基准值。数据排序围绕基准值进行,将列表中小于基准值的元素移到数组的底部,将大于基准值的元素移到数组的顶部。

function qSort (arr) {
	if (arr.length == 0) {
		return []
	}
	var left = []
	var right = []
	var pivot = arr[0]
	for (var i = 1; i < arr.length; i++) {
		if (arr[i] < pivot) {
			left.push(arr[i])
		} else {
			right.push(arr[i])
		}
	}
	return qSort(left).concat(pivot, qSort(right))
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(qSort(arr));

images

检索算法

在列表中查找数据有两种方式:顺序查找和二分查找。顺序查找适用于元素随机排列的列表;二分查找适用于元素已排序的列表。二分查找效率更高,但是必须在进行查找之前花费额外的时间将列表中的元素排序。

顺序查找

对于查找数据,最简单的方法就是从列表的第一个元素开始对列表元素逐个进行判断,直到找到了想要的结果,或者直到列表结尾也没有找到。这种方法称为顺序查找,有时也被称为线性查找。

function seqSearch (arr, data) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] == data) {
      return i;
    }
  }
  return -1;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(seqSearch(arr, 15))

二分查找

二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤:

  • 首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。
  • 如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。
  • 如果某一步数组为空,则表示找不到目标元素。
function binSearch (arr, data) {
	var low = 0;
	var high = arr.length - 1
	while (low <= high) {
		var middle = Math.floor((low + high) / 2)
		if (arr[middle] < data) {
			low = middle + 1
		} else if (arr[middle] > data) {
			high = middle - 1
		} else {
			return middle
		}
	}
	return -1
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(binSearch(arr, 15))

最后

  • 非常谢谢观看,看到这里相信很不容易,毕竟相对枯燥的知识点很多,但却是必不可少的。希望这篇文章能让你对数据结构和算法有一个新的认识,或者产生一些新的想法,那么写这篇文章的意义就达到了,当然发现文章写得有问题的,也非常欢迎指出,一起共同成长。
  • 欢迎关注我的github—-https://github.com/zoro-web/blog,你的关注是我整理知识的更大动力,我的博客会定期整理发布一些文章。

你有必要知道的25个JavaScript面试题

原文地址:http://www.jb51.net/article/77140.htm

 

1、使用 typeof bar === “object” 判断 bar 是不是一个对象有神马潜在的弊端?如何避免这种弊端?

使用 typeof 的弊端是显而易见的(这种弊端同使用 instanceof):

1
2
3
4
5
6
let obj = {};
let arr = [];
console.log(typeof obj === ‘object’); //true
console.log(typeof arr === ‘object’); //true
console.log(typeof null === ‘object’); //true

从上面的输出结果可知,typeof bar === “object” 并不能准确判断 bar 就是一个 Object。可以通过 Object.prototype.toString.call(bar) === “[object Object]” 来避免这种弊端:

1
2
3
4
5
6
let obj = {};
let arr = [];
console.log(Object.prototype.toString.call(obj)); //[object Object]
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(Object.prototype.toString.call(null)); //[object Null]

另外,为了珍爱生命,请远离 ==:

而 [] === false 是返回 false 的。

2、下面的代码会在 console 输出神马?为什么?

1
2
3
4
5
6
(function(){
 var a = b = 3;
})();
console.log(“a defined? ” + (typeof a !== ‘undefined’));
console.log(“b defined? ” + (typeof b !== ‘undefined’));

这跟变量作用域有关,输出换成下面的:

1
2
console.log(b); //3
console,log(typeof a); //undefined

拆解一下自执行函数中的变量赋值:

b = 3;
var a = b;

所以 b 成了全局变量,而 a 是自执行函数的一个局部变量。

3、下面的代码会在 console 输出神马?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = {
 foo: “bar”,
 func: function() {
 var self = this;
 console.log(“outer func: this.foo = ” + this.foo);
 console.log(“outer func: self.foo = ” + self.foo);
 (function() {
 console.log(“inner func: this.foo = ” + this.foo);
 console.log(“inner func: self.foo = ” + self.foo);
 }());
 }
};
myObject.func();

第一个和第二个的输出不难判断,在 ES6 之前,JavaScript 只有函数作用域,所以 func 中的 IIFE 有自己的独立作用域,并且它能访问到外部作用域中的 self,所以第三个输出会报错,因为 this 在可访问到的作用域内是 undefined,第四个输出是 bar。如果你知道闭包,也很容易解决的:

1
2
3
4
(function(test) {
 console.log(“inner func: this.foo = ” + test.foo); //’bar’
 console.log(“inner func: self.foo = ” + self.foo);
}(self));

如果对闭包不熟悉,可以参考本文:从作用域链谈闭包

4、将 JavaScript 代码包含在一个函数块中有神马意思呢?为什么要这么做?

换句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。

IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。

1
2
3
4
5
for(var i = 0; i < 5; i++) {
 setTimeout(function() {
 console.log(i);
 }, 1000);
}

上面的输出并不是你以为的0,1,2,3,4,而输出的全部是5,这时 IIFE 就能有用了:

1
2
3
4
5
6
7
for(var i = 0; i < 5; i++) {
 (function(i) {
 setTimeout(function() {
 console.log(i);
 }, 1000);
 })(i)
}

而在 JQuery/Node 的插件和模块开发中,为避免变量污染,也是一个大大的 IIFE:

1
2
3
(function($) {
 //代码
 } )(jQuery);

5、在严格模式(‘use strict’)下进行 JavaScript 开发有神马好处?

消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的Javascript做好铺垫。
6、下面两个函数的返回值是一样的吗?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo1()
{
 return {
 bar: “hello”
 };
}
function foo2()
{
 return
 {
 bar: “hello”
 };
}

在编程语言中,基本都是使用分号(;)将语句分隔开,这可以增加代码的可读性和整洁性。而在JS中,如若语句各占独立一行,通常可以省略语句间的分号(;),JS 解析器会根据能否正常编译来决定是否自动填充分号:

1
2
3
var test = 1 +
2
console.log(test); //3

在上述情况下,为了正确解析代码,就不会自动填充分号了,但是对于 return 、break、continue 等语句,如果后面紧跟换行,解析器一定会自动在后面填充分号(;),所以上面的第二个函数就变成了这样:

1
2
3
4
5
6
7
function foo2()
{
 return;
 {
 bar: “hello”
 };
}

所以第二个函数是返回 undefined。

7、神马是 NaN,它的类型是神马?怎么测试一个值是否等于 NaN?

NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,可以通过 isNaN(param) 来判断一个值是否是 NaN:

1
2
3
4
5
6
7
8
9
console.log(isNaN(NaN)); //true
console.log(isNaN(23)); //false
console.log(isNaN(‘ds’)); //true
console.log(isNaN(‘32131sdasd’)); //true
console.log(NaN === NaN); //false
console.log(NaN === undefined); //false
console.log(undefined === undefined); //false
console.log(typeof NaN); //number
console.log(Object.prototype.toString.call(NaN)); //[object Number]

ES6 中,isNaN() 成为了 Number 的静态方法:Number.isNaN().

8、解释一下下面代码的输出

1
2
console.log(0.1 + 0.2); //0.30000000000000004
console.log(0.1 + 0.2 == 0.3); //false

JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024,每个浮点数占64位。但是,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,会有舍入误差。

由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:

0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100…因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004。

对于保证浮点数计算的正确性,有两种常见方式。

一是先升幂再降幂:

1
2
3
4
5
6
7
8
9
10
function add(num1, num2){
 let r1, r2, m;
 r1 = (”+num1).split(‘.’)[1].length;
 r2 = (”+num2).split(‘.’)[1].length;
 m = Math.pow(10,Math.max(r1,r2));
 return (num1 * m + num2 * m) / m;
}
console.log(add(0.1,0.2)); //0.3
console.log(add(0.15,0.2256)); //0.3756

二是是使用内置的 toPrecision() 和 toFixed() 方法,注意,方法的返回值字符串。

1
2
3
4
function add(x, y) {
 return x.toPrecision() + y.toPrecision()
}
console.log(add(0.1,0.2)); //”0.10.2″

9、实现函数 isInteger(x) 来判断 x 是否是整数

可以将 x 转换成10进制,判断和本身是不是相等即可:

1
2
3
function isInteger(x) {
 return parseInt(x, 10) === x;
}

ES6 对数值进行了扩展,提供了静态方法 isInteger() 来判断参数是否是整数:

1
2
3
4
5
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger(“15”) // false
Number.isInteger(true) // false

JavaScript能够准确表示的整数范围在 -2^53 到 2^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限,并提供了 Number.isSafeInteger() 来判断整数是否是安全型整数。

10、在下面的代码中,数字 1-4 会以什么顺序输出?为什么会这样输出?

1
2
3
4
5
6
(function() {
 console.log(1);
 setTimeout(function(){console.log(2)}, 1000);
 setTimeout(function(){console.log(3)}, 0);
 console.log(4);
})();

这个就不多解释了,主要是 JavaScript 的定时机制和时间循环,不要忘了,JavaScript 是单线程的。详解可以参考 从setTimeout谈JavaScript运行机制。

11、写一个少于 80 字符的函数,判断一个字符串是不是回文字符串

1
2
3
4
function isPalindrome(str) {
 str = str.replace(/\W/g, ”).toLowerCase();
 return (str == str.split(”).reverse().join(”));
}

这个题我在 codewars 上碰到过,并收录了一些不错的解决方式,可以戳这里:Palindrome For Your Dome

12、写一个按照下面方式调用都能正常工作的 sum 方法

1
2
console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

针对这个题,可以判断参数个数来实现:

1
2
3
4
5
6
7
8
9
10
11
function sum() {
 var fir = arguments[0];
 if(arguments.length === 2) {
 return arguments[0] + arguments[1]
 } else {
 return function(sec) {
 return fir + sec;
 }
 }
}

13、根据下面的代码片段回答后面的问题

1
2
3
4
5
6
for (var i = 0; i < 5; i++) {
 var btn = document.createElement(‘button’);
 btn.appendChild(document.createTextNode(‘Button ‘ + i));
 btn.addEventListener(‘click’, function(){ console.log(i); });
 document.body.appendChild(btn);
}

1、点击 Button 4,会在控制台输出什么?

2、给出一种符合预期的实现方式

  • 1、点击5个按钮中的任意一个,都是输出5
  • 2、参考 IIFE。

14、下面的代码会输出什么?为什么?

1
2
3
4
5
6
var arr1 = “john”.split(”); j o h n
var arr2 = arr1.reverse(); n h o j
var arr3 = “jones”.split(”); j o n e s
arr2.push(arr3);
console.log(“array 1: length=” + arr1.length + ” last=” + arr1.slice(-1));
console.log(“array 2: length=” + arr2.length + ” last=” + arr2.slice(-1));

会输出什么呢?你运行下就知道了,可能会在你的意料之外。

reverse() 会改变数组本身,并返回原数组的引用。

slice 的用法请参考:slice

15、下面的代码会输出什么?为什么?

1
2
3
4
5
6
console.log(1 + “2” + “2”);
console.log(1 + +”2″ + “2”);
console.log(1 + -“1” + “2”);
console.log(+”1″ + “1” + “2”);
console.log( “A” – “B” + “2”);
console.log( “A” – “B” + 2);

输出什么,自己去运行吧,需要注意三个点:

多个数字和数字字符串混合运算时,跟操作数的位置有关

1
2
console.log(2 + 1 + ‘3’); / /‘33′
console.log(‘3′ + 2 + 1); //’321’

数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字

1
2
console.log(typeof ‘3’); // string
console.log(typeof +’3′); //number

同样,可以在数字前添加 ”,将数字转为字符串

1
2
console.log(typeof 3); // number
console.log(typeof (”+3)); //string

对于运算结果不能转换成数字的,将返回 NaN

1
2
console.log(‘a’ * ‘sd’); //NaN
console.log(‘A’ – ‘B’); // NaN

这张图是运算转换的规则

16、

如果 list 很大,下面的这段递归代码会造成堆栈溢出。如果在不改变递归模式的前提下修善这段代码?

1
2
3
4
5
6
7
8
9
10
var list = readHugeList();
var nextListItem = function() {
 var item = list.pop();
 if (item) {
 // process the list item…
 nextListItem();
 }
};

原文上的解决方式是加个定时器:

1
2
3
4
5
6
7
8
9
10
var list = readHugeList();
var nextListItem = function() {
 var item = list.pop();
 if (item) {
 // process the list item…
 setTimeout( nextListItem, 0);
 }
};

解决方式的原理请参考第10题。

17、什么是闭包?举例说明

可以参考此篇:从作用域链谈闭包

18、下面的代码会输出什么?为啥?

1
2
3
for (var i = 0; i < 5; i++) {
 setTimeout(function() { console.log(i); }, i * 1000 );
}

请往前面翻,参考第4题,解决方式已经在上面了

19、解释下列代码的输出

1
2
3
4
console.log(“0 || 1 = “+(0 || 1));
console.log(“1 || 2 = “+(1 || 2));
console.log(“0 && 1 = “+(0 && 1));
console.log(“1 && 2 = “+(1 && 2));

逻辑与和逻辑或运算符会返回一个值,并且二者都是短路运算符:

逻辑与返回第一个是 false 的操作数 或者 最后一个是 true的操作数

1
2
3
console.log(1 && 2 && 0); //0
console.log(1 && 0 && 1); //0
console.log(1 && 2 && 3); //3

如果某个操作数为 false,则该操作数之后的操作数都不会被计算

逻辑或返回第一个是 true 的操作数 或者 最后一个是 false的操作数

1
2
3
console.log(1 || 2 || 0); //1
console.log(0 || 2 || 1); //2
console.log(0 || 0 || false); //false

如果某个操作数为 true,则该操作数之后的操作数都不会被计算

如果逻辑与和逻辑或作混合运算,则逻辑与的优先级高:

1
2
3
console.log(1 && 2 || 0); //2
console.log(0 || 2 && 1); //1
console.log(0 && 2 || 1); //1

在 JavaScript,常见的 false 值:

0, ‘0’, +0, -0, false, ”,null,undefined,null,NaN
要注意空数组([])和空对象({}):

1
2
3
4
console.log([] == false) //true
console.log({} == false) //false
console.log(Boolean([])) //true
console.log(Boolean({})) //true

所以在 if 中,[] 和 {} 都表现为 true:

20、解释下面代码的输出

1
2
console.log(false == ‘0’)
console.log(false === ‘0’)

请参考前面第14题运算符转换规则的图。

21、解释下面代码的输出

1
2
3
4
5
6
7
8
var a={},
 b={key:’b’},
 c={key:’c’};
a[b]=123;
a[c]=456;
console.log(a[b]);

输出是456。

22、解释下面代码的输出

1
console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

结果是10的阶乘。这是一个递归调用,为了简化,我初始化 n=5,则调用链和返回链如下:

23、解释下面代码的输出

1
2
3
4
5
(function(x) {
 return (function(y) {
 console.log(x);
 })(2)
})(1);

输出1,闭包能够访问外部作用域的变量或参数。

24、解释下面代码的输出,并修复存在的问题

1
2
3
4
5
6
7
8
9
10
11
var hero = {
 _name: ‘John Doe’,
 getSecretIdentity: function (){
 return this._name;
 }
};
var stoleSecretIdentity = hero.getSecretIdentity;
console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

将 getSecretIdentity 赋给 stoleSecretIdentity,等价于定义了 stoleSecretIdentity 函数:

1
2
3
4
var stoleSecretIdentity = function (){
 return this._name;
}
stoleSecretIdentity

的上下文是全局环境,所以第一个输出 undefined。若要输出 John Doe,则要通过 call 、apply 和 bind 等方式改变 stoleSecretIdentity 的this 指向(hero)。

第二个是调用对象的方法,输出 John Doe。

25、给你一个 DOM 元素,创建一个能访问该元素所有子元素的函数,并且要将每个子元素传递给指定的回调函数。

函数接受两个参数:

  • DOM
  • 指定的回调函数

原文利用 深度优先搜索(Depth-First-Search) 给了一个实现:

1
2
3
4
5
6
7
function Traverse(p_element,p_callback) {
 p_callback(p_element);
 var list = p_element.children;
 for (var i = 0; i < list.length; i++) {
 Traverse(list[i],p_callback); // recursive call
 }
}

以上就是为大家分享的25个JavaScript面试题,希望对大家参加面试有所帮助。

程序人生

IT人表示屁股上还得纹一个</body> , 要不中间来个hello world!

真正的程序员喜欢兼卖爆米花,他们利用CPU散发出的热量做爆米花,可以根据米花
爆裂的速度听出正在运行什么程序。

十年生死两茫茫,写程序,到天亮。
千行代码,Bug何处藏。
纵使上线又怎样,朝令改,夕断肠。
领导每天新想法,天天改,日日忙。
相顾无言,惟有泪千行。
每晚灯火阑珊处,夜难寐,又加班。

老婆给当程序员的老公打电话:“下班顺路买三个包子带回来,如果看到卖西瓜的,买一个。”
当晚,程序员老公手捧一个包子进了家门。。。
老婆怒道:“你怎么就买了一个包子?!”
老公答曰:“因为看到了卖西瓜的。”

程序员爱情观:爱情就是死循环,一旦执行就陷进去了;
爱上一个人,就是内存泄漏–你永远释放不了;
真正爱上一个人的时候,那就是常量限定,永远不会改变;
女朋友就是私有变量,只有我这个类才能调用;
情人就是指针用的时候一定要注意,要不然就带来巨大的灾难。

某程序员对书法十分感兴趣,退休后决定在这方面有所建树。
于是花重金购买了上等的文房四宝。
一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,
定神片刻,泼墨挥毫,郑重地写下一行字:hello world.

一个程序员在海滨游泳时溺水身亡。
当时海滩上有许多救生员,救生员们只听见有人大声喊“F1!”“F1!”,谁都不知道“F1”究竟是什么意思。

你能让一个程序员一天到晚呆在淋浴房里吗?
给他一瓶洗发香波,上面写着:
for(;;)
{
涂抹香波;
温水冲洗;
}

打,打,打劫
一女黑夜遇抢劫,颤抖曰:“大哥,我是搞java的,两个月没发工资了,还刚被裁员,你看报道就知道了,真的没有钱… …”
劫匪听后竟然痛哭流涕,“妹子,同行,俺原来是做C++的,金融危机闹得做劫匪也
不踏实,你拿好工牌,后面那帮抢劫是做.NET的,你放心,我们绝不抢自己人。”
“对了,边上那条路不要走,那边是搞PHP的… …”

有一个物理学家,工程师和一个程序员驾驶着一辆汽车行驶在阿尔卑斯山脉上,在下山的时候,
忽然,汽车的刹车失灵了,汽车无法控制地向下冲去,眼看前面就是一个悬崖峭壁,
但是很幸运的是在这个悬崖的前面有一些小树让他们的汽车停了下来,而没有掉下
山去。三个惊魂未定地从车里爬了出来。
物理学家说,“我觉得我们应该建立一个模型来模拟在下山过程中刹车片在高温情况下失灵的情形”。
工程师说,“我在车的后备厢来有个扳手,要不我们把车拆开看看到底是什么原因”。
程序员说,“为什么我们不找个相同的车再来一次以重现这个问题呢?”

有一个小伙子在一个办公大楼的门口抽着烟,一个妇女路过他身边,并对他说,
“你知道不知道这个东西会危害你的健康?我是说,你有没有注意到香烟盒上的那个警告(Warning)?”
小伙子说,“没事儿,我是一个程序员”。
那妇女说,“这又怎样?”
程序员说,“我们从来不关心Warning,只关心Error”

两字符串走进一个酒吧坐下,酒保过来,“两位来点什么?”
第一个字符串说,“我要一杯湿啤 fulk boorg jdk^CjfdLk jk3s d#f67howe%^U r89nvy~~owmc63^Dz x.xvcu。”
“对不起,”第二个字符串对酒保说,“我这哥们没有\0结尾。

为API生,为框架死,为debug奋斗一辈子,吃符号亏,上大小写的当,最后死在需求上

程序员眼中的女人
有的女人就像Windows 虽然很优秀,但是安全隐患太大。
有的女人就像UNIX 她条件很好,然而不是谁都能玩的起。
有的女人就像C# 长的很漂亮,但是家务活不行。
有的女人就像C++,她会默默的为你做很多的事情。
有的女人就像JAVA,只需一点付出她就会为你到处服务。
有的女人就像JAVA script,虽然对她处处小心但最终还是没有结果。
有的女人就像汇编 虽然很麻烦,但是有的时候还得求它。

一百万只猴子,给他们一百万个键盘,其中的一个会写出Java程序,其它的写的都是Perl程序。

十个顶尖软件工程师参加培训管理人员的课程。
老师提出了这样一个问题:“假如你供职的公司是为航空电子设备提供软件的。有一天你乘飞机出差,当你上了飞机后你发现一个牌子上写着这个飞机用的是你的团
队开发的Beta版软件,你会下飞机吗?”
九个软件工程师举了手。
老师看着第十个问道:“你为什么愿意留在飞机上?”
这人回答说:“如果是我的团队写的这个软件,飞机根本无法起飞,根本谈不上坠毁。”

这个笑话大概来自70年代的米国:一个新手在试着修复一台坏了的LISP机器,他的
方法就是反复关上电源然后打开。专家看到之后,严厉地批评说:“你这样是没用
的,你必须要明白出错的深层次原因。”
专家关掉电源,然后打开。运行正常了。

为什么程序员总是分不清万圣节和圣诞节?因为 Oct 31 == Dec 25。

小沈阳版程序员~~~
程序员其实可痛苦的了……需求一做一改,一个月就过去了;嚎~
需求再一改一调,一季度就过去了;嚎~
程序员最痛苦的事儿是啥,知道不?就是,程序没做完,需求又改了;
程序员最最痛苦的事儿是啥,知道不? 就是,系统好不容易做完了,方案全改了;
程序员最最最痛苦的事儿是啥,知道不? 就是,系统做完了,狗日的客户跑了;
程序员最最最最最痛苦的事儿是啥,知道不? 就是,狗日的客户又回来了,程序给删没了!

这个世界上只有10种人:懂得二进制的和不懂得二进制的。

Borland说我很有前途,Sun笑了;Sun说我很有钱,IBM笑了;IBM说我很专业,
Sybase笑了;Sybase说我数据库很牛,Oracle笑了;Oracle说我是开放的,Linux笑
了;Linux说我要打败Unix,微软笑了;微软说我的系统很稳定,我们都笑了。

程序员的祝福
祝大家在以后的日子里. 男生象Oracle般健壮; 女生象win7般漂亮; 桃花运象IE中
毒般频繁; 钱包如Gmail容量般壮大, 升职速度赶上微软打补丁 , 追女朋友像木马
一样猖獗, 生活像重装电脑后一样幸福, 写程序敲代码和聊天一样有**。

一个合格的程序员是不会写出 诸如 “摧毁地球” 这样的程序的,他们会写一个函
数叫 “摧毁行星”而把地球当一个参数传进去。

【 程序猿的三重境界】第一重:无尽bug常作客,困闷调试伴不眠。第二重:千行
代码过,bug不沾身。第三重:编码间,bug灰飞烟灭。

四个2B青年掐架。
A:你丫等着,我爹是敏感词!
B:操你大爷!你丫牛逼神马,我爹在百度索根本无法显示!!
C: 我爹404 not found!!!
D:我爹 Connection Reset !!!!

天冷了,办公室的门上出现了一张告示,赫然写着,“不关门,有BUG”,然后就见
每个出去的程序员,非常虔诚的把门关上了。

程序员找不到对象,一般有三种情况:1、 C# JAVA都有对象,但是经常找不到对象
。2、ASM C直接没有对象。3、javascript都是伪对象,最多算暧昧。但C++日子一
直都好过,因为C++是多继承,富二代呀!!!

汇编,C和C++是好朋友,每天一起吃饭。可是前天晚上C++一个人去吃饭了。问他怎
么了,他说“汇编和C没有对象,他们去过节了。。。

栈和队列的区别是啥? 吃多了拉就是队列;吃多了吐就是栈

CSDN社区网友on1y_1onely留言:“C确实不适合做应用层,C有C的地盘。”网友
zbdbao回复:“就是传说中的C盘么。”当时我就笑喷了……

一个IT经理走进一家拉面馆说:“你们需要客户端吗?” 老板说:“面一般是伙计
端,忙的时候才需要客户端。

一同学问我,软件外包是什么。解释了几句还没明白,遂想了一下:包工头知道吧?顿悟!

正在码代码ing,医院回来的同事一脸的苦逼样子,问他怎么了?他回答:得了类风
湿性关节炎了,我怕会遗传给下一代啊。我一脸的问号:谁说类风湿性关节炎能遗
传的?丫一脸诧异:类不是继承的吗?

世界上最遥远的距离不是生与死,而是你亲手制造的BUG就在你眼前,你却怎么都找不到她。。。

真的勇士,敢于直面惨淡的warning、敢于正视淋漓的error。

成功chroot过很多系统,却从未成功chroot过妹子的心。

某小偷潜入某IT公司欲行窃,没想到始终有人,他只好等啊等啊等,结果始终有一
大拨人在加班。过了一个月,小偷终于逮到机会溜出来,同伙问:去哪了?小偷:
在IT公司呆了一个月。同伙:收获不错吧?小偷苦笑:别TM提了,三十个大夜下来
,我现在已经学会写程序了。

继承,是幸福的延续;重载,是幸福的重生。

一同事最近bug特别多,假装关切问他有多少个bug,他回答:10。过几日,再问,
他回答:10。我:“这么多天过去了,怎么一点变化都没有?”,他:”哪里没有
变化啊,以前是十进制,现在变成十六进制。

假如生活欺骗了你,找50个程序员问问为什么编程;假如生活让你想死,找50个程
序员问问BUG改完了没有;假如你觉得生活拮据,找50个程序员问问工资涨了没有;
假如你觉得活着无聊,找50个程序员问问他们一天都干了什么!

经考证,C语言是女的…原因如下:1.无论你让她干什么,她绝对不可能自己找到方
法。2.总是用复杂的方法解决简单的问题。3.不可能自主认识到本身错误。4.浪费
时间是十分正常的事情。5.无论跟她说什么,都得用她能理解的方式做充分说明。
6.只要你有一点错,她一定可以挑出来。7.反正,是你的错。

必备技能:去得了公司,回得了厨房;不羡慕好车,不想买新房;不惊动腾讯,不
激怒同行;写得了代码,查得出异常;做得出产品,看得准市场;接触过VC,见识
过投行。

如果你的朋友最近没和你联系,要理解!只有三种可能:第一,他死了;第二,他
改行当程序猿了;第三,需求又改了!

某日和同学聊起对象问题,我答道:哥最不缺的就是对象了,要知道哥是程序员,
每天不知道要new多少个对象出来,而且想要啥类型就有啥类型。如果你想要,哥给
你来个for循环嵌套,new上她千个百个

做为一名程序员,我每次看到工资单的时候,都会想:那些数字为神马不是16进制。

一个程序出错信息: Keyboard not found … press F1 to continue

大家喝的是啤酒,这时你入座了。
你给自己倒了杯可乐,这叫低配置。
你给自已倒了杯啤酒,这叫标准配置。
你给自己倒了杯茶水,这茶的颜色还跟啤酒一样,这叫木马。
你给自己倒了杯可乐,还滴了几滴醋,不仅颜色跟啤酒一样,而且不冒热气还有泡泡,这叫超级木马。
你的同事给你倒了杯白酒,这叫推荐配置。
菜过三巡,你就不跟他们客气了。
你向对面的人敬酒,这叫p2p。
你向对面的人敬酒,他回敬你,你又再敬他……,这叫tcp。
你向一桌人挨个敬酒,这叫令牌环。
你说只要是兄弟就干了这杯,这叫广播。
有一个人过来向这桌敬酒,你说不行你先过了我这关,这叫防火墙。
你的小弟们过来敬你酒,这叫一对多。
你是boss,所有人过来敬你酒,这叫服务器。
酒是一样的,可是喝酒的人是不同的。
你越喝脸越红,这叫频繁分配释放资源。
你越喝脸越白,这叫资源不释放。
你已经醉了,却说我还能喝,叫做资源额度不足。
你明明能喝,却说我已经醉了,叫做资源保留。
喝酒喝到最后的结果都一样
你突然跑向厕所,这叫捕获异常。
你在厕所吐了,反而觉得状态不错,这叫清空内存。
你在台面上吐了,觉得很惭愧,这叫程序异常。
你在boss面前吐了,觉得很害怕,这叫系统崩溃。
你吐到了boss身上,只能索性晕倒了,这叫硬件休克。

与女友分手两月有余,精神萎靡,面带菜色。家人介绍一女孩,昨日与其相亲。女孩果然漂亮,一向吝啬的我决定破例请她吃晚饭。
选了一个蛮贵的西餐厅,点了比较贵的菜。女孩眉开眼笑,与我谈得很投机。聊着聊着,她说:“我给你讲个笑话吧。”“ok”
“一只螳螂要给一只雌蝴蝶介绍对象,见面时发现对方是只雄蜘蛛。见面后螳螂问蝴蝶‘如何?’,‘他长的太难看了’,‘别看人家长的丑,人家还有网站呢’。”
“呵呵………”我笑。忽然她问:“你有网站吗?”

系统程序员
1、头皮经常发麻,在看见一个蓝色屏幕的时候比较明显,在屏幕上什幺都看不见的时候尤其明显;
2、乘电梯的时候总担心死机,并且在墙上找reset键;
3、指甲特别长,因为按F7到F12比较省力;
4、只要手里有东西,就不停地按,以为是Alt-F、S;
5、机箱从来不上盖子,以便判断硬盘是否在转;
6、经常莫名其妙地跟踪别人,手里不停按F10;
7、所有的接口都插上了硬盘,因此觉得26个字母不够;
8、一有空就念叨“下辈子不做程序员了”;
9、总是觉得9号以后是a号;

10、不怕病毒,但是很害怕自己的程序;

原文地址:http://blog.csdn.net/qy1387/article/details/7957394

Webpack 打包优化之体积篇

原文出处: 晚晴幽草轩

 

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如Vue, React, Angular等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者Browserify, Grunt,到后来赢得宝座的 Gulp, 以及独树一帜的 fis3, 以及下一代打包神器 Rollup ;在 browserify,grunt,gulp,rollup,webpack 可以一窥其中部分对比。在本文要探究的是,当前打包工具绝对霸者 Webpack。

webpack

Webpack,当前各大主流框架默认配备的打包方案,对其如何使用,已有较完备中英文文档;并且,各主流框架也有对应 CLI 予以基础配置,故不作为探讨范畴。从产品层来讲,如何使得构建的包体积小、运行快,这有必要不断摸索实践,提炼升级,使之臻于最佳。本文将从以下些许方面,对 Webpack 打包体积方面,做下优化探讨(备注: Webpack实践版本: 3.3.0):

定位 webpack 大的原因

这里推荐使用 webpack-bundle-analyzer —— Webpack 插件和 CLI 实用程序,她可以将内容束展示为方便交互的直观树状图,让你明白你所构建包中真正引入的内容;我们可以借助她,发现它大体有哪些模块组成,找到不合时宜的存在,然后优化它。我们可以在 项目的 package.json 文件中注入如下命令,以方便运行她(npm run analyz),默认会打开 http://127.0.0.1:8888 作为展示。

“analyz”: “NODE_ENV=production npm_config_report=true npm run build”

93f72404-b338-11e6-92d4-9a365550a701

 

当然,同类型的还有 webpack-chart 以及 webpack-analyse,这两个站点也是以可视方式呈现构造的组件,可以让你清楚的看到模块的组成部分;不过稍显麻烦的是,你需要运行以下命令,生成工具分析所需要的 json 文件:

JavaScript

1
2
3
webpack –profile –json > stats.json
// 如果,运行指定的 weboack 文件,可用此命令
webpack –config build/webpack.prod.conf.js  –profile –json > stats.json

引入 DllPlugin 和 DllReferencePlugin

DllPlugin 和 DllReferencePlugin 提供了以大幅度提高构建时间性能的方式拆分软件包的方法。其中原理是,将特定的第三方NPM包模块提前构建👌,然后通过页面引入。这不仅能够使得 vendor 文件可以大幅度减小,同时,也极大的提高了构件速度。鉴于篇幅,具体用法可参见:webpack.dll.conf.js

外部引入模块(CDN)

如今前端开发,自然是使用ES6甚至更高版本,撸将起来才更嗨。但由于浏览器兼容问题,仍得使用 babel 转换。而这 babel-polyfill 也得引入以确保兼容;还比如项目开发中常用到的 moment, lodash等,都是挺大的存在,如果必须引入的话,即考虑外部引入之,再借助 externals 予以指定, webpack可以处理使之不参与打包,而依旧可以在代码中通过CMD、AMD或者window/global全局的方式访问。

JavaScript

1
2
3
4
5
6
7
8
9
// webpack 中予以指定
externals: {
  // ‘vue’: ‘Vue’,
  // ‘lodash’: ‘_’,
  ‘babel-polyfill’: ‘window’
}
//
<script src=”//cdn.bootcss.com/autotrack/2.4.1/autotrack.js”></script>
<script src=”//cdn.bootcss.com/babel-polyfill/7.0.0-alpha.15/polyfill.min.js”></script>

需要补充的是 externals 中:key 是 require 的包名,value 是全局的变量。

让每个第三包“引有所值”

确定引入的必要性

前端发展到如今时期,倘若项目采用了 MVVM模式框架,数据双向绑定,那么像 jQuery 这般类库,不能说没有丝毫引入的必要,至少可以说确实没有引入的必要。对此,如果还有些顾虑,完全可以参考下 YOU MIGHT NOT NEED JQUERY;用原生写几行代码就可以解决的事儿,实在不易引入这么个庞然大物,平添烦恼。

避免类库引而不用

倘若这类情况发生,对整个打包体积,不仅大而且亏。项目一旦大了,很难人为保证每个引入的类库,都被有用到,尤其是二次开发。所以工具的利用十分必要,强烈推荐类如 Eslint这般工具,并且注入对应规则,对声明却未使用的代码,给予强制提醒;这不仅可以有效的规避类似情形发生(也适用于普通变量的检测),而且还能使得团队代码风格,尽可能地保持相似;要知道代码足够遵守规则,也可让压缩工具更有效压缩代码,一举多得,何乐不为?

尽量使用模块化引入

如果说 jQuery 确实没有引入必要,很多人会同意;但对于 lodash 这类依赖的工具,并不是所有人都会去造一发轮子的。然而全包引入 400kb 的体量,可否有让你心肝一颤?幸好的是,lodash 提供了模块化的引入方式;可按需引入,快哉快哉:

JavaScript

1
2
3
4
5
import { debounce } from ‘lodash’
import { throttle } from ‘lodash’
// 改成如下写法
import debounce from ‘lodash/debounce’
import throttle from ‘lodash/throttle’

擅懒如你的优秀程序员,是否也发现这样写颇为麻烦?那么恭喜你,这个问题已经被解决;lodash-webpack-pluginbabel-plugin-lodash 的存在(组合使用),即是解决这问题的。它可将全路径引用的 lodash, 自动转变为模块化按使用引入(如下例示);并且所需配置也十分简单,就不在此赘述(温馨提示:当涉及些特殊方法时,尚需些留意)。

JavaScript

1
2
3
4
// 引入组件,自动转换
import _ from ‘lodash’
_.debounce()
_.throttle()

额外补充的是,即便采用如上写法,还是不够快捷,每个用到的文件,都写一遍 import,实在多有不便。更可取的是,将项目所需的方法,统一引入,按需添加,组建出本地 lodash 类库,然后 export 给框架层(比如 Vue.prototype),以便全局使用;详情可参见:vue-modular-import-lodash

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// helper 文件夹下 lodash,统一引入你需要的方法
import _ from ‘lodash’
export default {
  cloneDeep: _.cloneDeep,
  debounce: _.debounce,
  throttle: _.throttle,
  size: _.size,
  pick: _.pick,
  isEmpty: _.isEmpty
}
// 注入到全局
import _ from ‘@helper/lodash.js’
Vue.prototype.$_ = _
// vue 组件内运用
this.$_.debounce()

尽可能引入更合适的包

作为前端开发的你,想必知道有 momentjs 的存在(Parse, validate, manipulate, and display dates in javascript.);更多的是,你想必知道它很好用,然而它的体态却十分丰满(丰盈),没念及此,是否有重新造轮子的冲动?SpaceTime: A lightweight way to manipulate, traverse, compare, and format dates and times across planet Earth。 具有与 monent 相似 api 的新类库,其体积又相对小很多(当然,据观察其灵活度略逊一筹);date-fns:现代JavaScript日期实用程序库( Modern JavaScript date utility library ),如 lodash 一样,可支持模块化;知道这些或者更多的你,会如何选择?

按需异步加载模块

关于前端开发优化,重要的一条是,尽可能合并请求及资源,如常用的请求数据合并,压缩合并 js,构造雪碧图诸此等等(当然得适当,注意体积,过大不宜);但,同时也当因需制宜,根据需要去异步加载,避免无端就引入早成的浪费。webpack 也是内置对这方面的支持; 假如,你使用的是 Vue,将一个组件(以及其所有依赖)改为异步加载,所需要的只是把:

JavaScript

1
import Foo from ‘./Foo.vue’

改为如下写法:

JavaScript

1
const Foo = () => import(‘./Foo.vue’)

如此分割之时,该组件所依赖的其他组件或其他模块,都会自动被分割进对应的 chunk 里,实现异步加载,当然也支持把组件按组分块,将同组中组件,打包在同个异步 chunk 中。如此能够非常有效的抑制 Javascript 包过大,同时也使得资源的利用更加合理化。

生产环境,压缩混淆并移除console

现代化中等规模以上的开发中,区分开发环境、测试环境和生产环境,并根据需要予以区别对待,已然成为行业共识;可能的话,还会有预发布环境。对待生产环境,压缩混淆可以很有效的减小包的体积;同时,如果能够移除使用比较频繁的 console,而不是简单的替换为空方法,也是精彩的一笔小优化。如果使用 UglifyJsPlugin 插件来压缩代码,加入如下配置,即可移除掉代码中的 console:

JavaScript

1
2
3
4
5
6
7
8
new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false,
    drop_console: true,
    pure_funcs: [‘console.log’]
  },
  sourceMap: false
})

Webpack3 新功能: Scope Hoisting

截止目前(17-08-06), Webpack 最新版本是 3.4.1;Webpack在 3.0 版本,提供了一个新的功能:Scope Hoisting,又译作“作用域提升”。只需在配置文件中添加一个新的插件,就可以让 Webpack 打包出来的代码文件更小、运行的更快:

JavaScript

1
2
3
4
5
module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

据悉这个 Scope Hoisting 与 Tree Shaking,最初都是由 Rollup 实现的。在个人中实践中,这个功能的注入,对打包体积虽有影响,却不甚明显,有兴趣的盆友可以试下;更对关于此功能讯息,可参见 Webpack 3 的新功能:Scope Hoisting

本文自本月(08)四号开始陆陆续续写,原本的内容意图是,涉及 Webpack 打包优化的体积和速度两个方面;岂料,临近写完的时候(06号晚),已记不得多久没关机的 Mac,竟然被重启了下;屋漏多半会偏逢连夜雨,那一向会自动同步保存(30min)的作业部落,竟然没给同步,WTF!整个周末的敲敲打打,皆付之东流,泪崩泪目😂。无奈之下,只得重新写过,直到夜深,才补齐关于体积优化那部分;毕竟涉及内容较多,干脆,就分成两部分来完成😪。也在此提醒广大笔友,及时备份数据并确认,这很重要😂。在此也预告下一篇 《Webpack 打包优化之速度篇》,当然,此文也扔在完善中。