Giter Site home page Giter Site logo

notebook's Introduction

issue.template

  • what 它是什么?
  • when 它在什么场景下发生?
  • why 它为什么会这样,有没有其他原因?
  • how 它是怎么做到的?

工欲善其事,必先利其器。

notebook's People

Contributors

msforest avatar

Watchers

 avatar  avatar

notebook's Issues

three 视角调整

resetCameraPos = (position = new THREE.Vector3(0, 0, 0)) => {
    this.controls.target.copy(position);
    this.controls3d.target.copy(position);

    const pos = new THREE.Vector3(
      PARAMS.camera.position.x,
      PARAMS.camera.position.y,
      PARAMS.camera.position.z,
    );
    this.camera.position.copy(position.add(pos));
    this.camera.updateProjectionMatrix();
    this.controls.update();
    this.controls3d.update();
  };

angular中使用指令封装通讯录

之前对js版的通讯录进行了一些修改,数据从静态改成动态加载显示,经过一番思考后很快就改完了。现在要将通讯录在angular中使用,考虑到数据也是动态加载,有点小麻烦。一一记录在此:
按照angular的**,操作DOM结构不应该写在controller里,应该用指令来进行封装,而复杂业务逻辑应该写服务中。
尝试用angular写的代码如下:
一个精简的页面ng-txl.html

<!DOCTYPE html>
<html ng-app='app'>
<head>
	<meta charset="utf-8">
	<title>ng-txl</title>
	<link rel="stylesheet" href="style.css">
	<script type="text/javascript" src="jquery-1.8.3.min.js"></script>
	<script type="text/javascript" src="angular.js"></script>
	<script type="text/javascript" src="jquery.charfirst.pinyin.js"></script>
	<script type="text/javascript" src="ng-txl.js"></script>
</head>
<body>
<div ng-controller="txlCtrl">
	<txl></txl>
</div>
</body>
</html>

然后是ng-txl.js,我将service,directive,controller写在一起了,当然,这是不可取的。

var app = angular.module('app',[]);

app.service('InitialSort', function(){
	this.sort = function(html){
		var Initials=$('.initials');
        var LetterBox=$('#letter');
        Initials.find('ul').append('<li>A</li><li>B</li><li>C</li><li>D</li><li>E</li><li>F</li><li>G</li><li>H</li><li>I</li><li>J</li><li>K</li><li>L</li><li>M</li><li>N</li><li>O</li><li>P</li><li>Q</li><li>R</li><li>S</li><li>T</li><li>U</li><li>V</li><li>W</li><li>X</li><li>Y</li><li>Z</li><li>#</li>');
        initials2(html);
        $(".initials ul li").click(function(){
            var _this=$(this);
            var LetterHtml=_this.html();
            LetterBox.html(LetterHtml).fadeIn();
            Initials.css('background','rgba(145,145,145,0.6)');
            setTimeout(function(){
                Initials.css('background','rgba(145,145,145,0)');
                LetterBox.fadeOut();
            },1000);

            var _index = _this.index()
            if(_index==0){
                $('html,body').animate({scrollTop: '0px'}, 300);//点击第一个滚到顶部
            }else if(_index==27){
                var DefaultTop=$('#default').position().top;
                $('html,body').animate({scrollTop: DefaultTop+'px'}, 300);//点击最后一个滚到#号
            }else{
                var letter = _this.text();
                if($('#'+letter).length>0){
                    var LetterTop = $('#'+letter).position().top;
                    $('html,body').animate({scrollTop: LetterTop-45+'px'}, 300);
                }
            }
        })

        var windowHeight=$(window).height();
        var InitHeight=windowHeight-45;
        Initials.height(InitHeight);
        var LiHeight=InitHeight/28;
        Initials.find('li').height(LiHeight);
	}
});

app.directive('initialSort', ['$compile', 'InitialSort', function($compile, InitialSort){
	return {
		restrict:'EC',
		template: `<div>
						<div id="letter" ></div>
						<div class="sort_box"></div>
						<div class="initials">
							<ul>
								<li><img src="img/068.png"></li>
							</ul>
						</div>
					</div>`,
		link:function(scope, element, attr){
			var html = [], item=null;
		    for(var i = 0,len = scope.chineseArr.length; i < len; i++){
		    	item = chineseArr[i];
		        html += `<div class="sort_list" ng-click=payee(${JSON.stringify(item)})><div class="num_name">${item.name}</div></div>`;
		        item = null;
		    }
			InitialSort.sort(html);
			$compile(element.contents())(scope);  //使用compile对dom进行再次编译
		}
	}
}]);
app.controller('txlCtrl', ['$scope', function($scope){
		$scope.chineseArr = [
			            {"id":1,name:"涨水"},
			            {"id":2,name:"美女"},
			            {"id":3,name:"准备"},
			            {"id":4,name:"请问"},
			            {"id":5,name:"水电费"},
			            {"id":6,name:"不能"},
			            {"id":7,name:"更好"},
			            {"id":8,name:"熬吧"},
			            {"id":9,name:"凉快了"},
			            {"id":10,name:"潍坊"},
			            {"id":11,name:"拉屎"},
			            {"id":12,name:"胸围"},
			            {"id":13,name:"漂亮"},
			            {"id":14,name:"离开"},
			            {"id":15,name:"额无法"},
			            {"id":16,name:"123"},
			            {"id":17,name:"+sdkl"},
			            {"id":18,name:"AB"}
			            ];
			$scope.payee = function(item){
				console.log(item);
			}
}]);

这里有用到es6的模板字符串,想想它比字符串拼接更简单,我就用了,因为正好我开始看es6方面的知识了。
以上代码是没有问题的,效果也是OK的,现在说说在编写directive的过程中遇到的问题:

一开始按照基本的指令编写方式完成,页面展示的效果也是没问题的,beautiful.但是click没有用,控制台没有打印我想要的东西,其实知道问题出现在哪儿,指令template的模板,然后在link函数对template进行动态修改dom结构,这时,使用的ng-click肯定是没有效果的,这需要对angular的指令生命周期要有一点了解,不然问题出现在哪儿都不知道,在link函数里动态添加的dom结构不在angular监听范围内,这个时候的dom结构几乎已经出现在真实页面中展示了,要想对link函数动态添加的dom结构使用ng的东西,需要对动态dom进行angular的强制编译,来达到在我的监管范围内的目的,即使用$compile。这时,ng-click就有效了,
到这一步的时候,控制台还有一点错误,和往常一样的写法ng-click=payee(item),这不会报错,但是item具体的对象值没有传递过来,而是传了'item'字符串,这我又不乐意了,于是,改成了ng-click=payee(${item}),控制台报错,这就好办了,有错误就代表有解决方案,没错误才是最危险的,报什么传递的是对象[object,object],不符合语法规范,然后在stackoverflow网上找到解决方案,就是上面所写的ng-click=payee(${JSON.stringify(item)}),这样问题就没了。


2016.11.30
今天又发现了一个问题,就是通讯录显示的数据不会随着数据的更新而更新,这算是一个很严重的问题,所幸发现的早,有更多的时间去解决,这里发解决方案记录一下:
指令的生成是根据chineseArr值来变化的,所以我们去监听chineseArr值的变化

link:function(scope, element, attr){
//对新旧值进行监听保证数据变化时,指令也要更新
scope.$watch('chineseArr',function(newValue,oldValue){
     if (newValue){
        $(element).find('.sort_box').html("");  //置空原有html 
        var html = [], item=null;
        for(var i = 0,len = scope.chineseArr.length; i < len; i++){
            item = scope.chineseArr[i];
            html += `<div class="sort_list" ng-click=payee(${JSON.stringify(item)})><div class="num_name">${item.name}</div></div>`;
            item = null;
        }
        InitialSort.sort(html, scope.chineseArr);
        $compile(element.contents())(scope);  //使用compile对dom进行再次编译
     }
});
}

一开始我一直在想怎么让指令重新渲染,问过一些人,也搜了网上,都是说用$compile重新编译,这种方法没用,郁闷了一小会。


2016-12-25
增加触摸字母列表滑动的功能,修改自定义服务

app.service('InitialSort', function(){
	this.sort = function(html, list){
		var Initials=$('.initials');
        var LetterBox=$('#letter');
        Initials.find('ul').append('<li>A</li><li>B</li><li>C</li><li>D</li><li>E</li><li>F</li><li>G</li><li>H</li><li>I</li><li>J</li><li>K</li><li>L</li><li>M</li><li>N</li><li>O</li><li>P</li><li>Q</li><li>R</li><li>S</li><li>T</li><li>U</li><li>V</li><li>W</li><li>X</li><li>Y</li><li>Z</li><li>#</li>');
        initials2(html, list);

        $(".initials ul li").on('click', function(e){
            var _this=$(this);
            var LetterHtml=_this.html();
            LetterBox.html(LetterHtml).fadeIn();

            Initials.css('background','rgba(145,145,145,0.6)');
            setTimeout(function(){
                Initials.css('background','rgba(145,145,145,0)');
                LetterBox.fadeOut();
            },1000);

            var _index = _this.index()
            if(_index==0){
                $('html,body').animate({scrollTop: '0px'}, 300);//点击第一个滚到顶部
            }else if(_index==27){
                var DefaultTop=$('#default').position().top;
                $('html,body').animate({scrollTop: DefaultTop+'px'}, 300);//点击最后一个滚到#号
            }else{
                var letter = _this.text();
                if($('#'+letter).length>0){
                    var LetterTop = $('#'+letter).position().top;
                    $('html,body').animate({scrollTop: LetterTop-45+'px'}, 300);
                }
            }
        });
        $('.initials ul').on('touchmove', function(event){
            event.preventDefault();
            var x = event.originalEvent.targetTouches[0].clientX;
            var y = event.originalEvent.targetTouches[0].clientY;
            var target = document.elementFromPoint(x,y);
            if(!target) return ;

            var LetterHtml=target.innerHTML,
                arr = '<img src="images/star.png">abcdefghijklmnopqrstuvwxyz#';
            if(arr.indexOf(LetterHtml.toLowerCase()) === -1) return ;

            console.log(LetterHtml)
            LetterBox.html(LetterHtml).fadeIn();

            if('<img src="img/068.png">' === LetterHtml){
                $('html,body').animate({scrollTop: '0px'}, 0);//点击第一个滚到顶部
            }else if('#' === LetterHtml){
                var DefaultTop=$('#default').position().top;
                $('html,body').animate({scrollTop: DefaultTop+'px'}, 0);//点击最后一个滚到#号
            }else{
                var letter = LetterHtml;
                if($('#'+letter).length>0){
                    var LetterTop = $('#'+letter).position().top;
                    $('html,body').animate({scrollTop: LetterTop-45+'px'}, 0);
                }
            }
        }).on('touchstart', function(e){
            Initials.css('background','rgba(145,145,145,0.6)');
        }).on('touchend', function(e){
            setTimeout(function(){
                Initials.css('background','rgba(145,145,145,0)');
                LetterBox.fadeOut();
            },500);
        });

        var windowHeight=$(window).height();
        var InitHeight=windowHeight-45;
        Initials.height(InitHeight);
        var LiHeight=InitHeight/28;
        Initials.find('li').height(LiHeight);
	}
});

代码在文件夹的ng命名的文件

It is not easy to meet each other in such a big world.- -世界这么大,能遇见,不容易。

transducer in javascript

了解transducer,首先要了解compose/pipe知识点。

compose / pipe

见名知义,两者都是对上一次的操作结果进行处理。区别是处理的方向是相反的,compose 是从右向左依次执行,pipe是从左向右依次执行。

var pipe = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
var compose = (...fns) => fns.reduceRight((f, g) => (...args) => g(f(...args)));

// 便于更好区分
var pipe = (...fns) => fns.reduceRight((f, g) => (...args) => f(g(...args)));
var compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

让我们实现一个函数对字符串进行各种变换

var getName = (person) => person.name
getName({ name: 'Buckethead' })  // 'Buckethead'

var uppercase = (string) => string.toUpperCase()
uppercase('Buckethead')  // 'BUCKETHEAD'

// 现在组合以上两个方法,可以这么实现
var name = getName({ name: 'Buckethead' })
uppercase(name)  // 'BUCKETHEAD'
// or
uppercase(getName({ name: 'Buckethead' }))

// 如果还要实现字符串截取
var get6Characters = (string) => string.substring(0, 6)
get6Characters('Buckethead')  // 'Bucket'

// 最终可能会写成这样
get6Characters(uppercase(getName({ name: 'Buckethead' }))) //'BUCKET'

以上代码看起来不怎么舒服

我们可以借助pipe/compose方法来编写让人赏心悦目的代码

pipe(
  getName,
  uppercase,
  get6Characters,
  reverse 
)({ name: 'Buckethead' })
// 'TEKCUB'

参考

线程 vs 进程

概念

进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。通俗点说,进程就是一个应用程序,线程是这个应用程序的各种交互。比如电脑卡死的时候,我们经常打开任务管理器,选择一个进程点结束,也就是关闭这个应用程序,系统释放应用程序所占用的资源。
image

进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

区别

线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而线程则可以与父进程下的其他线程共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。

进程是由操作系统进行调度和分配的最小单位
线程是由cpu进行调度和分配的基本单位

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  1. 一个程序至少有一个进程,一个进程至少有一个线程.
  2. 线程的划分尺度小于进程,使得多线程程序的并发性高。
  3. 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  4. 线程在执行过程中与进程也是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  5. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

throttle vs debounce

这两个概念一直都理不清楚,看过不少关于区分概念的文章,如JavaScript 函数节流和函数去抖应用场景辨析这篇文章,还是稀里糊涂滴:joy:,直到看到一句简单的英文句子才理解:

  • debouncing, executes the function if there was no new event in $wait milliseconds
  • throttle, in case of a "storm of events", this executes once every $threshold

我理解的意思是:

  • throttle,函数节流,每隔一定时间内执行一次event。
  • debounce,函数去抖,在一定时间内,若event不再触发,就在wait时间之后执行指定function。

可以参考特效

async2.6.1源码分析之parallel

分析参数为数组的情况

// entry
function parallel(tasks, callback) {  // parallelLimit -> parallel
    _parallel(eachOf, tasks, callback);
}

function _parallel(eachfn, tasks, callback) {
    callback = callback || noop;
    var results = isArrayLike(tasks) ? [] : {};

    eachfn(tasks, (task, key, taskCb) => {  // task如果是异步的,会被wrapAsync同步化
        wrapAsync(task)((err, ...result) => {
            if (result.length < 2) {
                [result] = result;
            }
            results[key] = result;
            taskCb(err);
        });
    }, err => callback(err, results));
}

function eachOf(coll, iteratee, callback) {
    var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
    eachOfImplementation(coll, wrapAsync(iteratee), callback);
}

function eachOfArrayLike(coll, iteratee, callback) {
    callback = once(callback || noop);
    var index = 0,
        completed = 0,
        {length} = coll,
        canceled = false;  // 表示任务队列是否撤销,嗯,用撤销更好理解
    if (length === 0) {
        callback(null);
    }

    function iteratorCallback(err, value) {
        if (err === false) {
            canceled = true
        }
        if (canceled === true) return   // 退出执行流程,这里的退出不是指执行async.parallel([], callback)里的callback,而是撤销async.parallel()
        if (err) {
            callback(err);  // 若某个task中间有错误,提前退出parallel,已经发起的iteratee的异步任务可能会被执行,但是最后的result只保存了已经执行过的结果
        } else if ((++completed === length) || value === breakLoop) {
            callback(null);
        }
    }

    for (; index < length; index++) {
        iteratee(coll[index], index, onlyOnce(iteratorCallback));   // 重点,只是利用js的异步机制,如果函数没有异步操作,执行还是串行的
    }
}

并行和并发的区别

看过代码之后,才发现不是真正意义上的parallel

parallel功能和Promise.all方法一样

css 知识点

css3 flex实例

css 选择器

getComputedStyle


选择器

  1. 分组
    对不同的标签使用同一个规则
div, span, a, input{
    font-size: 24px;
}
  1. 类选择器/ID选择器

列出一些特别的表达式

.class1.class2{     // 仅表示选择同时包含这些类名的元素(类名的顺序不限)
    color: red;
}

.class1 .class2{    // 同等于后代选择器
    color: red;
}

.class1, class2{    // 同1:分组
    color: red;
}
  1. 属性选择器
img[src]{
    content: attr(alt);
}

img[src="1.png"]{
    content: attr(alt);
}

img[src~="1.png"]{  // src值包含1.png
    content: attr(alt);
}

img[src^="1.png"]{  // 以1.png开头
    content: attr(alt);
}

img[src$="1.png"]{  // 以1.png结尾
    content: attr(alt);
}

img[src*="1.png"]{  // 包含1.png
    content: attr(alt);
}
  1. 后代选择器
div span{       // div元素下的所有span,不管span元素属于第几代
    color: red;
}

div > span{     // div元素的子span元素
    color: red;
}

h1 + p{         // 选择相邻兄弟元素
    color: red;
}

选择器的特殊性

选择器的特殊性分为四个等级:a,b,c,d.

  • 若样式为行内样式,则a=1;
  • b等于ID选择器的总数;
  • c等于类、伪类和属性选择器的数量;
  • d等于类型选择器和伪元素选择器的数量。

使用这些规则可以计算出任何css选择器的特殊性:

选择器 特殊性 以10为基数的特殊性
style='' 1,0,0,0 1000
#id #id1 0,2,0,0 200
#id .class 0,1,1,0 110
div#id 0,1,0,1 101
#id 0,1,0,0 100
p.class .class1 0,0,2,1 21
p.class 0,0,1,1 11
div p 0,0,0,2 2
p 0,0,0,1 1

基本上,用style属性编写的规则总是比其他任何规则特殊,具有ID选择器的规则总是比没有ID选择器的规则特殊,具有类选择器的规则比只有类型选择器的规则特殊。最后,如果两个规则的特殊性相同,那么后定义的规则优先。
综合所有规则:important>行内样式>id>class>标签>通用样式

mongodb聚合,日志

  1. 聚合
  2. 问题
  3. journal

导入导出

导出单个表: mongoexport -d dbname -c collectionname -o file -q '{a: 1}'
导入单个表: mongoimport -d dbname -c collectionname --file filename 
	数据添加:mongoimport -d dbname -c collectionname < filename
导出数据库: mongodump -h dbhost -d dbname -o dbdirectory
导入数据库: mongorestore -h dbhost -d dbname --dir dbdirectory

查看服务器的命令行参数

// mongo shell > db.serverCmdLineOpts()

{
	"argv" : [
		"/usr/local/opt/mongodb/bin/mongod",
		"--config",
		"/usr/local/etc/mongod.conf"
	],
	"parsed" : {
		"config" : "/usr/local/etc/mongod.conf",
		"net" : {
			"bindIp" : "127.0.0.1"
		},
		"storage" : {
			"dbPath" : "/usr/local/var/mongodb"
		},
		"systemLog" : {
			"destination" : "file",
			"logAppend" : true,
			"path" : "/usr/local/var/log/mongodb/mongo.log"
		}
	},
	"ok" : 1
}

// 查看当前mongo一次允许插入最大的bson大小
> db.isMaster().maxBsonObjectSize/(1024*1024)+' MB'

// 查看一个collection的bson大小
> Object.bsonsize(db.test.findOne())

async2.6.1源码分析之asyncify

使同步函数变成异步函数操作,result通过callback传递出去

export default function asyncify(func) {
    if (isAsync(func)) {    // 判断是否是async/await函数
        return function (...args/*, callback*/) {
            const callback = args.pop()
            const promise = func.apply(this, args)
            return handlePromise(promise, callback)
        }
    }

    // 在func外层包了一层函数,使得func被延长演出的时间
    return initialParams(function (args, callback) {
        var result;
        try {
            result = func.apply(this, args);
        } catch (e) {
            return callback(e);
        }
        // if result is Promise object
        if (result && typeof result.then === 'function') {  // 判断是否是promise函数
            return handlePromise(result, callback)
        } else {
            callback(null, result);  // sync函数还是sync,并没有async
        }
    });
}

// 处理promise的结果,将result通过callback传出去,而不是then;保证了对外统一的接口
function handlePromise(promise, callback) {
    return promise.then(value => {
        invokeCallback(callback, null, value);
    }, err => {
        invokeCallback(callback, err.message ? err : new Error(err));
    });
}

function invokeCallback(callback, error, value) {
    try {
        callback(error, value);
    } catch (err) {
        setImmediate(e => { throw e }, err);
    }
}

// import initialParams from './internal/initialParams';
export default function initialParams(fn) {
    return function (...args/*, callback*/) {
        var callback = args.pop();
        fn.call(this, args, callback);
    };
}

处处皆闭包,处处皆尾调用

React HelloWorld

1. React库加载

2. react 知识碎片

3. react Lifecycle

4. react router

React库加载

<script src="https://cdn.bootcss.com/react/15.6.1/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.6.1/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-core/5.8.38/browser.min.js"></script> //6.0以上使用会报错,刚接触React,原因不明
<script type="text/babel"></script>

//以下是官方提供的,加载时间不如上面的
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>

学习资料:
facebook's React
阮一峰React技术栈

js编码规范

编写可维护的代码
项目维护是由不同的人负责的,要想做到别人也能看懂你的代码,需要互相遵循一些成形的编码规范,这样才能做到降低你我他的成本,毕竟不可能一直从事一个项目,一旦转移到新的项目上,很容易会忘记之前的代码,可维护的代码意味着具有如下特性:

  • 阅读性好
  • 具有一致性
  • 预见性好
  • 看起来如同一个人编写
  • 有文档

接下来一一细说:

1. 尽量少用全局变量

JavaScript使用函数管理作用域。变量在函数内声明,只在函数内有效,不能在外部使用。全局变量与之相反,在函数外部声明,在函数内部无需声明即可简单地使用。

每一个JavaScript环境都有全局对象,可在函数外部使用this进行访问。创建的每一个全局变量都为全局对象所有。在浏览器中,为了方便,使用window表示全局对象本事。

myglobal = 'hello';
console.log(myglobal);//hello
console.log(window.myglobal);//hello
console.log(window['myglobal']);//hello
console.log(this.myglobal); //hello

1.1 全局变量的问题

全局变量的问题在于它们整个JavaScript应用或web页面内共享。它们生存于同一个全局命名空间内,总有可能发生命名冲突。譬如当一个应用程序中两个独立的部分定义了同名的全局变量,但却有不同的目的时。
网页经常会包含一些非页面开发人员编写的代码,譬如:

  • 第三方JavaScript库
  • 来自于广告合作伙伴的脚本
  • 来自于第三方用户的跟踪与分析脚本的代码
  • 各种小工具、徽章和按钮

为了与同一个页面上的其他脚本友好共存,要尽可能少地使用全局变量。要想最小化控制全局变量的数量,可以使用命名空间模式或只执行立即生效函数,最重要也是最常用的还是使用var声明变量。

JavaScript总是在不知不觉中出人意料地创建了全局变量,其原因在于JavaScript的两个。第一个特性是JavaScript可直接使用变量,无需声明。第二个特性是JavaScript有个暗示全局变量的概念,即任何变量,未经声明,就为全局对象所有。

function sum(x,y){
  result = x + y;
  return result;
}

在这个例子中,result未经声明就使用了。代码虽然在一般情况下可以正常工作,但如果在调用该函数后,在全局命名空间使用了另外的result变量,问题就会出现。

首要的规则就是用var声明变量,正如改善后的sum()函数所示:

function sum(x,y){
  var result = x + y;
  return result;
}

另外一种创建隐式全局变量的反模式是带有var声明的链式赋值。在下面的代码片段中,a是局部变量,b是全局变量,这也许并不是你想要的。

function foo(){
  var a = b = 0;
    ...
}

一切都是因为从右至左的操作符优先级的原因。首先,优先级较高的表达式b=0,此时b未经声明。表达式返回值为0,它被赋给var声明的局部变量a,如var a = (b = 0);如果对链式赋值的所有变量都进行了声明,就不会创建出不期望的全局变量。例如:

function foo(){
  var a,b;
  a = b = 0; //此时均为局部变量
}

1.2 变量释放时的副作用

隐含全局变量与明确定义的全局变量有细微的不同,不同之处在于能否使用delete操作符撤销变量。

  • 使用var创建的全局变量不能删除
  • 不使用var创建的隐含全局变量可以删除

这表明隐含全局变量严格来讲不是真正的变量,而是全局对象的属性。属性可以通过delete操作符删除,但变量不可以。

var a = 1;
b = 2;
function foo(){
  c = 3;
}

delete a; //false
delete b; //true
delete c; //true

在es5 strict模式中,为没有声明的变量赋值会抛出错误。

2. 单一var模式(Single var Pattern)

只使用一个var在函数顶部进行变量声明是一种非常有用的模式。它的好处在于:

  • 提供一个单一的地址以查找到函数需要的所有局部变量
  • 防止出现变量在定义前就被使用的错误逻辑
  • 帮助牢记要声明变量,以尽可能少地使用全局变量
  • 更少的编码

单一var模式如下所示:

function func(){
  var a = 1,
    b = 2,
    sum = a + b,
    myobject = {},
    i,
    j;
  //...
}

使用一个var关键字声明由逗号分割的多个变量。在声明变量的同时初始化变量,为变量赋初值也是一种好的作风。这样可以防止逻辑错误,也可提高代码的可读性。当你以后重新看这段代码时,你可以根据变量的初始值知道使用这些变量的意图。比如,它应该是一个对象还是一个整型?

在声明变量时也可能做些实质性的工作,比如上述代码中的sum = a + b,另一个例子是dom(文档对象模型)的引用。如下面代码中所示,使用单一var声明将DOM引用赋给局部变量。

function foo(){
  var el = document.getElmentById('id'),
  style = el.style;
  //...
}

2.1 提升:零散变量的问题

JavaScript允许在函数的任意地方声明多个变量。无论在哪里声明,效果都等同于在函数顶部声明。这就是所谓的==提升==。当先使用变量再在函数后面声明变量时可能会导致逻辑错误。对JavaScript而言,只要变量是在同一个范围里,就被视为已经声明,哪怕是在变量声明前就使用。如下例子:

myname = 'global';
function func(){
  console.log(myname); //undefined
  var myname = 'local';
  console.log(myname); //local
}
func();

在这个例子中,会想当然的认为第一个为'global',第二个为'local',这是一个合乎情理的期望。但事实上并不是这样,所有的变量声明都会提升到函数的顶部。因此,为了避免这类混乱,最好在开始就声明要用的所有变量。
上面的例子与下面的效果一样:

myname = 'global';
function func(){
  var myname;
  console.log(myname);
  myname = 'local';
  console.log(myname);
}

3. for循环

for循环经常用于遍历数组或数组对象。通用模式如下:

for(var i = 0; i < myarray.length; i++){
  //...
}

这种模式的问题在于每次循环迭代时都要访问数据的长度,会使性能变慢,特别是当myarray不是数据,而是HTML容器对象时。
使用如下模式将长度缓存起来,会使性能得到更大的提升:

for(var i = 0, max = myarray.length; i < max; i++){
  //...
}

对于循环的最后一个改进是,用i++代替以下两种表达式:

i = i + 1;
i += 1;

4.避免使用隐式类型转换

JavaScript在使用比较语句时会执行隐式类型转换,这也是为什么执行false==0' ' == 0这类比较语句后返回true。
为了避免隐式类型转换导致的混淆不清,请在使用比较语句使用===!==操作符进行比较。

5.避免使用eval()

如果在代码中看到使用了eval(),请牢记一句俗话,“eval()是一个魔鬼”。该函数可以将任意字符串当做一个JavaScript代码来执行。当需要讨论的代码是预先就编写好了,就没有理由需要使用eval()。而如果代码是在运行时动态生成的,则也有其他更好的方法来代替eval()实现其功能。
大部分情形下,使用setInterval()、setTimeout()和function()等构造函数来传递参数,会导致类似eval()的隐患。

//反模式
setTimeout('myFunc()',1000);
setTimeout('myFunc(1,2,3)',1000);

//推荐模式
setTimeout(myFunc,1000);
setTimeout(function(){
  myFunc(1,2,3);
},1000);

6.使用parseInt()的数值约定

通过使用parseInt(),可以从一个字符串中获取数值。该函数的第二个参数是一个进制参数,通常可以忽略该参数,但是最好不要这样做,因为当解析的字符串是0开始就会出现错误。在es3版本中,0开始的字符串会被当做一个八进制,而在es5版本中发生了改变。为了避免不一致性和未预期,请每次都指定进制参数:

var month = '06',
  year = '09';
  month = parseInt(month, 10);
  year = parseInt(year, 10);

在本例中,如果忽略了进制参数,使用类似parseInt(year)的方法,那么返回值将是0,因为‘09’会当做一个八进制处理,而09在八进制中不是一个合法的数值。

另外一个将字符串转换为数值的方法是:

+'08'; //8
Number('08'); // 8

这种方法通常会比parseInt()快很多,因为正如其名称一样,parseInt()是解析而不是简单的转换。但是如果希望输入‘09 hello’会返回一个数值,那么除parseInt()之外,其他方法都会返回NaN。

7. 编码约定

遵循编码约定是非常重要的,这可以使得代码更为一致、可预测、更容易阅读和理解。一个新加入团队的开发者通过了解编码约定,可以很快提高工作效率,理解团队其他人员编写的代码。

7.1 缩进

没有缩进的代码是很难阅读的。如果使用了不一致的缩进,也是很令人头疼的事情。因为这样的做法看起来遵循了约定,但是会导致很多令人困惑的疑问。标准化使用缩进is very important.

7.2 大括号

应该经常使用大括号,甚至在可选的情况下,都请使用大括号。技术上,在if语句和for语句中如果仅有一行语句,可以不需要大括号,但是为了一致性和更方便升级,最好还是使用大括号。

7.3 空格

使用空格也有助于改善代码的可读性和一致性。在列表表达式和语句结束后面添加空格。

7.4 命名约定

另一个提高代码可预测性和可维护性的方法是使用命名约定。这就意味着采用一致的方法来对变量和函数进行命名。

  • 构造函数首字母大写
  • 全局常量使用单词大写
  • 编写注释

redux 源码解读

// store.js
const initialState = {};
const middlewares = [thunk, logger];

export default createStore(reducers, initialState, compose(applyMiddleware(...middlewares)));
// 若有中间件,则返回一个包含全局store和dispatch属性的object,此dispatch!==store.dispatch
// 否则,只返回全局的store


// App.js
ReactDOM.render(
    <Provider store={store}>
        <Routes/>
    </Provider>,
    document.getElementById('root')
);


// 以下为结合以上例子,redux和中间价分析
import {createStore, applyMiddleware, compose, combineReducers} from 'redux'

// M1
public function combineReducers(reducers: object)
    // => 1. 先检查所有reducer是否都真实存在
    // 2. 返回一个闭包函数`(state,action)=>{}`作为createStore的第一个参数, 此函数作用是依次执行所有有效的reducer,返回一个新的state,即store.getState的值;若所有reducers返回的action是默认值,则返回上一个state。

// M2
pulic function compose(a: function, b: function, c: function, ...)
    // => 返回一个闭包函数作为createStore的第三个参数,此函数等于`(...args) => a(b(c(...args)))`

// M3
public function applyMiddleware(...middleware)
    // => 返回一个闭包函数作为compose的返回值,也就是createStore的第三个参数,此函数等于`(createStore) => (reducer, preloadState) => ...middleware() `,依次执行中间件,返回一个新的dispatch,
    // || 也可直接作为createStore的第三个参数,返回包含state和新的dispatch属性

// M4
public object createStore(reducer: function, preloadState: object, enhance: function)
    // => store {
    //    dispatch: 接受一个action参数,用于执行M1中所有的reducers,若上一次action的reducers还在执行中,下一次action紧跟着执行会报错,不允许同时执行两次action去修改state,所以reducers要尽可能的简单,不要执行过于复杂、耗时的代码;reducers执行完毕后,立即执行substribe注册的监听器;最后返回相同的action,用于下一个中间件处理当前action,**此action对象要有一个type属性**
    //    getState: 返回当时的state
    //    subscribe: 注册监听器
    //    replaceReducers: 顾名思义,替代已传入的reducer
    //}

// M5
public function|object bindActionCreators(actionCreators: function|object, dispatch)
    // => 若返回一个闭包函数,() => dispatch(actionCreator.apply(this, arguments))
    // 若返回一个对象,方法全是上面的闭包函数


import thunk from 'redux-thunk'

// M6
function createThunkMiddleware(extraArgument) {
    // {dispatch, getState} 来自于applyMiddleware对每个中间件的遍历传递
    // next来自于store.dispatch
    return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
            // 调用action参数的dispatch, 是由applyMiddleware生成的,不等于next
            return action(dispatch, getState, extraArgument);
        }

        return next(action);
    };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

image

http/2 - High Performance Browser Networking

http/2

翻译自:http2

http2使web应用程序变得更快、更轻量、更强壮。更好的是,它还为优化我们的应用程序和提高性能开辟了许多全新的机会!

http/2主要目标:为了减少延迟,开启multiplexing;使用头部压缩,优化传输协议的负载量;支持请求优先级和服务器推送功能。为了实现这些功能,加入了许多其他的增强协议,如new flow control, error handling, and upgrade mechanisms;最关键的一点还是我们在实践中如何理解和使用这些功能。

http/2不改变原有的http语义,所有核心概念都保留,像method, status codes, URIs and header。http/2仅仅修改数据在C/S之间如何转换和传输,管理中间的整个过程,使用一个新的层来隐藏所有的复杂度。这样,对于现有的应用程序就不要任何修改,对于使用者来说,这是非常棒的好消息。

我们不应该只是对提供有效的应用程序感兴趣;我们的目标是提供最佳性能!http2开发了大量的优化功能,我们的工作就是充分利用好这些。

why not http/1.2?

为了实现HTTP工作组设置的性能目标,HTTP/2引入了一个新的二进制成帧层,它与以前的HTTP/1.x服务器和客户端不兼容, 因此主要协议版本增加到HTTP/2

也就是说,除非您通过使用原始TCP套接字实现Web服务器(或自定义客户端),否则您将看不到任何区别:所有新的低级框架都由客户端和服务器代表您执行. 唯一可观察到的差异将是提高性能和新功能的可用性,如请求优先级,流量控制和服务器推送.

Brief History of SPDY and HTTP/2

spdy是一个试验性协议,由Google开发并于2009年中发布,它的目的是为了减少web加载时延,实现目标大纲如下:

  • 目标是减少50%的页面加载时间
  • 网站作者无需对内容进行任何更改
  • 最大限度地降低部署复杂性,避免网络基础架构的变化
  • 与开源社区合作开发此新协议
  • 收集真实的性能数据以(验证)实验协议

Design and Technical Goals

HTTP/0.9是一个引导万维网的单线协议; HTTP/1.0记录了信息标准中HTTP/0.9的流行扩展; HTTP/1.1引入了官方的IETF标准;因此,HTTP/0.9-1.x完全按照它的目的提供:HTTP是互联网上最普遍和广泛采用的应用程序协议之一。

不幸的是,简单的实现是以应用程序性能为代价的:http/1.x使用多个连接来实现并发和减少时延;而且,请求响应信息头在多次传输过程中是重复发送;也没有高效的优先级策略等,所以就导致了tcp连接性能低下。

对于简单的应用程序,那些缺陷还不足以影响使用;但随着应用程序的扩大、复杂度的提高,使用http/1.x就暴露了一些问题。这也是http/2设计的意图:

HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection… Specifically, it allows interleaving of request and response messages on the same connection and uses an efficient coding for HTTP header fields. It also allows prioritization of requests, letting more important requests complete more quickly, further improving performance.

The resulting protocol is more friendly to the network, because fewer TCP connections can be used in comparison to HTTP/1.x. This means less competition with other flows, and longer-lived connections, which in turn leads to better utilization of available network capacity. Finally, HTTP/2 also enables more efficient processing of messages through use of binary message framing.

大体意思是http/2采用单个连接通道下使用头部压缩、多路复用、异步发送request/response、允许设置优先级等功能,使用二进制消息帧高效处理message。这一过程,减少了tcp连接数,二进制传输等解决了http/1.x存在的一些缺陷。

http/2没有替代http/1.x,同样的语义、同样的功能、同样的概念;即API用法不变,重要的是理解底层的一些变化是如何提升性能的。

先了解一下二进制帧层。

Binary Framing Layer

HTTP/2的所有性能增强的核心是引入了新的二进制成帧层,下图显示了message是如何在C/S之间进行包装和传输的。
image

选择在套接字接口和HTTP的API之间引入新的优化编码机制�,是为了保持原有�的使用不变,如:http1/x词法、方法、头信息毫无变化,变化的是以一种被转码的方式进行传输。所有http/2交互信息被拆分成被二进制格式转码的message和frame,而http/1.x是以换行符分隔的明文传输。

所以,C/S之间必须使用新的二进制转码机制来解析传输数据,庆幸的是,C/S已经帮我们做好了这些工作

二进制协议的优缺点

ASCII协议容易使用和检测。但是正确的解析实现是很难的、效率低下:因为可选的空格,不同的终止序列和其他怪癖使得很难区分有效负载的协议,然后就导致安全解析问题。相反,使用二进制协议会带来更高性能、更强大和正确的实现。

Streams, Messages, and Frames

新的二进制帧机制改变了C/S之间数据交换方式。要描述这个过程,让我们熟悉几个关键术语:

Stream
建立连接内的双向字节流,可以携带一个或多个消息

Message
映射到逻辑请求或响应消息的完整帧序列

Frame
http/2最小的通信单位,每个单位包含一个帧头,它至少有一个所属的Stream ID

1. 所有通信都通过单个TCP连接执行,该连接可以承载任意数量的双向流(Stream)。
2. 每个流都有唯一的标识符和可选的优先级信息,用于携带双向消息(Message)
3. 每条message都是一条逻辑HTTP消息,例如请求或响应,它由一个或多个帧组成
4. 帧是承载特定类型数据的最小通信单元,例如HTTP标头,消息有效负载等。来自不同流的帧可以被交错发送,然后通过每帧的流标识符重新组装。

image

简而言之,HTTP/2将HTTP协议通信分解为可交换的二进制帧,然后将其映射到特定流的消息中,并且所有这些帧都在单个TCP连接中复用。这是支持HTTP/2协议提供的所有其他功能和性能优化的基础。

Request and Response Multiplexing

使用HTTP/1.x,如果客户端想要进行多个并行请求以提高性能,则必须使用多个TCP连接;此行为是HTTP/1.x传递模型的直接结果,它确保每个连接一次只能传递一个响应(响应排队)。这就导致线头阻塞和底层TCP连接的低效使用。

HTTP/2中的新二进制成帧层消除了这些限制,并允许客户端和服务器将HTTP消息分解为独立的帧,交错传输,然后重新组合它们,从而实现完整的请求和响应多路复用。
image

图显示了同一个连接中正在传输的多个流:客户端正在向服务器发送DATA帧(流5),而服务器正在把流1和3以乱序的帧序列向客户端发送。因此,有三个并行流在传输。

将HTTP消息分解为独立的帧,交错发送,然后在另一端重新组装是HTTP/2最重要的增强功能。这种机制在所有Web技术的整个堆栈中引入了众多性能优势的连锁反应,使我们能够:

  • 并行交错多个请求而不阻塞任何一个请求
  • 并行交错多个响应而不阻塞任何一个响应
  • 使用单个连接并行提供多个请求和响应
  • 删除不必要的HTTP/1.x变通方法,例如图像精灵和域名分片
  • 通过消除不必要的延迟并提高可用网络容量的利用率,缩短页面加载时间

HTTP/2中的新二进制成帧层解决了HTTP/1.x中发现的行头阻塞问题,并消除了因并发请求打开多个连接的需要。因此,这使我们的应用程序更快,更简单,部署成本更低。

Stream Prioritization

一旦HTTP消息可以被分成许多单独的帧,并且我们允许来自不同流的帧被多路复用,那么客户端和服务器识别帧的顺序成为关键的性能考虑因素。为了实现这一点,HTTP/2标准允许每个流具有相关的权重和依赖关系:

  • 可以为每个流分配1到256之间的整数权重
  • 可以给每个流明确依赖另一个流

流依赖性和权重的组合允许客户端构造和传递“prioritization tree”,该树表示它希望如何接收响应。反过来,服务器可以使用此信息控制cpu,内存和其他资源的分配来确定处理流的优先级,而且一旦响应数据可用,就分配带宽确保向客户端最佳响应。
image

HTTP/2通过引用另一个流的唯一标识符作为其父级来声明流依赖性;如果省略,则称该流依赖于“root stream”。流依赖性表示,应该在其依赖性之前为父流分配资源, 例如,响应C之前需先处理并传递响应D.

共享相同父级(即兄弟级流)的流应按其权重的比例分配资源。例如,如果流A的权重为12,而其中一个兄弟B的权重为4,那么要确定每个流应该接收的资源的比例:

  1. 权重和:4+12=16
  2. 每个流的权重除以总权重:A=12/16, B=4/16

因此,A分配3/4,B分配1/4的可用资源;现在我们来依次分析上图中例子,从左到右:

  1. A和B没有明确的依赖关系,即它们被依赖一个隐式“root stream”;A的权重是12,B的权重是4,因此,B应该被分配A资源的1/3。
  2. D依赖于“root stream”, C依赖于D;因此,D应该在C之前接收完整的资源分配。权重是无关紧要的,因为依赖性的优先级高于权重。
  3. D应该在C之前获得全部资源分配; C应该在A和B之前获得全部资源分配;流B应该接收分配给流A的资源的三分之一
  4. D应该在E和C之前获得全部资源分配; E和C应在A和B之前获得相等的分配; A和B应根据其权重获得比例分配。

如上面的示例所示,流依赖性和权重的组合为资源优先级提供了一种策略,这是用于改进浏览器性能的关键特征,其中不同的资源类型有不同的依赖性和权重。更好的是,HTTP/2协议还允许客户端在任何时候更新这些首选项,这样可以在浏览器中进一步优化。例如,我们可以改变依赖性并重新分配权重以响应用户交互和其他信号。

PS: 流依赖性和权重表示传输偏好,而不是必须存在的一种策略,因此不保证特定的处理或传输顺序。也就是说,客户端不能强制服务器使用流优先级按特定顺序处理流。虽然这看似违反直觉,但事实上它是理想的行为:如果阻止了更高优先级的资源,我们不希望阻止服务器在较低优先级资源上取得进展。

Browser Request Prioritization and HTTP/2

在浏览器中呈现页面时,并非所有资源都具有相同的优先级: HTML文档对于构建DOM至关重要;CSS是构建CSSOM所必需的;而JavaScript资源上是可以阻止DOM和CSSOM构造;并且通常以较低优先级获取诸如图像之类的剩余资源。

为了加快页面的加载时间,所有现代浏览器根据资源类型、页面上的位置,甚至先前访问了解的优先级来确定请求的优先级;例如,如果在先前访问中某个资源上的渲染被阻止,那么同一资源在未来可能会被优先考虑更高。

使用HTTP/1.x,浏览器对于有优先级数据的能力有限:协议不支持多路复用,并且无法将请求优先级传递给服务器。相反,它必须依赖于并行连接的使用,这使得每个源最多可以有六个请求的有限并行性。因此,请求在客户端上排队,直到连接可用,这会增加不必要的网络延迟。HTTP Pipelining解决了客户端部分并发请求的问题,没有解决服务器并发响应的问题。

HTTP/2解决了这些低效问题:消除了请求排队和线头阻塞,因为浏览器可以在发现所有请求时调度它们,并且浏览器可以通过流依赖性和权重来传达其流优先级首选项,从而允许服务器进一步优化响应交付。

One Connection Per Origin

使用新的二进制帧层机制,HTTP/2不再需要多个TCP连接来并行多路复用流;每个流被分成许多帧,可以乱序发送和设置优先级。因此,所有HTTP/2连接都是持久的,并且每个源只需要一个连接,这提供了许多性能优势。

对于SPDY和HTTP/2,它们的杀手级特性是在单拥塞控制信道上的任意多路复用。

大多数HTTP传输都是短而突发的,而TCP则针对长期的批量数据传输进行了优化。通过重用相同的连接,HTTP/2能够更有效地使用每个TCP连接,并且还可以显着降低整体协议开销。此外,使用更少的连接减少了完整连接路径(即客户端,中介服务器和源服务器)的内存和处理占用空间,这降低了总体操作成本并提高了网络利用率和容量。因此,迁移到HTTP/2不仅应该减少网络延迟,还应该有助于提高吞吐量并降低运营成本。

PS: 减少连接数是提高HTTPS部署性能的一项特别重要的功能:这可以转化为更少的昂贵TLS握手,更好的会话重用以及所需客户端和服务器资源的整体减少。

Packet Loss, High-RTT Links, and HTTP/2 Performance

等等,我们列出了同一个域名使用一个TCP连接的好处,难道就没有一点点缺点吗?是的,人无完人 😱

  • 我们已经从HTTP中消除了线头阻塞,但在TCP级别仍然存在线头阻塞
  • 如果禁用TCP窗口缩放,带宽延迟产品的影响可能会限制连接吞吐量。
  • 发生数据包丢失时,TCP拥塞窗口大小会减小,这会降低整个连接的最大吞吐量。

此列表中的每个清单都可能会对HTTP / 2连接的吞吐量和延迟性能产生负面影响。然而,尽管有这些限制,转向多个连接将导致其自身的性能权衡:

  • 由于不同的压缩上下文而导致的头压缩效率较低
  • 由于不同的TCP流导致请求优先级不太有效
  • 由于更多竞争流量,每个TCP流的利用率较低,拥塞的可能性较高
  • 由于更多TCP流量导致资源开销增加

上述优点和缺点不全面,然而在构建特定场景,其中一个或多个连接可能被证明是有益的。但是,在部署HTTP/2的实验证据表明,单个连接是首选的部署策略:

到目前为止,在测试中,压缩和优先级的好处超过了线头阻塞的负面影响(特别是在存在丢包的情况下)。-- Hypertext Transfer Protocol version 2, Draft 2

与所有性能优化过程一样,当您消除一个性能瓶颈时,您将解锁下一个性能瓶颈。在HTTP/2的情况下,TCP可能就是这样。这就是为什么服务器上经过良好调整的TCP堆栈再次成为HTTP/2的关键优化标准的原因。

目前正在进行研究以解决这些问题并提高TCP性能:TCP快速开放,增加初始拥塞窗口等等。话虽如此,重要的是要承认HTTP/2与其前身一样,并不强制要求使用TCP。其他传输,例如UDP,在我们展望未来时并不属于可能性范围。

Flow Control

流量控制是一种机制,可以防止发送方丢弃或不处理来自接收方的数据:接收方可能忙,负载过重,或者可能只愿意为特定的资源分配固定数量的资源流。例如,客户端可能已经请求具有高优先级的大视频流,但是用户已暂停视频,并且客户端现在想要暂停或限制其从服务器的传递以避免获取和缓冲不必要的数据。或者,代理服务器可以具有快速下游和慢速上游连接,并且类似地希望调节下游传送数据的速度以匹配上游速度以控制其资源使用;等等。

上述要求是否想起TCP流量控制?参见流量控制
但是,由于HTTP/2流在单个TCP连接中进行多路复用,因此TCP流控制不够精细,并且不提供必要的应用程序级API来管理各个流的传送。为了解决这个问题,HTTP/2提供了一组简单的构建块,允许客户端和服务器实现自己的流和连接级流控制:

  • 流量控制是方向性的。每个接收器可以选择为每个流和整个连接设置所需的任何窗口大小。
  • 流量控制是基于信用的。每个接收器通告其初始连接和流流控制窗口(以字节为单位),每当发送器发出DATA帧并通过接收器发送的WINDOW_UPDATE帧递增时,该窗口就会减少。
  • 无法禁用流量控制。建立HTTP / 2连接后,客户端和服务器交换SETTINGS帧,从而在两个方向上设置流​​控制窗口大小。流控制窗口的默认值设置为65,535字节,但接收器可以设置较大的最大窗口大小(2^31 - 1字节),并在收到任何数据时通过发送WINDOW_UPDATE帧来维护它。
  • 流控制是逐跳的,而不是端到端的。也就是说,中间商可以使用它来控制资源使用并基于自己的标准和启发式实现资源分配机制。

HTTP/2没有指定实现流控制的任何特定算法。相反,它提供了简单的构建块,并将实现推迟到客户端和服务器,客户端和服务器可以使用它来实现自定义策略来规范资源使用和分配,以及实现可以帮助提高实际和感知性能的新交付功能。

例如,应用层流量控制允许浏览器仅获取特定资源的一部分,持续获取直到stream减少到零,然后稍后恢复;例如,获取预览或首先扫描图像,显示它并允许其他高优先级提取继续进行,并在更多关键资源完成加载后恢复提取。

Server Push

HTTP/2另一个强大的特性是服务器能够为单个客户端请求发送多个响应。也就是说,除了对原始请求的响应之外,服务器还可以将其他资源推送到客户端,而客户端不必明确请求每个资源!
image

PS: HTTP/2摆脱了请求/响应语义的限制,并实现了一对多和服务器启动的推送工作流程,从而在浏览器内外打开了一个新的交互可能性世界。这是一项启用功能,对于我们如何考虑协议以及在何处以及如何使用协议将产生重要的长期影响。

为什么我们需要在浏览器中使用这样的机制?因为Web应用程序由许多资源组成,所有这些资源都是客户端通过检查服务器提供的文档来发现的。因此,为什么不消除额外的延迟并让服务器提前推送相关资源?服务器已经知道客户端需要哪些资源;这是服务器推送。

实际上,如果您曾经通过URI获取CSS,JavaScript或任何其他文件,那么您已经拥有了服务器推送的实践经验!通过手动将资源内联到文档中,我们实际上是将该资源推送到客户端,而无需等待客户端请求它。使用HTTP/2,我们可以获得相同的结果,但具有额外的性能优势:

  • 推送的资源可以由客户端缓存
  • 推送的资源可以在不同的页面上重用
  • 推送的资源可以与其他资源一起多路复用
  • 推送的资源可以由服务器确定优先级
  • 客户可以拒绝推送资源

每个推送资源都是一个流,与内联资源不同,它允许客户端对其进行单独多路复用,优先级排序和处理。浏览器强制执行的唯一安全限制是推送的资源必须遵循同源策略:服务器必须对提供的内容具有权威性。

PUSH_PROMISE 101

Header Compression

每个HTTP传输都带有一组标头,用于描述传输的资源及其属性。在HTTP/1.x中,此元数据始终以纯文本形式发送,并且每次传输增加500-800字节的开销,如果使用HTTP cookie,有时会增加千字节数; 为了减少这种开销并提高性能,HTTP/2使用HPACK压缩格式压缩请求和响应头元数据,该格式使用两种简单但功能强大的技术:

  1. 它允许通过静态霍夫曼编码对发送的报头字段进行编码,这减少了它们各自的传输大小。
  2. 它要求客户端和服务器都维护和更新先前看到的头字段的索引列表(即,建立共享压缩上下文),然后将其用作有效编码先前发送的值的参考。

霍夫曼编码允许在传输时压缩各个值,并且先前传输的值的索引列表允许我们通过传输索引值来编码重复值(图12-6),该索引值可用于有效地查找和重建完整的头部数据。
image

为进一步优化,HPACK压缩由静态和动态表组成的上下文:静态表由rfc文档定义,提供了所有可能使用的常见HTTP头字段的列表; 动态表最初为空,由特定连接中的交换值进行更新。结果,通过对先前未见过的值使用静态霍夫曼编码,并且对已经存在于每一侧的静态或动态表中的值的索引的替换,减少了每个请求的大小。

PS: HTTP/2中的请求和响应头字段的定义保持不变,但有一些小的例外:所有标题字段名称都是小写的,请求行现在拆分为单独的:method,:scheme,:authority和:path伪标题字段。

Security and Performance of HPACK

HTTP/2和SPDY的早期版本使用带有自定义字典的zlib来压缩所有HTTP标头,从而使传输的标头数据大小减少85%-88%,并显着改善页面加载时间延迟。

然而,在2012年夏天,针对TLS和SPDY压缩算法发布了“CRIME”安全攻击,这可能导致会话劫持。因此,zlib压缩算法被HPACK取代,HPACK专门设计用于:解决发现的安全问题,高效且易于正确实现,当然,还可以实现HTTP头元数据的良好压缩。

有关HPACK压缩算法的完整详细信息,请参阅https://tools.ietf.org/html/draft-ietf-httpbis-header-compression

PS 如果你正在阅读,并发现错误的地方,请不要吝啬你的建议。谢谢

http/2 入门

what (http/2是什么)

http 自 1989 年问世以来,就一直霸占了网络通信协议的铁王座。虽然有缺陷在身,但一直针对自身缺陷,
不断地提高、优化,使自己变得更好。而http/2没有改变http原有的任何语义,仅仅优化底层连接方式。
http 经历了以下几个重要历程:

  • http0.9: 第一个被公开露面的版本号,每个 tcp 连接只能支持一个请求响应,且只支持 get 方法,
    仅支持 html 传输。(缺点很明显:dizzy_face:)
  • http1.0: 增加了更多的方法,如 post/head;增加了其他格式内容的传输;(还是短链接)
  • http1.1: 优化了 tcp 连接方式,使得支持长链接和 http 流水线操作;引入了缓存机制;支持响应分块;
    增加 push/delete/options 方法。(引起了head-of-line block)
  • http2: 参考wiki😸 优化底层连接传输方式,
    同域名使用同一个tcp连接;使用二进制消息帧传输;

why (为什么会有http/2)

随着 http1.1 的知名度在逐渐扩大,web 应用程序的资源也在不断增加,浏览器加载资源的性能问题也就
暴露出来了,如 tcp 连接受限,请求头阻塞,请求头资源重复传输;然后又是 ssl 增加了一点难度。
因此,http2 于 2014 年诞生了,极乐降世,普度众生。

how (http/2是如何优化存在的缺陷)

了解了http发展历程,总结了存在这么几点缺陷:

  • 多个连接虽解决了并发现象,但没有解决请求头重复传输的问题
  • 虽然支持长链接,但server处理还是一一执行,而且还造成了head-of-line block

http/2 在 http/1.x 的基础上优化了传输机制,保持了原有的核心功能,但提供了高效优化。
http/2 协议的主要目标是针对 http/1.1 中存在的问题提出了解决方法, 并且还增加了更友好的功能:

  1. Multiplexing - 单个 tcp 连接异步发送多个请求
  2. Header compression - 在每个请求中,不需要发送相同请求头
  3. Binary protocol - 使用二进制分帧传输数据,不再使用 http1.1 的文本格式
  4. Request prioritization - 使得有限的资源定向到最重要的Stream
  5. Server push - 服务器可以异步发送数据到客户端的缓存

how http2 works

  • 客户端通过 http1.1 发送一个升级 http2 的请求,如果服务器支持,服务器返回 101 响应码
    作为同意升级 http2。然后客户端就在相同的 tcp 连接发送/接收请求。
  • 每一个请求和响应提供一个唯一的 stream ID,且把请求和响应信息拆分成 frame;stream
    ID 将和 frame 绑定在一起,用于区分不同的 stream。因为同一域下的请求使用同一个 tcp
    连接。
  • stream 运行设置优先级,那样的话,服务器根据优先级分配 memory/cpu/bandwidth。
  • Header 压缩确保请求头不会冗余。客户端和服务器会共同维护一份 header 表,当有新的请求
    头时,只会发送额外的请求头。
  • 服务端推送,运行客户端以一种高效的方式加载包含的资源。与 websockets 协议的服务器推送
    不同的是,websockets 协议的服务器可以在任何时候向客户端发送数据,即使没有来自客户
    端的请求。相反,http2 服务器推送仍然符合请求响应模式。

issue

  1. 为什么二进制帧可以高效处理message?

shader 函数

  • step
step (a, x)
{
  if (x < a) 
  {
    return 0;
  }
  else
  {
    return 1;
  }
}

当 x < a时,返回0;反正返回1;

  • lerp
lerp(a, b, w)
{
  return a + w*(b-a)
}

当 w = 0 时,返回a,当 w = 1 时,返回b;
否则返回对 a 和 b 的差值,w 越接近0,返回结果越接近a,w越接近1,返回结果🈷️接近1,通常用来计算一些渐变量。

  • smoothstep
float smoothstep(float a, float b, float x) 
{
  x = clamp((x - a) / (b- a), 0.0, 1.0); 
  return x * x * (3 - 2 * x);
}

smoothstep可以用来生成0到1的平滑过渡值,它也叫平滑阶梯函数。
例子参考:
https://www.jianshu.com/p/53fe928a0fb6

async2.6.1源码分析之auto

两个知识点:

  1. 拓扑排序算法
  2. 生产者消费者模式

代码中都有指出

function auto(tasks, concurrency, callback) {
    if (typeof concurrency === 'function') {
        // concurrency is optional, shift the args.
        callback = concurrency;
        concurrency = null;
    }
    callback = once(callback || noop);
    var numTasks = Object.keys(tasks).length;
    if (!numTasks) {
        return callback(null);
    }
    if (!concurrency) {
        concurrency = numTasks;
    }

    var results = {};   // 保存task的结果
    var runningTasks = 0;   // 用于标志队列是否执行完毕或是否还有剩余队列可执行
    var canceled = false;   // 是否撤销async调用
    var hasError = false;   // 是否提前退出调用

    var listeners = Object.create(null);

    var readyTasks = [];

    // for cycle detection:
    var readyToCheck = []; // tasks that have been identified as reachable
    // without the possibility of returning to an ancestor task
    var uncheckedDependencies = {};

    // 1。为拓扑排序检查做数据准备
    Object.keys(tasks).forEach(key => {
        var task = tasks[key]
        if (!Array.isArray(task)) {
            // no dependencies
            enqueueTask(key, [task]); // 度为0
            readyToCheck.push(key);
            return;
        }

        var dependencies = task.slice(0, task.length - 1);
        var remainingDependencies = dependencies.length;
        if (remainingDependencies === 0) {
            enqueueTask(key, task); // 度为0
            readyToCheck.push(key);
            return;
        }
        uncheckedDependencies[key] = remainingDependencies;

        dependencies.forEach(dependencyName => {
            if (!tasks[dependencyName]) {
                throw new Error('async.auto task `' + key +
                    '` has a non-existent dependency `' +
                    dependencyName + '` in ' +
                    dependencies.join(', '));
            }
            addListener(dependencyName, () => {
                remainingDependencies--;
                if (remainingDependencies === 0) {
                    enqueueTask(key, task); // 度不为0的key,只是依赖的task全部执行完才加入到最后的任务队列中
                }
            });
        });
    });

    checkForDeadlocks();    // 2. 检查依赖关系是否存在循环♻️
    processQueue(); // 开始执行队列

    // 把入度为0的点加入队列
    function enqueueTask(key, task) {
        readyTasks.push(() => runTask(key, task));
    }

    function processQueue() {
        if (canceled) return    // 撤销async.auto()程序
        if (readyTasks.length === 0 && runningTasks === 0) {
            return callback(null, results);
        }
        // 优先执行度为0的点
        while(readyTasks.length && runningTasks < concurrency) { // 设置并发限制,等待前一轮的某一个task执行结束再增加下一个task
            var run = readyTasks.shift();
            run();  // 执行runTask
        }

    }

    // 相当于生产者,根据taskName往池子(listeners)里生产东西
    function addListener(taskName, fn) {
        var taskListeners = listeners[taskName];
        if (!taskListeners) {
            taskListeners = listeners[taskName] = [];
        }

        taskListeners.push(fn);
    }

    function taskComplete(taskName) {
        var taskListeners = listeners[taskName] || [];
        taskListeners.forEach(fn => fn());  //类似于消费者,根据taskName获取池子里的东西,使得每一个依赖taskName、度不为0的点减1,直至为0
        processQueue();
    }


    function runTask(key, task) {
        if (hasError) return;

        var taskCallback = onlyOnce((err, ...result) => {
            runningTasks--; // 执行队列-1
            if (err === false) {
                canceled = true
                return
            }
            if (result.length < 2) {
                [result] = result;
            }
            if (err) {
                var safeResults = {};
                Object.keys(results).forEach(rkey => {
                    safeResults[rkey] = results[rkey];
                });
                safeResults[key] = result;
                hasError = true;
                listeners = Object.create(null);
                if (canceled) return
                callback(err, safeResults);
            } else {
                results[key] = result;
                taskComplete(key);
            }
        });

        runningTasks++; // 执行队列+1
        var taskFn = wrapAsync(task[task.length - 1]);
        if (task.length > 1) {
            taskFn(results, taskCallback);
        } else {
            taskFn(taskCallback);
        }
    }

    /**
     * 拓扑排序
     * // https://acm.sjtu.edu.cn/w/images/4/4e/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F.pdf
     * 把入度为0的点放入队列
     * while(队列非空){
     *      now = 弹出对头
     *      for(遍历now的所有出边){
     *          to = 出边
     *          du[to]--;
     *          if(!du[to]) to入队
     *      }
     *      // now出列,放入结果序列
     * }
     *
     * wiki:
     * L 表示排好序的队列
     * S 表示度为0的边
     * while(S非空){
     *      N = S.shift()
     *      L.push(N);
     *      for(遍历N的出边){
     *          m = N指向的点
     *          从graph移除指向m的边
     *          if(m度为0) S.push(m)
     *      }
     * }
     *
     * if(graph还有边) return error(至少有一个循环♻️)
     * else return L (拓扑排序的结果)
     */
    function checkForDeadlocks() {
        // Kahn's algorithm
        // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
        // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
        var currentTask;
        var counter = 0;
        while (readyToCheck.length) {
            currentTask = readyToCheck.pop();
            counter++;
            getDependents(currentTask).forEach(dependent => {
                if (--uncheckedDependencies[dependent] === 0) {
                    readyToCheck.push(dependent);
                }
            });
        }

        if (counter !== numTasks) { // 若队列里的个数不等于人物队列的个数,则判定为存在循环依赖♻️
            throw new Error(
                'async.auto cannot execute tasks due to a recursive dependency'
            );
        }
    }

    // 获取度为0指向的出边数据
    function getDependents(taskName) {
        var result = [];
        Object.keys(tasks).forEach(key => {
            const task = tasks[key]
            if (Array.isArray(task) && task.indexOf(taskName) >= 0) {
                result.push(key);
            }
        });
        return result;
    }
}

angularjs指令--指令创建

指令(Directives)是所有AngularJS应用最重要的部分。尽管AngularJS已经提供了非常丰富的指令,但还是经常需要创建应用特定的指令。

自定义指令

指令可以有四种表现形式,分别为元素(element)、属性(attribute)、注释(comment)、样式类(class),正好代表ECMAScript的前四个字母,还是很好记的。基本样式如下:

var app = angular.module('myapp', []);
 
app.directive('helloWorld', function() {
  return {
      restrict: 'ECMA',
      replace: true,
      transclude: true,
      template: '<div>Hello World!!</div>'
      //templateUrl: 'hello.html'
  };
});

使用app.directive()方法在模块中注册一个新的指令,第一个参数是指令的名称,第二个参数是返回指令定义对象的函数;如果你的指令依赖于其他的对象或者服务,比如 $rootScope, $http, 或者$compile,他们可以在这个时间被注入。
页面使用形式:

<hello-world></hello-world>
<div class="hello-world"></div>
<!-- directive:hello-world -->
<div hello-world></div>

部分属性说明:

  • restrict: 用来声明指令在DOM中以何种方式被声明,默认值是A
  • replace: 默认值为false,意思是模板会被当做子元素插入到调用此指令的元素内部
  • transclude: 为true,表示允许将自己的html模板嵌入到指令模板中,默认值为false
  • template: html文本,展示在页面渲染的内容
  • templateUrl: 外部html文件路径,在实际生产中,可以提前将模板缓存到一个定义模板的javascript文件中。

angularjs深度学习参考


Adventure may hurt you, but monotony will kill you. - -也许冒险会让你受伤,但一成不变会使你灭亡。

服务器环境搭建

1. jdk配置

1.1 下载

http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html

下载文件存放在 [ /usr/local/ ] ,下载rmp格式的文件

1.2 安装

首先检查系统是否安装jdk,使用java –version命令,如果有,则先卸载,卸载命令rpm –qa|grep java;否则安装自己所需要配置的jdk

路径切换到文件存放路径下,然后使用该rpm -ivh jdk-7-linux-i586.rpm命令解压安装,默认安装路径 /usr/java/jdk1.7.0-xx ,使用mv /usr/java/jdk1.7.0-xx /usr/java/jdk7命令更改文件名

1.3 环境变量

配置环境变量,打开配置文件profile
vi  /etc/profile
在该profile文件中最下面添加:

JAVA_HOME=/usr/java/jdk1.7.0/      //自己的jdk路径
JRE_HOME=/usr/java/jdk1.7.0/jre
PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/jt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
export JAVA_HOME JRE_HOME PATH CLASSPATH

添加完毕保存退出
source /etc/profile命令使profile文件生效

1.4 测试

检查是否安装成功java –version

如果以上都ok,恭喜你进入升级了,进入下一个配置

参考资料


2. 防火墙配置

centos7 关闭firewall安装iptables并配置
参考资料


3. tomcat7配置

3.1 下载

tomcat7下载

下载文件存放在 /usr/local/

3.2 解压安装

切换到tomcat路径下,使用该tar -zxvf apache-tomcat-7.0.28 命令解压安装,可以使用命令mv /usr/local/apache-tomcat-7.0.28 /usr/local/tomcat7修改文件名
赋予tomcat的执行权限 chmod +x /usr/local/tomcat7

3.3 环境配置

编辑profile文件
vi /etc/profile
在该profile文件中最下面添加:

TOMCAT_HOME=/usr/local/apache-tomcat-7.0. 28
CATALINA_HOME=/usr/local/apache-tomcat-7.0. 28
export TOMCAT_HOME CATALINA_HOME

添加完毕保存退出
source /etc/profile命令使profile文件生效

3.4 测试

将tomcat默认端口8080加入防火墙,启动tomcat,浏览器进行测试,这一步偷点懒。
vi /etc/sysconfig/iptables

image

把红色内容复制一遍,只需修改端口号,保存退出,重启防火墙service iptables restart
参考资料

4. mysql配置

下载
mysql-5.7.10-linux-glibc2.5-x86_64.tar.gz(glibc版)

PS:不要下载mysql-5.7.10.tar.gz这个版本,安装很麻烦,需要做很多前期准备

4.1 安装

tar -zxvf mysql-5.7.10-linux-glibc2.5-x86_64.tar.gz
ln –s mysql-5.7.10-linux-glibc2.5-x86_64 mysql

详细操作参考该文档

注意mysql安装过程中的临时密码
image

4.2 配置

是否把端口加入防火墙取决于需求

权限分配

给ito分配权限
mysql> revoke all on *.* from ito@"%" identified by '123456';
先收回名为ito的用户的说有权限
mysql> grant usage on *.* to [email protected] identified by '123456';
给名为ito,ip为192.168.82.13的用户登录查看权限, 登录密码为123456
mysql> grant select, insert, update, delete on *.* to [email protected] identified by '123456';给ip为192.168.82.139的ito用户授予增删改查权限,登录密码为123456
mysql> show grants for [email protected];
查看名为ito,ip为192.168.82.13的用户权限
mysql> flush privileges;
刷新权限

给名为test的用户分配查看的权限:
mysql> revoke all on *.* from test@"%" identified by '123456';
先收回名为test的用户的说有权限
mysql> grant usage on *.* to test@"%" identified by '123456';
给名为test的任意ip所有所有用户登录查看权限
mysql> grant select on *.* to test@% identified by '123456';
给名为test的任意ip所有所有用户分配查看权限
mysql> show grants for test@%;
查看名为test的任意ip所有所有用户的权限
mysql> flush privileges;
刷新权限

mysql> revoke all on *.* from root@"%" identified by '123456';
给名为root的用户设置权限前要先收回所有权限
mysql> grant all on *.* to [email protected] identified by '123456';
给名为root,ip为192.168.82.139的用户分配所有权限,登录密码为123456
mysql> grant all on *.* to [email protected] identified by '123456';
给名为root,ip为192.168.82.71的用户分配所有权限,登录密码为123456
mysql> show grants for root@%;
查看名为root的任意ip所有所有用户的权限
mysql> show grants for [email protected];
查看名为root,ip为192.168.82.13的用户权限
mysql> show grants for [email protected];
查看名为root,ip为192.168.82.13的用户权限

mysql> flush privileges;
刷新权限

4.4 测试

image


5 redis配置

5.1 下载

官网下载redis,版本任意,存放在/usr/local/该路径下

5.2 安装

切换到redis存放位置

解压tar –zxvf redis-3.0.6.tar.gz

设置超链接ln –s redis-3.0.6 /usr/local/redis

切换路径cd redis

安装 make PREFIX=/usr/local/redis install

5.3 配置

  • 复制脚本到/etc/rc.d/init.d目录

    cp /usr/local/redis/utils/redis_init_script /etc/rc.d/init.d/redis

  • 更改redis脚本

    vi /etc/rc.d/init.d/redis

    修改的内容(三处):

'#'chkconfig: 2345 80 90

EXEC=/usr/local/redis/bin/redis-server
CLIEXEC=/usr/local/redis/bin/redis-cli

$EXEC $CONF &

image

  • 将redis配置文件拷贝到/etc/redis/${REDISPORT}.conf

    mkdir /etc/redis

    cp /usr/local/redis/redis.conf /etc/redis/6379.conf

  • 将Redis的命令所在目录添加到系统参数PATH中

    打开文件vi /etc/profile

    在最后行追加 ** export PATH="$PATH:/usr/local/redis/bin" **

    文件生效. /etc/profile 

  • 注册redis服务

    chkconfig --add redis

  • 启动redis服务

    service redis start 

    Ctrl+c 退出,redis在后台开启

  • 开启redis密码

    密码:在redis.conf里找'requirepass'后加密码,重启redis

  • Redis重启数据恢复

    数据恢复:在redis.conf里找'appendonly'的no改为yes

  • 初始化值

    设置两个值从10000开始

[ > set SEQ.KEY 10000 ]    //java使用的key从10000开始
[ > set SEQ.SN 10000 ]	   //java使用的sn从10000开始

5.4 测试

redis-cli

如果有密码,输入-a

参考资料


6. nginx配置(无)


7. 文件配置

  • 文件服务

http服务

查看http服务是否开启 chkconfig --list 若没有开启,则service httpd start

修改文件**/etc/httpd/conf/httpd.conf**

//在文件末尾加上
ServerName *:8888
NameVirtualHost 192.168.46.172:8888
<VirtualHost 192.168.46.172:8888>
	DocumentRoot /var/www/ito
	ServerName station1.example.com
	ServerAlias server1.example.com
</VirtualHost>
//修改	ps: Listener的80-->8888

保存退出

建相应的根目录mkdir /var/www/ito/

重启httpd,如果出现绑定端口失败,请阅读文章

把文件服务端口加入防火墙


8. jmx配置

Jmx配置分验证和无验证两种方式,我们使用无验证方式的配置。

修改Tomcat目录下的bin\catalina.sh

在该文件中查找set JAVA_OPTS=%JAVA_OPTS%,下面有一行为:

rem ----- Execute The Requested Command ---------------------------------------

在这一行的下面加

set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9008 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false 
//PS:9008为jmx端口号,一会儿要用到。

重启服务

在cmd中执行netstat -an,可查看到9008端口已经启用,说明Tomcat的Jmx配置成功

端口记得加入防火墙

使用windows下的jconsole远程连接

在cmd中敲入:jconsole(这个exe令在C:\Program Files\Java\jdk1.6.0_23\bin,即安装目录的bin下),即可启动jconsole的管理界面,在界面中输入如下内容:

service:jmx:rmi:///jndi/rmi://192.168.46.172:9008/jmxrmi

需要密码验证的配置参考该文章

不需要密码验证的配置参考文章

一沙一世界,一花一天堂。双手握无限,刹那是永恒。

抽象语法树例子

var esprima = require('esprima')
var estraverse = require('estraverse')
var escodegen = require('escodegen')

var fs = require('fs')
var path = require('path')

// 保留注释
function update(filename) {
  var code = fs.readFileSync(filename, 'utf8')
  var ast = esprima.parseModule(code, {
    tokens: true,
    comment: true,
    range: true,
  })
  ast = escodegen.attachComments(ast, ast.comments, ast.tokens)
  estraverse.traverse(ast, {
    enter(node) {
      if (node.type === 'ImportDeclaration') {
        node.source.value = ''
      }
    }
  })
  var newCode = escodegen.generate(ast, {
    format: {
      indent: {
        adjustMulfilineComment: true
      }
    },
    comment: true
  })

  fs.writeFileSync(filename, newCode)
}

function travel(dir) {
  if (fs.statSync(dir).isFile()) {
    return update(dir)
  }

  var dirs = fs.readdirSync(dir)
  dirs.forEach((file) => {
    var pathname = path.join(dir, file)
    if(fs.statSync(pathname).isDirector()) {
      travel(pathname)
    } else {
      update(pathname)
    }
  })
}

js笔记收录(1)

  1. call/apply/slice/splice/substr
  2. delete操作符
  3. setTimeout/setInterval中的this
  4. javascript-garden
  5. attribute & property(javascript.info)
  6. js 运算符的优先级
  7. js 相等(==)运算符深入解析


一直想整理关于js的一些特殊方法,eg:call/apply/slice/splice/substr...

call/apply

call/apply:可以简单理解为调用/应用,属于Function.prototype的一个方法,所以每个Function对象实例都有call/apply属性,两者作用效果是相同的,只是用法不同
foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3)

  • this表示执行foo方法的上下文相关对象
  • arg1,arg2,arg3表示传递给foo方法的参数

使用apply时,把参数当做一个整理传递;而使用call时,把参数分开,一个一个地传递;

call/apply:表示在指定作用域中执行调用函数(函数已执行)
bind: 表示给调用函数指定作用域(函数未执行)


callee/caller

  • callee 是arguments的一个属性,是指针类型,指向拥有arguments对象的函数
  • caller 是函数对象的一个属性,通过函数名调用,如FunctionName.caller,指向调用当前函数的函数的引用
    callee
//使用callee前
function test(x){
    if (x<=1) {
    	return 1;
    } else{
    	return x*test(x-1);
    };
};
//使用callee后
function test(x){
    if (x<=1) {
    	return 1;
    }else{
    	return x*arguments.callee(x-1);
    };
};

根据callee的定义,可以看出来callee是arguments对象的一个属性,指向arguments对象的函数,这个函数就是test(test=arguments.callee)
caller

function a(){
	b();
};
function b(){
	alert(b.caller);
};
a(); //结果就是弹出函数a和内容

函数b的属性caller调用当前函数b的函数引用a(就是指向当前函数b的父函数a),所以结果就是弹出 function a(){ b();};


splice

可以表示截取、替换、删除的意思,这里指数组。该方法会改变原始数组
splice是一个全能型的方法,通过参数的个数来实现不同的操作。

  • 删除:两个参数,如下:
    arr.splice(2,1)表示从数组下标为2开始向后删除一项,即删除下标为2的指定项
  • 插入:(2+n)个参数,如下:
    arr.splice(2,0,'add1','add2')表示从数组下标为2的指定项后面添加,第二个参数必须为0
  • 替换:(2+n)个参数,如下:
    arr.splice(2,1,'replace')表示从数组下标为2的指定项开始,后面的第一个指定项替换为replace字符串

slice/substr/substring

都表示截取的意思

  • slice() slice(i,[j])既可以表示字符串截取也可以表示数组截取,不改变原始内容,返回结果为从ij指定的内容,如果没有j,表示从i到末尾
  • substr() substr(start,length)表示字符串截取,不改变原始内容,如果没有第二个参数,表示截取到内容末尾
  • substring() substring(start,end)表示字符串截取,不改变原始内容,如果没有第二个参数,表示截取到内容末尾

push/pop/shift/unshift

这几个方法构成数据结构的堆栈和队列;push()从末尾追加数据,pop()从末尾输出数据,shift()从头部弹出数据,unshift()从头部追加数据。

  • push/pop:构成堆栈结构,遵循LIFO原则,即后进先出
  • unshift/push:构成队列结构,遵循FIFO原则,即先进先出
  • shift/pop:构成反队列结构,也是先进先出

移动端使用iscroll插件button触发两次

工作中使用angularjs为移动端编写页面,在页面中使用button+click方法进行事件响应,如

<button ng-click='method(arg0,arg1)'>点击</button>

使用手机进行点击时会引发两次事件响应,原因不明,不知道问题出在哪里,或许是button一次,click一次,然而并不是这样。google搜了一下,有说是点透原因,也有说是冒泡导致,然后一个个进行测试。点透原因使用fastclick库,用法:导入fastclick.js,然后初始化fastclick:

$(function(){
	FastClick.attach(document.body);
});

就这样试了一下,问题并没有好转,还是和之前一样,点击一次引发两次事件。第一个失败,第二个继续,阻止事件冒泡:

<button ng-click='method(arg0,arg1,$event)'>点击</button>

method = function(arg0,arg1,$event){
	$event.stopPropagation();
    //$event.preventDefault();
}

没有效果,也不是冒泡引起的。由于我不是很懂js和html,网上搜的方法都不适合,只能不断尝试,换个a标签试试:

<a ng-click='method(arg0,arg1)'>点击</a>

这时,那问题不见了,点击一次请求一次。虽然问题没有了,但是不知道这错误原因,有知道的还请告知,先谢谢了。对了,angular-touch.js也试过了,没有用,所以这肯定不是冒泡导致的。

2017-07-01
因为使用到iscroll开源插件,经过源码分析得知:options对象有一个preventDefaultException属性,对button/input/textarea/select进行了过滤,使得preventDefault()方法跳过,根据名称猜测preventDefaultException属性好像是过滤异常用的,不知道什么目的。
这个可以参考一下

移动端的touch事件和click事件

触摸屏幕响应的事件顺序

  • touchstart
  • touchmove
  • touchend
  • mouseover
  • mouseenter
  • mousedown
  • click

preventDefault的定义是取消事件的默认行为;stopPropagation的定义是取消事件的进一步捕获或冒泡;
要想阻止事件后面的行为,可以使用preventDefault()方法。在touchstart和touchend事件里调用preventDefault()方法,会失去页面滚动的功能,### 这是一个大大的?

对某个特定div区域既要响应滑动事件又要支持点击事件

可以在touchmove事件调用preventDefault()方法
参考资料

程序员八荣八耻

以动手实践为荣,以只看不练为耻;

以打印日志为荣,以单步跟踪为耻;

以空格缩进为荣,以制表缩进为耻;

以单元测试为荣,以人工测试为耻;

以模块复用为荣,以复制粘贴为耻;

以多态应用为荣,以分支判断为耻;

以干净利索为荣,以冗余拖沓为耻;

以总结分享为荣,以跪求其解为耻;

人不能仅仅依靠面包生活,他需要有希望才过得有意义的生活。

二进制浮点数的存储解读

参考:

image

ECMAScript文档写明,数字类型遵循IEEE 754标准的64位双精度格式存储。这种类型用于存储整数和分数,等同于Java和C中的double数据类型。JavaScript的一些新开发人员没有意识到这一点,并且相信如果他们使用1,它将以64位存储为:

0000000000000000000000000000000000000000000000000000000000000001

实际上存储形式是这样的:

0011111111110000000000000000000000000000000000000000000000000000

继续深入,了解为什么存储的格式与想象的不一样。

科学计数法,小学的时候学过,忘的差不多,回顾一下。
根据wiki定义,有效位数取值范围为0<=|significant|<base,因此正确表示为
image

significant表示有效位数,也被称为mantissa或precision。base表示统计的基数。exponent表示小数点向左向右移多少位,也叫指数。

任何数都可以用科学计数法来表示。如十进制和二进制系统中的数2可以表示为:
image

然后看一个小数如何表示:
image

指数大于0,表示小数点右移;指数小于0,表示小数点左移。

科学计数法可以用来表示数字的浮点数。进一步理解为浮点数就是小数点的移位。

IEEE754定义了两种精度存储方式——单(32)精度/双(64)精度
image

Javascript使用64位存储数字,下面是它如何以JavaScript的Number类型使用的双精度格式(每个数字为64位)分配这些位:
image

符号位占1位,指数占11位,52位分配给尾数(有效数字)

1的浮点数

1的科学计数法表示如下:
image

有效位是1,指数是0,可以得出如下表示:
0 00000000000 0000000000000000000000000000000000000000000000000001

然而,我们如何计算实际存储的格式呢?网上搜到一种方法可以计算出浮点数的存储格式:

function to64bitFloat(number) {
    var i, result = "";
    var dv = new DataView(new ArrayBuffer(8));

    dv.setFloat64(0, number, false);

    for (i = 0; i < 8; i++) {
        var bits = dv.getUint8(i).toString(2);
        if (bits.length < 8) {
            bits = new Array(8 - bits.length).fill('0').join("") + bits;
        }
        result += bits;
    }
    return result;
}

打印结果得到:
0 01111111111 0000000000000000000000000000000000000000000000000000
尾数占位符没有数字,指数中存在1。大大的疑问??

二进制中最高有效位始终为1,所以不存储,在执行数学运算时,第一个数字1由硬件前置。数字1在标准化形式的小数点之后没有数字,并且没有存储小数点之前的第一个数字,因此我们没有任何东西可以放入尾数,因此它全部为零。
指数存储是通过位偏移计算,得到的结果:
image

因此就计算出了指数占位符中的数。

3的浮点数

3的二进制是11,科学计数法如下:
image

小数点后只有一个1需要存储,小数点前的1不存储;然后根据指数的位偏移计算指数占位符:
image

关于尾数的一件事要注意,数字按照它们以科学形式放置的确切顺序存储 - 从小数点开始从左到右。考虑到这一点,让我们将所有数字放在浮点表示中:
image

0.1+0.2 !== 0.3

现在再来看看0.1加0.2为什么不等于0.3

0.1和0.2的浮点数

根据二进制转换规则,0.1是一个无线循环的小数:
image

用科学计数法表示如下:
image

由于尾数只能有52位,我们需要在小数点后将无限数舍入为52位。
image
image

指数的计算根据位偏移得出:
image

因此0.1的存储形式如下:
image

同样的方法,0.2的存储形式为:
image

0.1/0.2的科学计数法表示如下:
image

计算两个数相加,需要使得指数相同。调整后的0.1如下:
image

然后两数相加:
image

现在,计算结果以浮点格式存储,因此我们需要对结果进行标准化,必要时进行舍入并计算偏移二进制的指数。
image

当转换为浮点格式进行存储时,它具有以下位模式:
image

这正是执行语句0.1 + 0.2时存储的位模式。为了得到它,计算机必须绕三次 - 每个数字一个,第三次总和。当存储简单的0.3时,计算机仅执行一次舍入。这种舍入操作导致存储0.1 + 0.2和独立0.3的不同位模式。当JavaScript执行比较语句0.1 + 0.2 === 0.3时,它们的这些位模式被比较,并且由于它们不同,返回的结果是假的。

NaN/Infinity

这两个指数是1024,不等于Number.MAX_VALUE(指数是1023)
NaN的存储形式为:
image

Infinity是浮点数的另一个特例,用于处理溢出和一些数学运算,如1/0。无穷大用指数中的所有1和尾数中的全零表示
image

async2.6.1源码分析之waterfall

waterfall

定义很简单,只用一个函数就解决了,比看parallel轻松多了。

export default function waterfall(tasks, callback) {
    callback = once(callback || noop);
    if (!Array.isArray(tasks)) return callback(new Error('First argument to waterfall must be an array of functions'));
    if (!tasks.length) return callback();
    var taskIndex = 0;

    // 类似于自动执行器,和co模块功能相同,都是用于自动执行“async”后面的状态
    function nextTask(args) {
        var task = wrapAsync(tasks[taskIndex++]);  //对函数进行包装,判断函数是否是异步函数,同asyncify
        task(...args, onlyOnce(next));  // 如果args是空数组,就不会占据一个参数占位符,所以第一次调用时,参数只有一个onlyOnce(next)返回的函数
    }

    function next(err, ...args) {
        if (err === false) return       // https://github.com/caolan/async/issues/1064 false表达的是终止程序流程;对于更多的情况通常是传null/undefined,这里使用了一种技巧。<del>这里算是一个漏洞,不知道是有意为之还是疏忽大意,尽管发生概率不大</del>
        if (err || taskIndex === tasks.length) {
            return callback(err, ...args);
        }
        nextTask(args);
    }

    nextTask([]);
}

export default function once(fn) {
    return function (...args) {
        if (fn === null) return;
        var callFn = fn;
        fn = null;
        callFn.apply(this, args);
    };
}

export default function onlyOnce(fn) {
    return function (...args) {
        if (fn === null) throw new Error("Callback was already called.");
        var callFn = fn;
        fn = null;
        callFn.apply(this, args);
    };
}

function isAsync(fn) {
    return fn[Symbol.toStringTag] === 'AsyncFunction';  //这里没看懂,什么情况下会为true?? 已解决
}
function wrapAsync(asyncFn) {
    return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;
}

onceonlyOnce 没看出来有什么区别,都只是为了保证fn不为空;即使在被返回的函数里切断了fn的关系,并不能使得下一次调用相同的fn等于null;我认为切断关系是为了尾调用优化的机制。once是为了保证fn在所在的词法作用域(waterfall)中只被调用一次,类似地,onlyOnce也是为了保证fn在所在的词法作用域(nextTask)中只被调用一次。

  • once用于async库内部保证callback被安全调用一次;onlyOnce用于外部调用async库内部的callback被安全调用一次,如果调用多次,把错误抛给用户。

error===false可以起到提前退出流程控制的作用,但是会使得程序挂起,因为callback无法执行

算法

算法

1. 广度优先算法
2. 深度优先算法
3. 洗牌算法

1. 广度优先算法**

依次遍历顺序为: A -> B -> C -> D -> E -> F

2. 深度优先算法**

依次遍历顺序为:A -> B -> D -> E -> C -> F -> G

数据源

[{
    "name": "it",
    "children": [{
        "name": "fsmr3",
        "children": [{
            "name": "provision",
            "children": [{
                "name": "scm",
                "result": "fail"
              }]
          }, {
            "name": "sct",
            "result": "pass"
          }, {
            "name": "qt",
            "result": "pass"
          } ]
      } ]
  }, {
    "name": "ut",
    "children": [{
        "name": "fsmr3",
        "children": [{
            "name": "provision1",
            "result": "pass"
          }, {
            "name": "sct1",
            "result": "pass"
          }, {
            "name": "qt1",
            "result": "pass"
          } ]
      } ]
  }]

问题

  • 输入一个name值, 输出children的值(name可以是任意级的值)
let names = [], child = [], nameSplit = [], temp = [];

/**
 * @name: getChildrenByName
 */
const treeName = function(nodes, phase) {
    if (!nodes || !nodes.length) return;
    for (let i = 0, len = nodes.length; i < len; i++) {
        const item = nodes[i];
       if(item.name === phase){
              names = item.children ? item.children : [item];
       }
        const childs = item.children;
        if (childs && childs.length > 0) {
             treeName(childs, phase);
        }
    }
};

/**
 * @name: getItemByChildren
 */
const treeChildren = function(nodes) {
    if (!nodes || !nodes.length) return;
    for (let i = 0, len = nodes.length; i < len; i++) {
        const item = nodes[i];
       if(!item.children){
              child.push(item)
       }
        const childs = item.children;
        if (childs && childs.length > 0) {
             treeChildren(childs);
        }
    }
};

treeName(arr, 'scm')
treeChildren(names)

console.log('names====',names);
console.log('child =====',child);

根据一个name值, 可以获取其children值, 这时候就有个问题, 如果一级不同, 二级有相同的name, 按照这个问题引发出另一种写法

const treeNameSplit = function(nodes, phase) {
    if (!nodes || !nodes.length) return;
    nameSplit = phase.split(';');

    for (let i = 0, len = nodes.length; i < len; i++) {
      const item = nodes[i];
       if(item.name === nameSplit[0]){
              nameSplit.shift();
              temp = item.children ? item.children : [item];
       }

        const childs = item.children;

        if (childs && childs.length > 0) {
             treeNameSplit(childs, phase);
        }
    }
};

treeNameSplit(arr, 'ut;fsmr3')
console.log('temp=====',temp[0]);

以上递归遍历是递归深度优先遍历算法。

非递归广度优先实现

let result = null;
const iterator1 = function(treeNodes, phase) {
    if (!treeNodes || !treeNodes.length) return;

    let stack = [...treeNodes];
    let item;

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

        console.log(item.name);
        if(item.name === phase){
              result = item;
              break;
        }

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

iterator1(arr, 'it');
console.log('arr', result)

非递归深度优先实现

const iterator2 = function(treeNodes, phase) {
    if (!treeNodes || !treeNodes.length) return;

    const stack = [...treeNodes];
    let item;

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

        console.log(item.name);
        if (item.name === phase) {
            result = item;
            break;
        }

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

iterator2(arr, 'it');
console.log('arr', result);

three 流光效果

<html lang="en">
	<head>
		<title>流光效果</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
	</head>
	<body>

		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js"
				}
			}
		</script>

		<script type="module">

import * as THREE from 'three';
import { OrbitControls } from './jsm/controls/OrbitControls.js';

function main() {
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set(100, 100, 0);

    var scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x000000 );
    // scene.fog = new THREE.Fog( 0xffffff, 0, 750 );

    const light = new THREE.AmbientLight( 0xffffff, 0.2);
    // light.position.set( 0.5, 1, 0.75 );
    scene.add( light );
    scene.add( camera );

    var renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );

  // 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼(惯性),这将给控制器带来重量感,如果该值被启用,必须在动画循环里调用.update()
controls.dampingFactor = 0.05; // 阻尼惯性大小

  const point1 = [50, 0, 0]; // 点1坐标
const point2 = [-50, 0, 0]; // 点2坐标
const controlPoint = [0, 50, 0]; // 控制点坐标

// 创建三维二次贝塞尔曲线
const curve = new THREE.QuadraticBezierCurve3(
  new THREE.Vector3(point1[0], point1[1], point1[2]),
  new THREE.Vector3(controlPoint[0], controlPoint[1], controlPoint[2]),
  new THREE.Vector3(point2[0], point2[1], point2[2])
);

const divisions = 20
const points = curve.getPoints(divisions)
console.log('points', points)

// 创建Geometry
const geometry = new THREE.BufferGeometry().setFromPoints(points);
// geometry.vertices = points; // 将上一步得到的点列表赋值给geometry的vertices属性
// var colors = new Array(points.length).fill(
//   new THREE.Color('#333300')
// );
var color = new THREE.Color('#ffff00')
var colors = []
for(var i=0; i<points.length; i++) {
    colors.push(color.r, color.g, color.b)
}
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );


// 生成材质
const material = new THREE.LineBasicMaterial({
  vertexColors: true, // 顶点着色
  transparent: true, // 定义此材质是否透明
  side: THREE.DoubleSide,
});
const mesh = new THREE.Line(geometry, material);
scene.add(mesh)

let colorIndex = 0; // 高亮颜色流动的索引值
let timestamp = 0; // 时间戳

function animate() {
  controls.update();
  console.log('xxxx')
  // 时间间隔
  let now = new Date().getTime();
  if (now - timestamp > 30) {
    var colors = []
    new Array(divisions + 1)
      .fill(new THREE.Color('#ffff00'))
      .map((color, index) => {
        if (index === colorIndex) {
          var color = new THREE.Color('#ff0000');
          colors.push(color.r, color.g, color.b)
        }
        colors.push(color.r, color.g, color.b)
        return color;
      });
    geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

    // 如果geometry.colors数据发生变化,colorsNeedUpdate值需要被设置为true
    // geometry.colorsNeedUpdate = true;
    timestamp = now;
    colorIndex++;
    if (colorIndex > divisions) {
      colorIndex = 0;
    }
  }
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
animate()

}

main();


		</script>

	</body>
</html>

angularjs指令--隔离作用域

指令的作用域介绍:通常,隔离指令的scope会带来很多的便利,尤其是你要操作多个scope模型的时候,但有时为了使代码能够正确工作,你也需要从指令内部访问scope的属性。
作用域表现形式有两种:

  • scope: false/true,默认是false
  • scope: {} 隔离作用域,这里又有三种策略 @ = &

scope布尔型

默认值为false,表示从父作用域继承过来。
为true,表示从父作用域继承并创建一个新的作用域对象。
如果一个元素上有多个指令使用了隔离作用域,其中只有一个可以生效。只有指令模板中的根元素可以获得一个新的作用域。ng-controller就是从父极作用域继承并创建一个新的子作用域。

<div ng-controller='myCtrl'>
	{{test}}
</div>
angular.module('myApp',[]).controller('myCtrl', function($scope){
	$scope.test = 'test';
});

scope对象

当scope是一个对象的时候,指令的作用域与父极作用域就被隔开了。隔离作用域可能是最难理解的,但也是功能最强大的。具有隔离作用域的指令最主要的使用场景是创建可复用的组件,组件在未知的上下文中使用,并且可以避免污染所处的外部作用域或不经意地污染内部作用域。

  • scope: {}
    将scope设置为一个空对象,这样,指令的模板就无法访问外部作用域了
app.directive('helloWorld', function() {
  return {
    scope: {},
    restrict: 'AE',
    replace: true,
    template: '<p>{{hello}}</p>'
  };
});

页面使用指令:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color"/>
  <hello-world/>
</body>

这里的hello-world指令是不生效的,因为我们使用了隔离作用域,指令模板的作用域是无法访问外部controller的作用域。

接下来描述指令的绑定策略:

  • scope: {@}
    @ 将本地作用域和DOM中的属性值绑定起来;在html中,{{property}}表达式被指定到attr属性,当表达式发生变化时,attr属性也跟着变化,隔离scope中对应的属性也相应的变化。
app.directive('helloWorld', function() {
  return {
    scope: {
      color: '@colorAttr'
    }
  };
});

指令使用:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color"/>
  <hello-world color-attr="{{color}}"/>
</body>

这种方式称为单项绑定,因为这种绑定策略,你只能使用表达式传递给属性,通过scope隔离作用域传递给内部的属性。当父scope的属性发生变化时,你的隔离scope中的属性也跟着变化,也可以在指令内部监控属性的变化,并且触发一些任务。相反,你不能通过指令内部属性的变化来改变父scope的作用域。

  • scope: {=}
    通过=可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定,就像普通的数据绑定一样,本地属性会反映出父数据模型中所发生的改变。
app.directive('helloWorld', function() {
  return {
    scope: {
      color: '='
    }
  };
});

指令使用:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color"/>
  <hello-world color="color"/>
</body>

这种策略称为双向绑定。双向绑定使用的都是对象的引用,而不是字符串。

  • scope: {&}
    通过&符号可以对父极作用域进行绑定,以便在指令模板中运行函数;如果要调用一个参数的方法,需要传递一个对象。
    无参情况
app.directive("direct",function(){
        return{
            restrict: 'ECMA',
            template: '<div>{{ title }}</div>'+'<div><ul><li ng-repeat="x in          contents">{{ x.text }}</li></ul></div>',
            scope:{
              getTitle:'&', 
              getContent:'&'             
         },
            controller:function($scope){ 
               $scope.title=$scope.getTitle();     //调用无参函数  
               $scope.contents=$scope.getContent();    //调用无参函数 
           } 
      } 
 })
.controller("nameController",function($scope){
    $scope.title="标题";
    $scope.contents =[{text:1234},{text:5678}]; 
});

指令使用:

<div ng-controller="nameController">
      <direct get-title="title" get-content="contents"></direct> 
  </div>

1.指令的本地属性(即模板里花括号中的属性)需要从本地取值,所以使用了controller选项,而在controller选项中,两个无参方法分别返回了父级scope中的title字符串和contents对象数组。

2.在HTML中,我们把设置了get-title和get-content的属性值为title和contents,这实际上就完成了与父级scope的绑定,因为我们才可以从那儿取得实质的内容。

带参数的方法

app.directive("direct",function(){ 
return{
            restrict: 'ECMA',
            template: '<div><input ng-model="model"/></div>\
<div><button ng-click="show({name:model})">show</button>',
            scope:{
                show:'&'              
            }                      
         }
    })
    .controller("nameController",function($scope){
        $scope.showName=function(name){ 
          alert(name); 
         } 
     });

指令使用:

<div ng-controller="nameController">
      <direct show="showName(name)"></direct> 
</div>

这个例子中,通过模板中的ng-click触发了show函数并将一个叫做model的对象作为name参数传递了进去,而在html中,我们把show的属性值设为showName(name)。

尾递归(TCO)

递归表示函数调用自身;尾递归表示函数 return 表达式调用自身函数;而尾递归优化(TCO)是解决调用栈溢出的问题,js 解析器为防止函数无线调用,通常会设置一个最大调用次数。如下面这段代码:

function f(n, total) {
  if (n === 1) return total;
  return f(n - 1, n + total);
}

当 n 足够大时,某些浏览器或 node 会抛出调用栈溢出的错误。
image

我们多多少少了解一些尾递归的概念,然而对于尾递归(优化)到底有什么用呢 ❓

递归调用是一种线性调用,在内存中会保存当前调用函数的信息;如:f(x1) -> f(x2) -> f(x3),它的线性调用栈如图:
image

空间复杂度为 O(n)。如果实现尾递归优化,那么在栈中,函数调用栈会共用,空间复杂度为 O(1),因此对内存消耗起到了一定作用。

严格模式
尾递归优化只在严格模式下有效。这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

  • arguments:返回调用时函数的参数。
  • func.caller:返回调用当前函数的那个函数。

尾调用优化发生时,函数的调用栈会被改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

点击查看实现 TCO 的浏览器支持情况,很遗憾 chrome 浏览器未实现TCO

参考:

$elemMatch vs. Dot Notation

$elemMatch 和点运算符都是匹配数组里面嵌套的字段

db.inventory.find( { "instock": { $elemMatch: { qty: { $gt: 10, $lte: 20 } } } } )
db.inventory.find( { "instock.qty": { $gt: 10,  $lte: 20 } } )

两个都是匹配10<qty<=20,查出来的结果却有点不一样;$elemMatch是对数组嵌套的同一条记录的字段值要同时满足条件,即 qty>10 且 qty<=20;点运算符是数组中包含一条记录 a 存在 qty>10 和一条记录 b 存在 qty<=20(a 不一定等于 b)

例如:

> db.inventory.insertMany( [
   { item: "journal", instock: [ { warehouse: "A", qty: 5 }, { warehouse: "C", qty: 15 } ] },
   { item: "notebook", instock: [ { warehouse: "C", qty: 5 } ] },
   { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 15 } ] },
   { item: "planner", instock: [ { warehouse: "A", qty: 40 }, { warehouse: "B", qty: 5 } ] },
   { item: "postcard", instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);

> db.inventory.find( { "instock": { $elemMatch: { qty: { $gt: 10, $lte: 20 } } } } , {_id: 0}) //匹配的结果得在同一个记录里
{ "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] }
{ "item" : "paper", "instock" : [ { "warehouse" : "A", "qty" : 60 }, { "warehouse" : "B", "qty" : 15 } ] }
{ "item" : "postcard", "instock" : [ { "warehouse" : "B", "qty" : 15 }, { "warehouse" : "C", "qty" : 35 } ] }

> db.inventory.find( { "instock.qty": { $gt: 10,  $lte: 20 } } ,{_id: 0}) //匹配的结果可以不在同一个记录里
{ "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] }
{ "item" : "paper", "instock" : [ { "warehouse" : "A", "qty" : 60 }, { "warehouse" : "B", "qty" : 15 } ] }
{ "item" : "planner", "instock" : [ { "warehouse" : "A", "qty" : 40 }, { "warehouse" : "B", "qty" : 5 } ] }
{ "item" : "postcard", "instock" : [ { "warehouse" : "B", "qty" : 15 }, { "warehouse" : "C", "qty" : 35 } ] }

> db.inventory.find( { "instock.qty": 5, "instock.warehouse": "A" } ,{_id: 0}) //匹配的结果可以不在同一个记录里
{ "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] }
{ "item" : "planner", "instock" : [ { "warehouse" : "A", "qty" : 40 }, { "warehouse" : "B", "qty" : 5 } ] }

资料参考
query-array-of-documents

$elemMatch vs dot notation

web 安全知识

  1. cookie
  2. xss
  3. csrf
  4. http/2.0
  5. cors
  6. dos

cookie

cookie是用于服务器对来自客户端请求的一种身份验证;cookie的使用为技术带来了好处,比如会话状态管理、个性化设置、浏览器行为跟踪等,但也引发了一些安全的问题,比如xss攻击。

cookie是由服务器生成并发送给客户端保存,之后的每次浏览器请求都会带上cookie信息,这会造成一定的带宽浪费,而且这些cookie信息都是很容易被获取,所以相应的安全预防是有必要的。

httponly

对于js不需要读取cookie信息的应用,可以将cookie设置成httponly类型,可以在一定程度上阻止跨域脚本攻击(xss)。HttpOnly标志并没有给你提供额外的加密或者安全性上的能力,当整个机器暴露在不安全的环境时,切记绝不能通过HTTP Cookie存储、传输机密或者敏感信息。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
cookie

跨站脚本攻击(cross site scripting)

xss攻击是由于用户输入不合法的数据导致浏览器解析文档造成的攻击。比如当你打开一封Email或附件、浏览论坛帖子时,可能恶意脚本会自动执行,因此,在做这些操作时一定要特别谨慎。
预防xss攻击措施:

  • 对动态载入输出的不可靠的内容进行转义(html/js/attribute/css/url)
  • 把html转换成json形式,读的时候通过json.parse读取
  • 使用内容安全策略(csp)
    xss预防

跨站请求伪造(cross-site request forgery)

csrf是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。比如
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
当你打开含有了这张图片的HTML页面是,如果你已经登录了你的银行帐号并且还有效(而且没有其它验证步骤),你的银行里的钱可能会被自动转走。
预防csrf攻击措施:

http/2.0

http2.0是自从1997年提出http1.1网络协议之后的一个大改版,最初是由spdy协议开发而来的,而spdy是由google开发的,google于2015年2月9日声明chrome浏览器不再使用spdy转而支持http2.0,并从chrome51之后开始使用http2.0协议;http2.0标准是在2015年5月份公开的,2015年底,所有现代浏览器都支持http2.0协议,比如Chrome, Opera, Firefox, Internet Explorer 11, Safari, Amazon Silk, and Edge browsers。截止2017年3月份,一千万个网站中就有13.7%的网站支持http2.
优势

  • 兼容http1.1
  • 支持现有的协议
  • 降低等待时间,提升页面价值速度
    1. 请求头部的数据压缩
    2. 修复http1.x行头部阻塞的缺陷
    3. 单个tcp连接运行发起多个请求

http/2.0 wiki
http2

CORS

出于安全原因,浏览器限制从脚本内发起的跨源http请求,意味着应用程序只能从加载应用程序的同一个域请求资源
跨域资源共享机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。

  • 请求头配置Access-Control-Allow-Origin信息以允许跨域请求

DOS

DOS

首字母检索手机通讯录模板

工作中需要用到类似于手机通讯录按首字母排序的列表,故而发现一些问题,在此记录下来:

问题一:在复制js文件里的strChineseFirstPY的内容时,复制不出来,不管是文件copy,还是内容copy,效果都是为空白,但是这空白又占据字符个数,然后就猜想到这里面的unicode码包含有某种特殊转义符,导致内容空白。所以通过字符串拼接的方法把该unicode码进行加起来,问题解决了。

问题二:把该插件用入到项目中,发现拼音为N开头的中文被排到默认里显示,即#列表显示,经过代码跟踪,发现是代码少了case:N的比较,加上即可。


2016-11-27
今天思考了一下,这些数据列表都是写死的,然后能不能通过动态加载数据显示出来呢?在项目中肯定是不可取的,于是动手尝试一下,思考过程中以为很复杂,写的时候才发现原来不是很难,只需要把数据包装到数组中,然后通过遍历动态生成dom结构,如下:

	var SortList=$(".sort_list");
    var SortBox=$(".sort_box");
    SortList.sort(asc_sort).appendTo('.sort_box');//按首字母排序
    function asc_sort(a, b) {
        return makePy($(b).find('.num_name').text().charAt(0))[0].toUpperCase() < makePy($(a).find('.num_name').text().charAt(0))[0].toUpperCase() ? 1 : -1;
    }

    var initials = [];
    var num=0;
    SortList.each(function(i) {
        var initial = makePy($(this).find('.num_name').text().charAt(0))[0].toUpperCase();
        if(initial>='A'&&initial<='Z'){
            if (initials.indexOf(initial) === -1)
                initials.push(initial);
        }else{
            num++;
        }
    });
    ...

SortList获取页面静态列表,只需要把这个列表改成动态获取,其他不变,如下:

	var chineseArr = [
            {"id":1,name:"涨水"},
            {"id":2,name:"美女"},
            {"id":3,name:"准备"},
            {"id":4,name:"请问"},
            {"id":5,name:"水电费"},
            {"id":6,name:"不能"},
            {"id":7,name:"更好"},
            {"id":8,name:"熬吧"},
            {"id":9,name:"凉快了"},
            {"id":10,name:"潍坊"},
            {"id":11,name:"拉屎"},
            {"id":12,name:"胸围"},
            {"id":13,name:"漂亮"},
            {"id":14,name:"离开"},
            {"id":15,name:"额无法"},
            {"id":16,name:"123"},
            {"id":17,name:"+sdkl"},
            {"id":18,name:"AB"}
            ];
	var SortBox=$(".sort_box");

    var html = [];
    for(var i = 0,len = chineseArr.length; i < len; i++){
        html += `<div class="sort_list">
                    <div class="num_name">${chineseArr[i].name}</div>
                </div>`;
    }
    var SortList=$(html);

    SortList.sort(asc_sort).appendTo('.sort_box');
    function asc_sort(a, b) {
        return makePy($(b).find('.num_name').text().charAt(0))[0].toUpperCase() < makePy($(a).find('.num_name').text().charAt(0))[0].toUpperCase() ? 1 : -1;
    }
    ...

效果和原来一样。(修改后的把头像去掉了)

往事,你好;往事,你孬;往事,再见。

react 父子之间组件通信的意外发现

  1. 场景
    有A,B两个组件,A组件里嵌套B组件,B组件的数据来源于A组件,现在对B组件的数据进行删除,还需要与A组件的数据保持同步,B组件关闭时会被unmount,重新打开B组件时原有的状态丢失。
    image

简便的方法就是把B组件设为无状态的组件,所有状态全部放在A组件里,增加删除什么的方法全部写在A组件里,这是一种不好的写法,违背了react的component**。该属于B组件的操作的状态和方法都应该写在自身组件里。

但是,B组件数据来源A组件,修改了B的数据也要同时修改A的数据,修改了A的数据还要同时反映到B的数据,也就是同一份数据需要应用到两个组件。使用第三方库时,每次打开B组件时B都被重新Mount/Unmount。

S1: A把数据组装成对象:[{}, ...], B直接修改props的数据【ps:一直以为props是read only,没想到object还是可以修改,看来还是没用摆脱js的引用】

class B extends React.Component{
  constructor(props) {
        super(props);
        this.state = {
            tags: [...this.props.tags]
        }
    }
    handleChange(tag) {
        this.setState((prevState) => prevState.tags.map((tagItem) => {
            if (tagItem.key === tag.key) {
                if (!tag.selected && this.props.handleAdd) {
                    this.props.handleAdd(tag.key);
                } else if (tag.selected && this.props.handleDelete) {
                    this.props.handleDelete(tag.key);
                }
                tagItem.selected = !tagItem.selected;
            }
            return tagItem;
        }))
    }

    render(){
        const selectedTags = [], unselectedTags = [];
        props.tags.forEach((tag) => {
            if (tag.selected) {
                selectedTags.push((<CheckableTag
                    key={tag.key}
                    checked={false}
                    onChange={(checked) => this.handleChange(tag)}
                    style={tagStyle}
                >
                    {tag.tag || 'N/A'}
                </CheckableTag>))
            } else {
                unselectedTags.push((
                    <CheckableTag
                        key={tag.key}
                        checked={false}
                        onChange={(checked) => this.handleChange(tag)}
                        style={tagStyle}
                    >
                        {tag.tag || 'N/A'}
                    </CheckableTag>
                ))
            }
        })
    
        return (
            <div>
                <Panel header="Selected Columns" bsStyle="success">
                    {selectedTags}
                </Panel>
                <Panel header="Unselected Columns" bsStyle="danger">
                    {unselectedTags}
                </Panel>
            </div>
        );
    }
}

class A extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            data: [{}, ...]
        }
    }

    handleDelete(){
        // this.state.data.splice(index, 1)
    }

    handleAdd(){
        // this.state.data.push()
    }

    render(){
        return (<OverlayTrigger trigger='click' placement="bottom" overlay={
                        <Popover id="tooltip" title="Columns Filter">
                            <ColumnTag
                                data={this.data}
                                handleDelete={this.handleDelete}
                                handleAdd={this.handleAdd}/>
                        </Popover>
                    } rootClose={true}>
                        <Button bsStyle="primary" >Columns Filter</Button>
                    </OverlayTrigger>)
    }
}

S2: 在A组件里获取B组件实例,再次加载时使用上一次保存的实例

class B extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            tags: [...this.props.data],
        }
    }

    handleChange(tag, checked) {
        const tags = this.state.tags;
        const find = tags.find(item=>item.key===tag.key);
        if (checked) {
            find.flag = true;
            this.props.handleAdd.call(this, tag.key);
        } else {
            find.flag = false;
            this.props.handleDelete.call(this, tag.key);
        }

        this.setState({tags: [...tags]})
    }

    render() {
        const { tags } = this.state;
        const selectedTags=[], unselectedTags = [];
        tags.forEach(tag=>{
            if(tag.flag){
                selectedTags.push(<CheckableTag
                    key={tag.key}
                    checked={false}
                    onChange={(checked) => this.handleChange(tag, false)}
                    style={tagStyle}
                >
                    {tag.tag||'N/A'}
                </CheckableTag>)
            }else{
                unselectedTags.push(<CheckableTag
                    key={tag.key}
                    checked={false}
                    onChange={(checked) => this.handleChange(tag,true)}
                    style={tagStyle}
                >
                    {tag.tag||'N/A'}
                </CheckableTag>)
            }
        })
        
        return (
            <div className={css.tag}>
                <h2>{this.props.title}</h2>
                <h3>Selected Columns</h3>
                {selectedTags}
                <h3>Unselected Columns</h3>
                {unselectedTags}
            </div>
        );
    }
}

B.propTypes = {
    title: PropTypes.string.isRequired,
    handleDelete: PropTypes.func.isRequired,
    handleAdd: PropTypes.func.isRequired,
}


class A extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            data: [{}, ...],
            colContent: 'test',
        }
    }

    handleDelete(){
        // this.state.data.splice(index, 1)
    }

    handleAdd(){
        // this.state.data.push()
    }

    columnFilter = ()=>{
        let colContent = <span></span>;
        if(!React.isValidElement(this.state.colContent)){
            colContent = <Popover id="tooltip" style={{maxWidth: '50%', maxHeight: '60%', left: '40%'}}>
                    <B title="Columns Filter"
                        data={this.state.tags}
                        handleDelete={this.handleDelete}
                        handleAdd={this.handleAdd}/></Popover>
            this.setState({colContent})
        }else{
            colContent = this.ref._overlay.props.children //【此种方式可以保存instance】
            this.setState({colContent})
        }
    }

    render(){
        return (
            <OverlayTrigger ref={node=>this.ref=node} trigger='click' placement="bottom" overlay={this.state.colContent} rootClose={true}>
                <Button bsStyle="primary" onClick={this.columnFilter}>Columns Filter</Button>
            </OverlayTrigger>)
    }
}

gulp & webpack

1. gulp安装

在深入研究配置任务之前,首先进行gulp安装
$ npm install gulp -g
这种命令安装是全局安装,可以访问gulp的客户端,我们需要在本地工程目录下进行本地安装,使用cd切换到工程目录运行如下命令(需先手动新建一个package.json文件
$ npm install gulp --save-dev
安装完成后,package.json会自动生成一些gulp的配置信息

2. 插件安装

通过安装插件来实现以下任务:

  • Sass compile (gulp-ruby-sass)
  • Autoprefixer (gulp-autoprefixer)
  • Minify CSS (gulp-cssnano)
  • JSHint (gulp-jshint)
  • Concatenation (gulp-concat)
  • Uglify (gulp-uglify)
  • Compress images (gulp-imagemin)
  • LiveReload (gulp-livereload)
  • Caching of images so only changed images are compressed (gulp-cache)
  • Notify of changes (gulp-notify)
  • Clean files for a clean build (del)

运行以下命令进行本地安装:
$ npm install gulp-ruby-sass gulp-autoprefixer gulp-cssnano gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev
安装的插件信息都会在package.json文件里体现出来

3. 使用插件

在当前工程目录新建一个gulpfile.js文件,编写加载代码,如下:

var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    cssnano = require('gulp-cssnano'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

4. 创建task

4.1 Compile Sass, Autoprefix and minify

首先,将配置Sass编译,实现一个expanded的样式、可自动添加前缀且最小化文件保存到本地一个Sass编译器,

gulp.task('styles', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version'))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(rename({suffix: '.min'}))
    .pipe(cssnano())
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(notify({ message: 'Styles task complete' }));
});

对以上代码做一些说明

gulp.task('styles', function() { ... )};

gulp.taskAPI是用来声明创建一个task,便于能够在终端运行$ gulp style

return sass('src/styles/main.scss', { style: 'expanded' })

这是gulp-ruby-sass插件的一个API,用于设置编译的源文件和一些参数;在收到编译结束通知之前,此API会异步返回一个流,确定这个任务是全部完成;

.pipe(autoprefixer('last 2 version'))

使用pipe方法将源文件输出到插件中;

.pipe(gulp.dest('dist/assets/css'));

gulp.destAPI用于设置文件输出路径;

4.2 JSHint, concat, and minify JavaScript

您是不是很好奇使用gulp怎么创建task,接下来我们将通过对脚本提示、拼接、最小化进行一些演示:

gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(rename({suffix: '.min'}))
    .pipe(uglify())
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

gulp.src用于指定要编译的文件路径,我们需要为jslint声明reporter,此处使用了默认值,更多信息请参考JSLint

4.3 Compress Images

接下来设置图片压缩:

gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
    .pipe(gulp.dest('dist/assets/img'))
    .pipe(notify({ message: 'Images task complete' }));
});

使用imagemin插件来进行图片压缩,我们可以使用缓存来进行一些优化,因此可以使用gulp-cache插件,然后修改下面代码

//before
.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))

//after
.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))

4.4 Clean up

在部署之前,清理一些源文件和构建文件是一个良好的作风,只保留线上需要的文件

gulp.task('clean', function() {
    return del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img']);
});

我们没有使用gulp插件,而是使用gulp内置的模块;最后返回以确保task完成

4.5 The default task

使用$ gulp运行默认的task,执行以上创建的task,

gulp.task('default', ['clean'], function() {
    gulp.start('styles', 'scripts', 'images');
});

注意,默认的task多了一个数组,表示在执行start之前会先执行数组里的task

4.6 Watch

表示对变化的文件进行一些监听;首先要新建一个task,使用gulp.watchAPI进行文件监听

gulp.task('watch', function() {

  // Watch .scss files
  gulp.watch('src/styles/**/*.scss', ['styles']);

  // Watch .js files
  gulp.watch('src/scripts/**/*.js', ['scripts']);

  // Watch image files
  gulp.watch('src/images/**/*', ['images']);

});

使用gulp.watch关注变化的文件和通过依赖的数组定义task,然后使用$ gulp watch就可以对改变的文件执行相应的task

4.7 LiveReload

gulp可以实行对变化的文件进行自动刷新,将修改watch配置 LiveReload server

gulp.task('watch', function() {

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/, reload on change
  gulp.watch(['dist/**']).on('change', livereload.changed);

});

前提是需要下载和启用LiveReload browser插件,你也可以手动配置

资料参考

HTTP缓存配置

新到一个工作环境,被要求解决浏览器缓存加载的问题。拿到一个新项目,包含各种资源文件:html/css/js/python/node,之前是只需要做前端,现在是前后都得干,有点不习惯,先熟悉一波文件目录结构,还是有规律可寻的。

问题:本地代码部署到线上,其他人使用浏览器打开没有加载最新部署的资源,还是使用浏览器缓存的文件,需要通过f12 + disabled cachectrl+f5来强制刷新加载。

过程:一般想要重新加载资源文件或请求API,是在后面加个时间戳来更改请求地址以实现浏览器缓存不存在同样的请求地址,这种解决方案我只是看看,真的做起来不太现实,文件这么多,API又何其多;就剩下以前想尝试但又没机会尝试的方法--通过配置HTTP header来解决浏览器缓存问题。

环境:系统ubuntu16.04 / 服务器Apache2.4.18
几个请求头的知识点:

  Etag: 根据文件内容生成的一个值,比根据缓存时间判断更划算

  Expires: 指定了一个日期/时间, 在这个日期/时间之后,HTTP响应被认为是过时的;需和Last-Modified结合使用。用于控制请求文件的有效时间,当请求数据在有效期内时客户端浏览器从缓存请求数据而不是服务器端.当缓存中数据失效或过期,才决定从服务器更新数据。HTTP 1.0 版本

  Cache-Control: 用于在http 请求和响应中通过指定指令来实现缓存机制。HTTP 1.1版本,资源在本地缓存多少秒。

  If-None-Match: 值为上一次响应头返回的etag值,作为请求头发送

  Last-Modified: 响应头返回的文件最后修改时间

  If-Modified-Since: 值为上一次响应头返回的修改时间,作为请求头发送

更多请参考mdn

然后就是更改Apache的配置信息,找到Apache的安装目录:

  1. 在/etc/apache2/mods_enabled/headers.load 配置全局的消息头
    image
    Max-age:单位是秒
  2. 在/etc/apche2/apache2.conf 根据不同资源文件配置不同的消息头
    image
    配置了ETag消息头却没有加载最新资源文件,添加如下配置信息即可解决
//https://stackoverflow.com/questions/29127144/cant-cache-resource-when-having-both-gzip-and-etag
<IfModule mod_headers.c>
    Header unset ETag
</IfModule>
FileETag None

表示不发送etag响应头

从Apache2.4.0之后,由于Etag默认被加上gzip后缀,导致请求不会发送304响应码
image

 https://httpd.apache.org/docs/trunk/mod/mod_deflate.html#deflatealteretag

发现了问题就去解决问题,不去解决就是耍无赖,毕竟响应码还是很重要的一个信息

image
开启mod_header.c的命令:sudo a2enmod headers

修改etag的生成规则

先了解etag的原理
image

https://httpd.apache.org/docs/2.4/zh-cn/mod/core.html#fileetag

简而言之,etag由文件在系统的索引节点+文件最后修改的时间+文件大小组成的
修改如下
image

http://httpd.apache.org/docs/current/mod/mod_headers.html

设置expires的值

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault A1000
</IfModule>

M means that the file's last modification time should be used as the base time, and A means the client's access time should be used

https://httpd.apache.org/docs/trunk/mod/mod_expires.html

sudo a2enmod expires 执行该命令启用模块,然后重启服务

以下仅供参考:

ExpiresActive On
ExpiresDefault A2592000
# GIF有效期为1个月
ExpiresByType image/gif A2592000
# HTML文档的有效期是最后修改时刻后的一星期
ExpiresByType text/html M604800
#以下的含义类似
ExpiresByType text/css "now plus 2 months"
ExpiresByType text/js "now plus 2 days"
ExpiresByType image/jpeg "access plus 2 months"
ExpiresByType image/bmp "access plus 2 months"
ExpiresByType image/x-icon "access plus 2 months"
ExpiresByType image/png "access plus 2 months"

问题发现(未解决)
经过本地和线上测试发现:客户端获取最新资源的措施,服务端已设置请求头ETAG来判断是否使用最新文件还是使用浏览器缓存文件,且浏览器已生效,IE和Firefox是没有问题的,chrome现在的情况是有时生效有时不生效,不生效是因为chrome请求头会报如下错误信息
image
综合网上解决方案,发生错误的原因:

  1. 请求的资源可能被(扩展插件或其他什么机制)屏蔽掉导致发送伪请求。经浏览器查看,将插件禁用,还是会报错
  2. 资源加载时间太长,优化加载时间。经本地测试,资源所加载时间都不超过1s

猜测有可能是网络问题,因为本地测试一切正常,只有线上才会出现改现象,而且线上服务器不再国内

ajax(异步js和xml)

AJAX全称: Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)

  • 用于创建快速动态网页
  • 通过ajax实现网页异步刷新

1. 创建XMLHttpRequest对象

var xhr;
if (window.XMLHttpRequest)
{
    //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xhr=new XMLHttpRequest();
}
else
{
    // IE6, IE5 浏览器执行代码
    xhr=new ActiveXObject("Microsoft.XMLHTTP");
}

2. 请求

xhr.open("GET","ajax_info.txt",true);  //arg1:请求类型;arg2:请求url;arg3:是否异步
xhr.send();  //将请求数据发送到服务器,请求类型为post时,需上传所传送参数

2.1 请求类型

  • get请求类型,上传参数是追加到URL后面;
  • post请求类型,上传参数是通过send方法传送的;
  • 当使用get请求时,可能会得到缓冲中的数据,为避免拿到缓冲数据,可以给url添加一个唯一的id,比如时间戳或随机数;post请求不会从缓冲中拿数据;
  • 当使用post请求传送表单数据时,需设置Content-Type头部信息xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  • 在XMLHttpRequest2中,使用FormData对象传送表单数据,可以不用设置请求头信息,xhr.send(new FormData(form))
  • 传送的数据最好经过encodeURIComponeng编码之后传送,防止不合法字符导致请求错误;

2.2 是否异步

  • async=false
    接受到响应后,首先检查status属性,不需要检查readystatus属性的变化值
     if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
     	//do
     }else{
     	//else
     }
    因为是同步,所以以上代码直接写在send()方法后面
  • async=true
    异步请求,需要检测readystatus属性的变化值
    • 0:未初始化,尚未调用open方法
    • 1:启动,已经调用open方法,但未调用send方法
    • 2:发送,已经调用send方法,尚未接收到响应
    • 3:接收,已经接受到部分响应数据
    • 4:完成,成功接受到全部响应数据
      因为readystatus值时刻在变化,所以在创建对象之后就开始监听值变化
     var xhr = xxx;
     xhr.onreadystatuschange = function(){
     	if(xhr.readystatus === 4){
     		if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
     			//do
     		}else{
     			//else
     		}
     	}
     }

3. 响应

响应的数据会自动填充到xhr对象的属性:responseText/responseXML

IScroll & ngInfiniteScroll

1. IScroll

2. ngInfiniteScroll

iscroll用法

推荐使用的html格式

<div id="wrapper">
    <ul>
        <li>...</li>
        <li>...</li>
        ...
    </ul>
</div>

js初始化iscroll对象,有三种方式如下:

  • 使用id选择器
<script type="text/javascript">
var myScroll = new IScroll('#wrapper');
</script>
  • 使用dom对象
var wrapper = document.getElementById('wrapper');
var myScroll = new IScroll(wrapper);
  • 使用类选择器
var myScroll = new IScroll('.wrapper');

iscroll可选参数配置

初始化时给第二个参数传递一个对象

var myScroll = new IScroll('#wrapper', {
    mouseWheel: true,
    scrollbars: true
});
  1. mouseWheel:表示是否支持滑轮滚动
  2. scrollbars:表示是否显示滚动条
  3. bounce:表示是否反弹
  4. click:表示是否支持鼠标点击
  5. hScroll:是否支持水平滚动
  6. vScroll:是否支持垂直滚动
  7. hScrollbar:是否显示水平滚动条
  8. vScrollbar:是否显示垂直滚动条
  9. momentum: 是否惯性拖动
  10. fadeScrollbars:是否有淡入淡出效果
  11. interactiveScrollbars: 滚动条是否可拖拽
  12. shrinkScrollbars:滚动条是否收缩,有效值‘clip’、‘scale’
  13. probeType: 1滚动不繁忙的时候触发;2滚动时每隔一定时间触发;3每滚动一像素触发一次(使用 iscroll-probe.js)
  14. keyBindings:键盘绑定参数 {pageUp:33,pageDown:34,end:35,home:36,left:37,up: 38,right:39,down:40}

iscroll事件

通过scroll的on方法来注册事件

myScroll = new IScroll('#wrapper');
myScroll.on('scrollEnd', doSomething);

  • beforeScrollStart:用户触摸屏幕scroll初始化之前
  • scrollCancel:scroll初始化但是还没有执行
  • scrollStart:scroll初始化
  • scroll:内容处于滑动中
  • scrollEnd:滑动结束
  • flick:轻击屏幕左/右
  • zoomStart:缩放开始
  • zoomEnd:缩放结束

three 相关资料

directive在不同controller使用

在angular的路上,一步步摸索,在工作中慢慢尝试。
自定义指令貌似很简单,不过肯定还有不为我知的地方,先从简单入手,自定义一个load指令:

var app = angular.module('app',[]);

app.directive('load', function(){
	return {
		restrict:'E',
		link:function(scope,element,attr){
			element.bind('mouseenter', function(){
				scope.$apply(attr.howtoload);  //需小写,对应属性howToLoad
			});
		}
	}
})

howtoload一定要在apply方法里,否则angular是无法对其进行监听。这里使用到link函数,涉及一点angular的生命周期,要想使用指令对事件进行操作,需要在指令编译后,然后用link函数创建可以操作DOM的事件。下面使用指令在控制器进行声明:

<div ng-controller="loadCtrl1">
	<load howToLoad="load1()">loading...</load>
</div>
<div ng-controller="loadCtrl2">
	<load howToLoad="load2()">loading...</load>
</div>

定义控制器方法

app.controller('loadCtrl1',['$scope', function($scope){
	$scope.load1 = function(){
		console.log('loading...');
	}
}])

app.controller('loadCtrl2',['$scope', function($scope){
	$scope.load2 = function(){
		console.log('loading...222');
	}
}])

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.