Coder Social home page Coder Social logo

bojue.github.io's Introduction

bojue.github.io's People

Contributors

bojue avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

peterpanbest

bojue.github.io's Issues

理解JavaScript立即执行函数

理解JavaScript立即执行函数

IIFE (Immediately Invokable Function Expressions):是在函数声明后立即调用的函数表达式。

立即执行函数通常包含两种使用格式,具体使用那一种风格可以根据个人习惯和团队规范选择:

// 第一种:
(function() {/* */})();

// 第二种:
(function(){/* */}())

// 也可以使用箭头函数声明
;(() => {/* */}())

数学符号都会导致函数立即执行,因为JavaScript引擎会将函数判断为表达式,而不是函数声明。

!function(){}();
+function(){}();
-function(){}();
~function(){}();

new关键字也会导致函数被立即执行。

new function(){ /* code */ }
new function(){ /* code */ }() 

IIFE的进阶使用是把它们作为函数调用进行传参调用:

var val = "global";
(function(global){
    var val = 'function';
    console.log(val) //function
    console.log(global.val) //global
}(window))

console.log(val)  //global

IIFE函数的作用:

  • 避免污染全局
    在JavaScript函数内部,会形成一个块级作用域的概念,可以利用IIFE函数避免全局污染,比如JQuery插件的封装模式:
(function($){
    ```
   //JQuery插件
})(jQuery);
  • 倒置代码的运行顺序

这是一个《你不知道的JavaScipt》上的一个经典的例子, 在UMD(Universal Module Definition)项目被广泛使用:

var val = 'global';
(function(fun){
    fun(window);
})(function def(global) {
    var val = 'function';
    console.log(val); //function
    console.log(global.val); // global
})
  • 模块模式

IIFE函数可以创建单例对象,我们成为模块模式。

var count = (function() {
    var initVal = 0;
    var number = initVal;
    return {
        init: function() {
            number = initVal; 
        },
        get: function() {
            return number;
        },
        set: function(val) {
            number = val;
        },
        add: function() {
            return ++number;
        }
    }
}());

console.log(count.get()); //0
count.set(10);
console.log(count.add());//11
count.init();
console.log(count.add());// 1

参考

深入理解JavaScript的this

深入理解JavaScript的this

this是在JavaScrip执行过程中进行绑定的,与this的声明位置和词法作用域没有关系。

This是JavaScript一个重要的概念和魔法,深入理解this的使用方法,才能写出更健壮的JavaScript代码。按照JavaScript的执行情况this的绑定规则可以分为:默认绑定,隐式绑定,显示绑定,new绑定,ES6箭头函数绑定。

默认绑定

实际上JavaScript的默认绑定是指不能归纳为其他绑定类型的this绑定。

默认绑定的this绑定的是全局变量,全局变量就是全局对象的一个同名属性。

var str = "test";
function getStr() {
    var str = "getStr";
    console.log(this.str); // test
    console.log(str) // getStr
}
getStr();

但是在严格模式下,this绑定的不是全局对象,而是undefined,所以此时使用this默认绑定会报错。

var str = "test";
function getStr() {
    "use strict"
    var str = "getStr";
    console.log(str); // getStr
    console.log(this.str); // 报错
}
getStr();

函数在非严格模式下声明,但是在严格模式下调用this绑定的是全局对象。

var str = "test";
function getStr() {
    var str = "getStr";
    console.log(this.str); // test
    console.log(str) // getStr
}
(function() {
    "use strict"
    getStr();
})()

隐式绑定

调用位置决定了this的绑定,当this的调用发生在上一个函数调用生成的上下文中,会发生隐式调用,即将this绑定在该上下文对象上。

var str = "str";
function consoleStr() {
    console.log(this.str)
}
let getStr = {
    str: "getStr",
    conStr: consoleStr
}
getStr.conStr() // getStr

let otherStrFun = getStr.conStr;
otherStrFun();  // str

我们分析一下隐式绑定的整个过程:

  1. consoleStr函数被声明之后,被赋值给getStr对象的conStr属性,getStr.conStr属性拥有consoleStr函数的一个引用
  2. 我们在调用getStr对象的conStr属性时,本质上调用的是consoleStr函数的引用,此时consoleStr函数执行上下文对象是getStr
  3. 所以consoleStr函数的this绑定在getStr对象上
  4. this.str === getStr.str成立。

在隐式绑定的时候,一定要理解隐式丢失现象,搞清楚其中的原理,我们分析如下隐式丢失现象的演示代码:

var str = "str";
function consoleStr() {
    console.log(this.str)
}
let getStr = {
    str: "getStr",
    getStrFun: consoleStr
}
let otherStrFun = getStr.getStrFun;
otherStrFun(); // str
  1. consoleStr函数的引用赋值getStr的getStrFun的属性
  2. 将getStr对象的getStrFun属性赋值给otherStrFun变量的时,其实是将consoleStr函数的引用赋值给了otherStrFun,所以这样的赋值结果就是 otherStrFun = consoleStr,也就是otherStrFun也是consoleStr的一个引用
  3. 这样在全局执行otherStrFun()时this转化成了默认绑定,在非严格模式下绑定全局对象。

显示绑定

我们可以使用call(),apply()方法强制实现this的绑定。

function customApi() {
    let CUSTOME_PER = "API_";
    console.log(CUSTOME_PER + this.api)
}
var apiObj = {
    api: 'person'
}
customApi.call(apiObj); //API_person

call()和apply()两个方法的使用基本相同,区别在于后面的参数,但是两个方法的第一个参数是一个对象,并将this绑定到这个对象上。

显示绑定让我们可以实现很实用的功能,主要是我们清楚当前的this绑定的是哪个对象,不会出现隐式绑定时出现的绑定丢失等问题。

需要注意的是当我们将null, undefined作为参数传入call,apply的时候,this绑定到全局对象上。

new绑定

使用new构造函数对于JavaScript来说更准确的表述应该是对函数的构造调用。

JavaScript没有类的概念,我们在使用JavaScript的new关键字创建对象的时候,并不会像Java类语言创建一个新的实例,对于JavaScript的new创建方式来说,这只不过是调用了一个普通的JavaScript函数而已。

function Person(name, age) {
    this.name = name;
    this.age = age;
}
let person = new Person('zhangsan', 29)

通过上面的代码,可以可以分析一下new构造调用创建新对象的整个过程:

  1. 创建新的对象,如:var obj = new Object()
  2. obj的_proto_指向构造函数的原型对象
  3. this指向obj
  4. 返回obj(没有返回其他内容的时候)

ES6箭头函数

箭头函数:不会创建自己的this,它只会从自己的作用域链的上一层继承this。

name = 'windowName'
const person = {
    getName: ()=> {
        let name = 'localName';
        console.log(this.name)
    }
}
person.getName();  //windowName

箭头函数的this不能使用call,apply更改this的绑定

参考

自己实现一个JavaScript Promise类

Promise对象用于表示一个异步操作的最终状态以及操作的值。Promise本质上是一个绑定了回调的对象,区别于将回调传入函数内部。

Promise 属性和方法

我们通过构造函数去创建promise实例:

let promise = new Promise( function(resolve, reject) {...} /* executor */ );

Promise构造函数传入一个参数executor函数,executor是带有resolve和reject两个参数的函数。在构造函数返回Promise实例对象前executor被调用,当前状态被初始化成pedding状态,executor内部执行一些异步操作,当异步操作执行完毕根据执行情况:当成功执行则调用resolve函数将promise状态更新为fulfilled,否则执行reject函数设置promise状态为rejected。

一个Promise只有三种状态:

  1. pedding:初始状态
  2. fulfilled:操作成功完成
  3. rejected:操作失败

Promise API

a. Promise方法

方法 描述
Promise.reject(reason) 返回一个Promise对象,状态设置为失败并传递失败原因给处理函数
Promise.resolve(value) 返回一个promise对象,能够将value以Promise是形式使用
Promise.all(iterable) iterable 是一个数组对象,只有当iterable每个promise对象都成功执行才会触发,返回一个数组对象保存promise对象数组的执行结果,和iterable每个对象的顺序保持一致
Promise.race(iterable) 返回一个Promise,按照iterable数组任意promise对象最先执行完毕的结果立即返回,成功执行返回执行结果还是失败返回原因

b. 构造函数

构造函数 描述
new Promise( function(resolve, reject)){} 通过new关键字创建实例对象,resoleve是异步函数成功执行调用,否则执行reject

c. 原型链方法

方法 描述
promise.then(onFulfilled, onRejected) 当promise解析完成,则调用onFulfilled,如果promise拒绝,则执行onRejected,两个都是可选的参数
promise.catch promise拒绝,等价于 promise.then(undefined, onRejected)

动手实现一个Promise类

我们已经整理了Promise对象的属性和方法,已经promise对象从原型链继承的属性和方法,现在我们需要一步一步自己去实现一个Promise类。

const STATE = {
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
}
const emptyObj = {};

function handleFun(fun, resolve, reject) {
    try {
        fun(resolve, reject);
    } catch (err) {
        reject(err);
    }
}
function getFun(fun) {
    return typeof fun === 'function' ? fun : function(e) {return e};
}

function PromiseFun(fu) {
    if(fu === emptyObj) return;
    this.defaultState = STATE.PENDING;
    this.state = STATE.PENDING;
    this.data = 0;
    this.onFulfilledList = [];
    this.onRejectedList = [];

    const resolve = (val) => {
        if(this.state !== STATE.PENDING) return;
        this.state = STATE.PENDING;
        this.data = val;
        this.onFulfilledList.forEach(fun => {
            fun(val);
        })
    }
    const reject = (reason) => {
        if(this.state !== STATE.PENDING) return;
        this.state = STATE.REJECTED;
        this.onRejectedList.forEach(fun => {
            fun(reason);
        })
    }
    handleFun(resolve, reject);
} 

PromiseFun.prototype.then = function(onFulfilled, onRejected) {
    let self = this;
    onFulfilled = getFun(onFulfilled);
    onRejected = getFun(onRejected);
    switch(this.state) {
        case STATE.PENDING:
            let fun_pending = function(resolve, rejected) {
                self.onFulfilledList.push(function(val) {
                    try {
                        let data = onFulfilled(self.data);
                        if(data instanceof PromiseFun) {
                            data.then(resolve, reject);
                        } else {
                            resolve(data);
                        }
                    } catch (err) {
                        reject(err);
                    }
                })
                self.onRejectedList.push(function(val) {
                    try {
                        let data = onRejected(self.data);
                        if(data instanceof PromiseFun) {
                            data.then(resolve, reject);
                        } else {
                            reject(data)
                        }
                    } catch (err) {
                        reject(err);
                    }
                })
            }
            return new PromiseFun(fun_pending);
        case STATE.FULFILLED:
            let fun_fulfilled = function(resolve, reject) {
                try {
                    let data = onFulfilled(self.data);
                    if(data instanceof PromiseFun) {
                        data.then(resolve, reject);
                    }else {
                        resolve(data);
                    }
                } catch (err) {
                    reject(err);
                }
            }
            return new PromiseFun(fun_fulfilled);
        case  STATE.REJECTED:
            let fun_rejected = function(resolve, reject) {
                try {
                    let data = onRejected(self.data);
                    if(data instanceof PromiseFun) {
                        data.then(resolve, reject);
                    }else {
                        reject(data);
                    }
                } catch(err) {
                    reject(err);
                }
            }  
            return new PromiseFun(fun_rejected); 
    }
}

PromiseFun.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}

PromiseFun.resolve = function(value) {
    return new PromiseFun(function(resolve, rejected) {
        resolve(value);
    }) 
}

PromiseFun.reject = function(reason) {
    return new PromiseFun(function(resolve, rejected) {
        rejected(reason);
    })
}

PromiseFun.all = function(promiseArr) {
    if(!Array.isArray(promiseArr)) return;
    let len = promiseArr.length;
    let count = 0;
    let resultArr = [];
    for(let i = 0; i < len; i++) {
        let item = promiseArr[i];
        PromiseFun.resolve(item).then(function(val) {
            count++;
            resultArr[i] = val;
            if(count === len) {
                return reason(resultArr);
            }
        }, function(reason) {
            reject(reason)
        })
    }

}

参考

  1. 使用Promise | MDN
  2. Promise | MDN
  3. JavaScript Promise简介
  4. Promises/A+规范
  5. 剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类
  6. promise Core.js
  7. Promise简易实现

前端路由实现方式

路由:根据不同的url地址,显示不同的页面或者更新局部视图,呈现出来不同的内容。前端路由的实现方式分为服务端,Hash,History三种常见的路由实现方式。

服务端

服务器端路由管理,常见的开发模式是前端根据url的不同,使用ajax发起异步请求,获取不同的页面资源,前端获取资源后更新页面。

后端路由处理,一般是基于前后端没有分离的项目,html和数据绑定发生在后端(后端渲染),有利于SEO,因为每次发送请求都需要获取资源,对服务器造成资源浪费,前端页面可能因为网速造成延迟,页面局部视图更新,ajax请求不同保存当前的请求状态,不能使用浏览器前进后退快捷键操作。

server路由处理实现类似于下面实现:不同的url请求路径,返回不同的模板

app.get('', function (req, res) {
    res.header('Access-Control-Allow-Origin', '*');
    res.sendFile( __dirname + "/" + "index.html" );
})

app.get('/home.html', function (req, res) {
    res.header('Access-Control-Allow-Origin', '*');
    res.sendFile( __dirname + "/" + "pages/home.html" );
})

app.get('*', function (req, res) {
    res.header('Access-Control-Allow-Origin', '*');
    res.sendFile( __dirname + "/" + "pages/404.html" );
})

Hash

在单页面(SPA)开发中,通过Hash可以实现前端路由,hash路由形如:http:localhost:8100/#/home, 在url后缀存在#(锚点),用来做页面定位,即根据页面id将该元素所在的区域展示在可视区域,#后面内容的改变不会发送请求到服务器。

前端路由需要实现一下:

根据不同的hash展示对应的页面
监听hash值的改变
保存当前url的请求状态或者参数(比如页面刷新和分享链接,别人可以获取同样的内容)
可以实现浏览器的前进后退功能
原理: 页面hash值可以通过 window.location.hash 属性获取,当url的hash值发生变化,会触发window对象的hashchange事件,通过监听 hashchange 事件,操作 window.location.hash 属性可以实现

route.js

function Route(params) {
    if(!params){
        console.log("请检查初始化数据")
        return false;
    }
    this.registeredRoute = [];
    this.contentId = params.contentId;
    this.routes = params.routes;
    this.devStatus = params.devStatus || 'none'
    this.otherRouter = params.otherRouter;
    this.init();
} 

Route.prototype = {
    constructor: Route,
    init: function()  {
        window.addEventListener('hashchange', (function(event){ 
            var currentHash = location.hash.substring(1) || this.otherRouter;
            var route = this.routes && this.routes.find(item => item['path'] === currentHash);
            if(this.devStatus === 'log') {
                console.log("hash has been changed to:", currentHash)
            }
            if(route) {
                this.activeRoute();
                this.render(route)
            }

        }).bind(this))
        var initEvent = new Event('hashchange');
        window.dispatchEvent(initEvent);
    },
    //更新视图
    render(route) {
        if(!$){
            console.log("请确保项目正确引入jQuery")
            return false;
        }
        var _routeContent =  $(`#${this.contentId}`);
        if(_routeContent) {
            var currentView = `<div>current page: ${route['path']}</div> 
            <div>Params:${JSON.stringify(route['params'])} </div>
            <div>View:${route['component']}</div>`;
            _routeContent.html(currentView)
        }else {
            console.log("请绑定需要更新的视图区域");  
        }
    },
    //当前激活路由样式
    activeRoute() {
        var _routeList = $(".route") || [];
        for(var i=0; i< _routeList.length;i++) {
            var _item = _routeList[i];
            var _classList = _item.classList;
            var _attr = 'data-route-param';
            var _defActice = !location.hash && _aDome['context'].getAttribute(_attr )==='home');
            var _aDome = $(_item.getElementsByTagName("a") && $(_item.getElementsByTagName("a")[0]);
            var _activeBool = _aDome['context'].getAttribute(_attr) === location.hash.substring(1)
            || _defActice;
            if(_activeBool) {
                _classList.add('active')
            } else {
                _classList.remove('active');
            }
        }
    }

}

index.html

<div id="route-content"></div>
<script>
window.onload = function(){
   //路由列表
   var routes = [
        {
            path:'home',
            params: {
                id:1
            },
            component: '<dev>home page </dev>'
        },
        {
            path:'list',
            params: {
                id:2
            },
            component: '<dev>list page </dev>'
        },
        {
            path:'about',
            params: {
                id:3
            },
            component: '<dev>about page </dev>'
        },
        {
            path:'info',
            params: {
                id:4
            },
            component: '<dev>info page </dev>'
        }
    ];
    var _routeContent =  $("#route-content");
    if(!_routeContent) {
        console.log("请检查是否存在#route-content视图区域")
    }
    var route = new Route({
        contentId: 'route-content',
        routes: routes, //路由集合
        otherRouter: 'home',//默认路由
        devStatus: 'log' //设置开发模式
    });
}
</script>

History

window.history (window是浏览器的全局对象,所以window.history和history相同)是浏览器提供的用来记录和操作浏览器页面历史栈的对象的接口,提供了常用的属性和方法:

history.back();     //回退
history.go(-1);     //等同于history.back();
history.forward();  //前进
history.go(1); //等同forward()
window.history.length; //历史栈页面的数量

H5对History进行了扩展,增加了两个重要的新的方法:

History.pushState()  //浏览器历史记录压栈,增加一条历史记录
History.replaceState() //浏览器历史记录最后一条数据更新,替换当前历史记录

操作pushState和replaceState方法会触发popstate事件,页面刷新不同浏览器事件监听存在差异,Chrome和Safari会触发popstate事件,Firefox不会触发。 我们可以通过pushState(),replaceState()记录和更新当前url和参数; pushState(),replaceState()包含三个参数:

state:存储当前参数的JSON
title:短标题,浏览器实现不统一有些fireFox会直接忽略,可以设置为null做占位,
url:当前url,更新浏览器url的值

总结

  • hash 路由实现: 兼容性比较好,url比较丑陋,不能使用浏览器栈操作前进后退
  • History 路由实现: 比较直观,需要服务器端配合,用户体验好,响应快,不需要每次发送服务器请求,通过操作浏览器历史栈完成页面跳转,低版本浏览器不支持H5特性,建议使用Hash

JavaScript设计模式-Mixin模式

JavaScript设计模式-Mixin模式

JavaScript没有类似概念,无法在语言层面实现类的继承和多态等行为,JavaScript只有对象,一个对象无法直接复制到其他对象。JavaScript的Mixin(混入)模式是为了模拟类的复制行为。

显示混入

在包含类的类C语言中(Python,C++,Java)定义了类的概念,通过类的继承或者多重继承,子类对象复制了父类定义的属性和方法。

在JavaScript语言中,Mixin的显示混入是模拟类的多重继承的机制。

function minxin(sourceObj, targetObj, rewriteBool) {
    if(!(sourceObj && targetObj)) return ;
    for(let key in sourceObj) {
        if(rewriteBool) {
            targetObj[key] = sourceObj[key];
        } else if(!(key in targetObj)){
            targetObj[key] = sourceObj[key];
        }
    }
    return targetObj;
}

let Aircraft = {
    name: '飞机',
    manufacturer: '**直升机设计研究所',
    flight: function() {
        console.log(`${this.name} 是飞机!`)
    },
    introduction: function() {
        console.log(`简介: 由 ${this.manufacturer} 研制!`)
    }
}

let HelicopterFlight = minxin(Aircraft, {
    name: 'AV500直升机',
    flight:function () {
        Aircraft.flight.call(this),
        console.log(`${this.name} 使用螺旋桨提供动力!` )
    }
})

HelicopterFlight.flight();
HelicopterFlight.introduction();

我们封装了自己的minxin核心代码,这个方法包含了数据源对象(sourceObj),目标对象(targetObj,),覆盖( rewriteBool)三个参数,通过对数据源对象的属性和方法的遍历,依次复制到目标对象,实现目标对象对数据源对象的复制,rewriteBool参数控制是否允许sourceObj对targetObj对象的相同属性和方法的重写。

Mixin的方法的复制不是函数的复制,而是对函数引用的简单复制,即sourceObj和targetObj的方法是同一个函数的引用。

隐式混入

Mixin的隐式混入,是通过this的重新绑定的机制实现。

let Aircraft = {
    name: '飞机',
    manufacturer: '**直升机设计研究所',
    flight: function() {
        console.log(`${this.name} 是飞机!`)
    }
}

let HelicopterFlight =  {
    name: 'AV500直升机',
    flight:function () {
        Aircraft.flight.call(this)
    }
}

HelicopterFlight.flight();

在HelicopterFlight上下文调用Aircraft.flight(),赋值发生在 HelicopterFlight对象上与Aircraft对象无关。

寄生继承

寄生继承是显示继承的一种变体。

function Aircraft() {
    this.name = '飞机'
}

Aircraft.prototype.flight = function() {
    console.log(`名称: ${this.name}`)
}

function HelicopterFlight() {
    let air = new Aircraft();
    air.name = "AV500";
    air.manufacturer = '**直升机设计研究所';

    let dir = air.flight;
    
    air.flight = function () {
        dir.call(this);
        console.log(`简介: ${this.name}${this.manufacturer}, 研发` )
    }
    return air;
}

let helicopterFlight = new HelicopterFlight();
helicopterFlight.flight()

我们使用new常见HelicopterFlight新的的对象并绑定this到新对象上,在这个新对象上我们实现在自己的额外操作然后返回新对象。

在HelicopterFlight内部通过this绑定实现在HelicopterFlight上下文中对Aircraft.flight()函数的调用。

所以说寄生模式是显示绑定和也是隐式绑定。

参考

JavaScript设计模式-简单工厂模式

JavaScript设计模式-简单工厂模式

简单工厂模式:又称静态工厂模式,由一个对象决定创建某一种对象类的实例。

我们创建IDC行业常见的设备对象:ups(UPS设备)时,我们可以使用简单工厂模式创建相关的实例,每次创建的都是同一种对象UPS的不太实例。

let createObject = function(name, data) {
    let object = new Object();
    object.name = name;
    object.data = data;
    object.getInfo = function() {
        console.log("名称: " , this.name)
    }
    return object;
}

let ups1 = createObject('ups1', {u:"24V", i:"12A"});
let ups2 = createObject('ups2', {u:"24V", i:"12A"});

ups1.getInfo();
ups2.getInfo();

当我们需要添加新的对象的时候,我们可以

let ups3 = createObject('ups3', {u:"12V", i:"12A"});

JavaScript对象的iterator遍历原理

我们可以使用for...in方式遍历对象的可枚举属性,通过对象的属性获取到属性值,但是我们无法直接遍历到对象的属性值。

在ES6中我们可以通过for...of的方式直接变量数组的值,而不需要通过遍历下标的方式获取。

let arr = [1,5,7];
for(let b of arr) {
    console.log(b) 
}

我们之所以能够使用for...of的方式获取数组的属性值,是因为在ES6中数组内置了一个iterator迭代器对象的函数,它包含一个next函数,我们可以手动的调用@@interator,了解它的使用原理。

let arr = [1,5,7];
let result = arr[Symbol.iterator]();
result.next(); // {value: 1, done: false}
result.next(); // {value: 5, done: false}
result.next(); // {value: 7, done: false}
result.next(); // {value: undefined, done: true}

我们通过手动的使用迭代器遍历数组,获取到每次遍历的结果都是由{value:...,done:...}格式的对象组成的返回结果,其中value标签当前可枚举的属性值,done表示枚举是否结束。

ES6中普通对象也是一个可以遍历的,但是ES6没有给普通对象内置interator迭代器,我们可以结合ES6的Symbol特性自己实现对象遍历的迭代器方式。

let obj = { 
    fir:"fir_param",
    sec:"sec_param"
}

Object.defineProperty(obj, Symbol.iterator, {
    writable:false,
    enumerable:false,
    configurable:true,
    value:function() {
        let _currentObj = this;
        let _keys = Object.keys(_currentObj);
        let _index = 0;
        return {
        next:function (){
            return {
                value: _currentObj[_keys[_index++]],
                done: _index > _keys.length
            }
        }
        }
    }
})

let vals = obj[Symbol.iterator]();
console.log(vals.next()); // {value: "fir_param", done: false}
console.log(vals.next()); // {value: "sec_param", done: false}
console.log(vals.next()); // {value: undefined, done: true}

for(let val of obj) {
    console.log(val)
}

我们在控制台就可以获取到of遍历的对象的属性值
// fir_param
// sec_param

参考

ES6语法中继承(extends)的实现原理

ES6语法中继承(extends)的实现原理

extends: extends用于声明类,或者类的表达式。以创建一个类,该类是extends 后那个类的子类。

class Person{
	constructor(name) {
      this.name = name;
   }
}

class liming extends Person {
   constructor() {
        this.name = "liming";
    	super(this.name)
   }
}

我们创建了一个父类Person,子类liming继承了父类Person,就拥有了父类定义的方法和属性。

在ES5中没有extends的概念,extends只是我们借助bable.js等工具实现的一个语法糖,我们可以使用原型链和构造函数,实现extends的核心原理。

function _inheritsLoose(subClass, superClass) { 
    subClass.prototype = Object.create(superClass.prototype); 
    subClass.prototype.constructor = subClass; 
    subClass.__proto__ = superClass;
}

var Person = function Person(name) {
  this.name = name;
};

var liming = function (_Person) {
  _inheritsLoose(liming, _Person);

  function liming() {
    var _this;
    _this.name = "liming";
    return _this = _Person.call(this, _this.name) || this;
  }

  return liming;
}(Person);

Web浏览器缓存机制

Web缓存是存在服务器和客户端之前的资源副本。客户端会缓存请求过的静态资源(图片,CSS 文件,JS文件等),当用户再次请求相同的url时,浏览器会根据缓存规则判断是否使用已经缓 存的静态资源文件,或者绕过资源缓存直接请求服务器重新获取资源。

Web缓存也就是HTTP缓存机制,是前端性能优化的重要措施,利用Web缓存可以:

  • 减少数据冗余传输
  • 减轻服务器请求压力
  • 减少资源请求因为网络传输导致的时延,加快渲染速度
  • 较少的数据传输可以减轻网络线路的传输瓶颈。

HTTP缓存首部

Web缓存机制主要是利用HTTP协议定义的首部信息控制缓存。常见的与缓存相关的首部有:

版本 首部 实例 描述
HTTP1.0 Pragma Pragma 设置页面是否缓存,Pragma为缓存,no-cache表示忽略缓存
Expires Mon, 22 Jul 2002 11:12:01 GMT Expires的值是格林尼治时间格式的参数,用来设置过期时间,即定义资源的失效时间,失效时间内则请求使用缓存资源
HTTP1.1 Cache-Control Cache-Control:max-age=600 Cache-Control首部是为了解决服务器和客户端时间不一致问题导致缓存失效的问题,(a)max-age=600:配置的是请求发起时开始计算的相对时间(秒),当前参数时间内请求资源使用缓存资源 (b)no-cache:每次发起请求都需要验证缓存资源的新鲜度,新鲜度满足则返回304状态码,使用缓存资源,否则返回200状态码,返回资源主体(c)no-store:不缓存,每次请求需要从服务器重新获取资源(d)public:所有内容只有客户端可以缓存(e)private:只允许客户端缓存
Last-Modified/If-Modified-Since 这两个首部需要配合使用,同时需要Cache-Control标识参与。(a)Last-Modified:服务器在相应资源请求时,标识资源最后的修改时间,(b)If-Modified-Since :资源过期并且存在Last-Modified声明,请求首部 If-Modified-Since获取Last-Mdodied的值发送到服务器和请求资源的修改时间比对,如果服务器资源修改过则返回资源主体和200状态码到浏览器,浏览器更新缓存资源,否则说明资源无修改返回304状态码
Etag/If-None-Match W/v20.1 配合Cache-Control使用。(a) Etag是请求的服务器资源的唯一标识,资源改动则标识更改,从而验证资源是否更新。(b)当资源过期请求携带首部If-None-Match赋值Etag的标识值,与服务资源标识进行比对,如果发生变化则变化则返回200状态码和资源主体,否则返回304状态码

缓存分类

HTTP缓存可以根据缓存过程分为强制缓存和协商缓存。

(1)强制缓存:向浏览器缓存查询请求结果,根据缓存结果规则是否使用缓存资源

  • 不存在缓存标识和缓存结果,直接请求服务器资源
  • 存在缓存结果和缓存标识,缓存结果失效,使用协商缓存
  • 存在缓存结果和缓存标识,缓存结果未失效,使用缓存

强制缓存的相关的首部:Expires和Cache-Control

(2)协商缓存:当强制缓存失效,缓存规则使用协商缓存,HTTP请求向服务器发起缓存器验证,服务器判断缓存是否生效。

  • 缓存生效,返回304状态码
  • 缓存失效,返回200状态码和新的资源结果,浏览器加载资源并且更新本地缓存

协商缓存相关的首部:Last-Modified / If-Modified-Since和Etag / If-None-Match

缓存优先级:

  • Pragma 和Expires是HTTP1.0定义的用于管理缓存的首部字段,当两个首部字段同时存在,Pragma的优先级高于Expires
  • Cache-Control是HTTP1.1定义的首部字段,是为了解决Expires定义的过期时间相对于服务器而已,无法确保服务器和客户端时间一致性问题。 Cache-Control的优先级高于Expires
  • 强制缓存优先级高于协商缓存
  • Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since

缓存的处理步骤

web缓存处理步骤很简单,包括了接收请求,解析报文,查询缓存,新鲜度检查,创建响应和发送,记录日志是事务操作的可选步骤。

  • 接收:读取请求报文
  • 解析:解析请求报文,获取URL和首部信息
  • 查找:查找本地缓存,没有缓存文件则请求服务器或者父类缓存,并缓存
  • 新鲜度检查:验证缓存是否新鲜,需要再验证则需要请求服务器验证缓存新鲜度
  • 创建响应:缓存主体和更新的首部构建响应报文
  • 发送:响应发送到服务器
  • 日志:记录信息

其他

浏览器缓存分为内存缓存和硬盘缓存,内存缓存读取速度快,时效性好;硬盘缓存读取缓存时需要I/O操作,重新解析缓存内容,相对于内存缓存速度慢但是不会占用内存资源。

浏览器将JS脚本资源和图片资源存储在内存缓存,css,xml文件存储的硬盘文件。

当浏览器刷新时,js,图片等资源直接从内存中加载,css文件需要重用从硬盘读取并解析渲染到页面。

参考

  1. HTTP 缓存
  2. HTTP权威指南
  3. Web缓存相关知识整理
  4. 彻底理解浏览器的缓存机制

Cookie机制

HTTP协议是无状态协议,服务器处理请求无法通过HTTP无法识别具体的客户,所以需要一种行的用户识别技术,来处理具体用户对应的操作。

在Web开发中涉及到用户识别的技术很多,主要包括但是不限于以下几种(参考《HTTP权威指南》):

方案 实例 描述
HTTP首部 From,User-Agent,Referer From包含了用户的E-mail地址信息
识别IP 代理服务器可能不支持
用户登录 用户名和密码 Authorization首部保存登录信息
胖客户端 每个用户生成独自的URL,无法缓存
Cookie 最常用的方案

Cookie是目前用户识别,实现持久化会话最好的方法。目前Cookie根据会话周期分为:会话Cookie和持久Cookie,其中会话Cookie在浏览器关闭后被清除,持久会话Cookie会存储在硬盘。Cookie常见的应用是在浏览器表单自动填写用户名和密码。

Cookie是以键值对的形式存储,在Cookie内部是以 Cookie: key = val 的形式保存信息,各个key=val键值是以分号隔开,比如 Cookie:id='123;name='javascript'

Cookie的工作流程

  1. 用户第一次访问服务器,服务器为了跟踪用户,在相应首部字段添加Set-cookie首部并赋值
  2. 浏览器获取Set-cookie的内容,存储在浏览器的Cookie数据库
  3. 用户再次请求,浏览器会根据请求的域名,路径,是否过期等信息查询Cookie数据库,如果有值则通过cookie首部携带cookie的内容返回服务器
  4. 服务器通过cookie首部内容判断用户的标识信息,查询用户并进行对应操作
  5. 浏览器根据Expiress或者Max-Age信息判断Cookie是否过去,如果过期更新Cookie
  6. 浏览器关闭,设置Discard参数的Cookie被清除

Set-cookie首部

Cookie存在Cookie0和Cookie1两个应用版本,Cookie1是Cookie0的扩展的新版本,Cookie0是指网景公司最开始开发置顶的Cookie版本,目前阶段Cookie0是使用范围最广的一个版本。

在学习Set-Cookie首部的属性,可以帮助我们了解Cookie的使用,下面是基于Cookie0版本上对Set-Cookie使用做个归纳:

属性 强制 实例 描述
NAME=VALUE true Set-Cookie:id="123" 标识用户的字符序列
Expires false Set-Cookie:id="123";expiress=Tue Oct 15 2019 12:50:17 GMT 过期时间(GMT)
Domain false Set-cookie:id="123";domain=baidu 可以发送Cookie到对应的域名
Path false Set-cookie:id="123";path=/blog 可以发送Cookie到对象的PATH
Secure false 在SSL安全连接时在发送Cookie

JavaScripr Cookie

在JavaScript中我们可以通过 document.cookie 对Cookie进行创建,读取,修改,删除。

document.cookie="username=John Doe"; //创建
let _cookie = document.cookie; //读取
document.cookie="username=John Doe; path=/"; //修改
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; //删除

我们删除Cookie是通过设置过期时间的方式实现的,只要是之前的时间都可以满足要求。

我们通过JavaScript设置的Cookie首部,不会直接删除服务器设置的所有的Cookie,只是简单的添加或者修改字符串指定的Cookie,等到下一次请求document.cookie和cookie一并会发送到服务器。

Cookie 过期

  • 会话Cookie,浏览器关闭
  • 持久化Cookie,过期时间或者Descard属性
  • 浏览器Cookie数据库内存限制,导致Cookie删除

其他

Cookie数据库位置,以window系统的Chrome浏览器为例:

C:\Users\userName\AppData\Local\Google\Chrome\User Data\Default

参考

JavaScript中call,apply,bind方法的使用及原理

在JavaScript里,call(),apply(),bind()都是Function内置的三个方法, 它们的作用都是显示的绑定this的指向,三个方法的第一个参数都是this指向的对象,也就是函数在运行时执行的上下文。

当我们定义一个新的对象,需要使用其他对象的方法的时候,我们不需要重新开发重复的方法逻辑,借助apply,apply,bind三个方法可以实现对这些的方法的调用。

我们定义三者的概念:

apply:调用一个对象(obj)的方法(func),并使用新的对象(thisArg)代替该对象,参数是数组

obj.func.apply(thisArg, [argsArray])

call:调用一个对象(obj)的方法(func),并使用新的对象(thisArg)代替该对象,参数是列表

obj.func.call(thisArg, arg1, arg2, ...)

bind:bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用,第一个thisArg在setTimeout中创建一个函数时传递的原始值都会转化成object,如果bind参数列表为空,thisArg赋值当前运行环境this

function.bind(thisArg[,arg1[,arg2[, ...]]])

特点:

  • apply,call,bind三个方法第一个参数都是函数在调用时this指向的对象,也就是运行时的上下文(this显示绑定的原理)
  • apply,call第一个参数为空,null,undefined,this指向的是window
  • apply,call两个方法只是参数形式有所不同,apply参数是一个数组,call则是参数列表版本
  • apply,call 则是立即调用,bind 是则返回函数的拷贝

常见的一些应用:

数组合并

我们创建arr和other两个数组,当我们需要合并两个数组的时候,可以使用concat方法进行操作,但是concat需要创建新的数组对象,我们可以借助apply方法不需要创建新的对象,不需要遍历数组,也可以实现需求:

let arr = [2,3,4,5,6];
let other = [10,89];
arr.push.apply(arr, other); // or Array.prototype.apply(arr, other)

console.log(arr); // [2, 3, 4, 5, 6, 10, 89]

我们借助了apply方法,push方法在调用的时候,this指向的是arr对象,这里的参数列表other,我们将other数组合并到arr数组对象上。
下面的是两种比较常见的写法,其实是一样的

继承

function Animal(name) {
    this.name = name;
    this.run = function() {
        console.log(`${this.name} is running!`)
    }
}

function Cat(name) {
    Animal.call(this, name);
}

let tom = new Cat("TOM");
tom.run(); // TOM is running!

创建绑定函数

this.name = 'global';
let cacheFun = {
    name : 'cache',
    getCacheName : function() {
        console.log("Name: ",this.name)
    }
}
cacheFun.getCacheName(); // Name is cache

let cacheName = cacheFun.getCacheName;
cacheName(); // Name:  global

在创建绑定函数的实例中,我们分析一下一部分调用过程:

  • 我们cacheFun对象作为缓存name字段的对象
  • 我们声明变量cacheName存储getCacheName方法的引用
  • 当我们调用cacheName方法时this绑定的是window对象,所以this.name获取的是global

我们需要借助bind方法,在函数调用的时候,绑定this的执行到cacheFun对象上:

let cacheNameByBind = cacheName.bind(cacheFun);
cacheNameByBind();  // Name is cache

实现原理

  • apply
Function.prototype.applyFun = function(content) {
    content = content || window;
    content.fun = this;
    var argsArr = arguments[1];

    if(!argsArr) {
       return content.fun();
    }
    if(!Array.isArray(argsArr)) {
        throw Error("参数列表必须是数组的格式!")
    }else {
        var result ;
        var len = argsArr.length;
        for(var i=0;i < len; i++) {
            result = content.fun(argsArr[i]);
        }
        delete content.fun;
        return result;
    }
}

// 测试
var arr = [1,5];
var other = [34,5,5];
Array.prototype.push.applyFun(arr, other);
console.log(arr) //  [1, 5, 34, 5, 5]
  • call
Function.prototype.callFun = function(context) {
    context = context ? Object(context) : window;
    context.fun = this;
    let args = [...arguments].slice(1);
    let result = context.fun(...args)
    delete context.fun
    return result
}

 arr = [1,2,3]
Array.prototype.push.callFun(arr, 4,5,6,6);
console.log(arr);  // [1,2,3,4,5,6,6]
  • bind

基础版

Function.prototype.bindFun = function(content) {
  let that = this;
  return function() {
    return that.apply(content)
  }
}

let Person = {
  getName : function() {
    console.log(`Name is ${this.name}`)
  }
}
let Liming = {
  name: 'ming'
}


let getName = Person.getName;
let getNmaeByming = getName.bindFun(Liming);

getNmaeByming(); // Name is ming

最终版本,处理通过new创建绑定是的this绑定,将that对象传入闭包缓存

Function.prototype.bindFun = function(content) {
  if(typeof this !== 'function') {
    return;
  }
  var that = this;
  var args = Array.prototype.slice.call(arguments, 1);
  var _bindFun = function() {
    var _content = this instanceof that ? this: content;
    var _args = [].slice.call(arguments, 0);
    that.apply(_content , args.concat(_args));
  }
  var BindFun = function() {};

  if(this.prototype) {
    BindFun.prototype = this.prototype;  
    _bindFun.prototype = new BindFun();
  }
  return _bindFun;
}

// 测试
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  return this.x + ',' + this.y; 
};

var emptyObj = {};
var YAxisPoint = Point.bindFun(emptyObj, 0);

var axisPoint = new YAxisPoint(5);
var str = axisPoint.toString(); 

console.log(str) // '0,5'

参考

JavaScript设计模式-亨元模式

JavaScript设计模式-亨元模式

亨元模式:运用共享技术来有效支持大量细粒度的对象,减少对象之间创建相同内容而造成性能开销。

享元模式是一种结构型设计模式,也是一种牵扯性能优化的设计模式。

我们以经典的享元模式实例说明享元模式的性能优化过程。
服装厂个生产男女服装各50款,现在需要每款衣服的模特照,存在多种解决方案:
第一种:50个男模特和50个女模特,直接穿着展示

let Model = function(gender, clothes) {
    this.gender = gender;
    this.clothes = clothes;
}

Model.prototype.takePhoto = function(){
    let photoInfo = `男/女款 : ${this.gender} 款式编号: ${this.clothes}`;
    console.info(photoInfo);
}

for(let i=0;i<50;i++) {
    let newModel = new Model('男', `male_${i}`);
    newModel.takePhoto();
}

for(let i=0;i<50;i++) {
    let newModel = new Model('女', `female_${i}`);
    newModel.takePhoto();
}

第二种:1个男模特和1个女模特(享元模式)

let Model = function(gender) {
    this.gender = gender;
}

Model.prototype.takePhoto = function(){
    let photoInfo = `男/女款 : ${this.gender} 款式编号: ${this.clothes}`;
    console.info(photoInfo);
}

let newModel_male = new Model('男');
for(let i=0;i<50;i++) {
    newModel_male.clothes = `male_${i}`;
    newModel_male.takePhoto();
}

let newModel_female = new Model('女');
for(let i=0;i<50;i++) {
    newModel_female.clothes = `female_${i}`;
    newModel_female.takePhoto();
}

第二种方案我们使用了享元模式,只需要两个模特就可以完成100款衣服的试穿,在第一种方案中,我们每试穿一款衣服就需要创建一个新的对象,每创建一个新的对象都会增加性能开销,这种开销是不必要的可以通过享元模式优化。

参考

  1. 《JavaScript设计模式》
  2. 深入理解JavaScript系列(37):设计模式之享元模式
  3. javaScript之享元模式

ES6语法中常量声明(const)的实现原理

ES6语法中常量声明(const)的实现原理

ES6 const 特点:

  • 临时性死区
  • 在定义的时候完成初始化
  • 不能重新定义
  • 不能重新赋值
  • 语义化标识,表示声明后不可更改的不变量

原理:

  • ES5没有块级的概念,我们只能大概模拟一下const的定义。
  • 我们将const 声明的变量绑定在全局对象上,借助 Object.defineProperty()劫持该对象,配置相关描述符实现const的特性。
  • 关键字和数字不能作为对象属性
var const_customer = function(param, value) {
    // 目前是在浏览器端测试全局对象window,如果是在node 环境全局对象global
    var _global = window; 

     
    var KEY_WORD = ['const', 'let', 'var','class', 'return']; //关键字列表(不全)
    var REG_NUMBER =  '^[1-9][0-9]*([.][0-9]+)?$|0' ; //数字正则表达式
    var _reg = new RegExp(REG_NUMBER)

    //检测键值是否存在并且被支持
    if(!param || _reg.test(parseFloat(param)) || KEY_WORD.indexOf(param) > -1) {
        throw new Error(`Unexpected token: ${param}`);
    }

    //检测是否被定义
    if(_global.hasOwnProperty(param)) {
        throw new Error(`${param} has already been declared !`);
    }

    _global[param] = value;
    Object.defineProperty(_global, param, {
        enumerable: false,
        configurable: false,
        get: function () {
            return value
        },
        set: function (data) {
            
            // 检测赋值异常
            if (_global.hasOwnProperty(param)) { 
                throw new Error(`${param} is read-only!`);
            } else {
                return value
            }
        }
    })
}

//功能测试

const_customer('TEST', "test_value"); 
console.log(TEST) // test_value
const_customer('TEST', "test_value"); // TEST has already been declared !
TEST = "TEST"; // Uncaught Error: TEST is read-only!

参考

JavaScript设计模式-原型模式

JavaScript设计模式-原型模式

设计模式是最佳的编程实践,是开发者面临一般开发问题的解决方案。

原型模式

原型模式让多个对象通过原型继承,分享同一个原型对象的属性和方法。
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayHello = function() {
    console.log("你好,我是:", this.name)
}

var person1 = new Person("person1", 24);
var person2 = new Person("person2",34);

person1.sayHello();
person2.sayHello();

通过原型链模式,我们直接将sayHello方法添加到Person的propertype属性上,Person的所有实例都可以通过原型链继承共用这个方法。

原型对象是一种创建型的设计模式,解决了方法和属性不能共有的问题。

参考

  1. 《JavaScript设计模式》
  2. 菜鸟教程
  3. Javascript单例模式概念与实例

JavaScript设计模式-链式调用

JavaScript设计模式-链式调用

链式调用又称链模式,我们常见的JQuery库就是使用链模式进行封装。

链式调用:通过调用对象的方法将当前对象返回 ,实现同一个方法的链式调用,从而避免对该对象的多个方法的多次调用。

我们在使用JQuery操作DOM元素,或者使用Gulp进行预编译时,我们使用的就是链式调用:

let gulp = require('gulp');

gulp.src('src.less')
    .pipe(less())
    .pipe(cssmin())

计算器

我们可以使用链式调用实现一个简单的仅支持加减乘除的计算器功能,代码的健壮性检测和必要的计算处理(除法运行结果小数位)全部忽略。

let Calculator = function() {
    this.val = 0;
}

Calculator.prototype.addition = function(number) {
    this.val = this.val + number;
    return this;
}

Calculator.prototype.subtraction = function(number) {
    this.val = this.val - number;
    return this;
}

Calculator.prototype.multiplication = function(number) {
    this.val = this.val * number;
    return this;
}

Calculator.prototype.division = function(number) {
    this.val = this.val / number;
    return this;
}

Calculator.prototype.value = function() {
    return this.val;
}

let nums = new  Calculator();
let result = nums.addition(19).addition(20).multiplication(2).subtraction(3).value();

console.log(result) // 75

根据Calculator创建一个新的对象,并且在原型链上实现了addition(),subtraction() ,multiplication(),division()四个常见的方法和一个返回值的方法value()。其中加减乘除方法每次返回对象本身,从而实现计算器的链式调用,每次只需要传递一个用于计算的参数。

参考

浏览器输入URL回车后页面的呈现过程

当在浏览器输入URL按下回车键到页面渲染整个流程,涉及一个完整的HTTP请求过程,按照处理顺序的请求过程可以把整个过程分为以下几个阶段。

  1. 域名解析(DNS服务)
  2. 端口解析
  3. 缓存判断
  4. 三次握手,建立连接
  5. 服务器响应报文
  6. 四次挥手
  7. 页面解析和渲染

在整个URL请求过程中,涉及到网络技术基础但很重要的内容,比如域名解析,端口解析,TCP握手和挥手过程,缓存,重定向都是需要概念,在开发过程中涉及到性能优化也是从这些过程中逐个寻找性能瓶颈。

域名解析

域名是一种字符型标识,对应机器IP地址本身的数字体标识,并且更方便记忆。

域名解析(DNS),英文名称Domain Name System,是将域名和IP地址相互映射的分布式数据库。

请求url为https://github.com 域名解析过程:

  1. 浏览器缓存:首先我们在浏览器缓存查询url映射的IP地址
  2. 如果1不存在,则在操作系统的缓存中查找url映射的IP地址,同时也会查找本地hosts查找(本地存在系统DNS缓存)
  3. 如果2不存在,则在路由器的DNS缓存中查找
  4. 如果3不存在,则向Local DNS发起域名解析请求
  5. 如果4不存在,则向Local DNS的根域名发起域名解析请求
  6. 如果5不存在,则向com的授权服务器发送域名解析请求
  7. 如果6不存在,则向github.com发送域名解析请求
  8. github.com查询本地的区域文件记录,找到www.github.com的IP地址,返还给Local DNS
  9. Local DNS缓存结果并将IP地址发送给路由器
  10. 路由器缓存并发送结果用户
  11. 本地操作系统DNS缓存记录,

端口解析

当url包含端口参数则解析端口,否则使用默认端口,HTTP的默认是80端口,HTTPS的默认是443端口。

比如我们常见的url:

a. https://www.github.com
b. https://www.github.com:4200

其中 a 的端口解析结果是默认的443,b的端口是4200

缓存

我们需要了解缓存使用的基本内容,缓存分为协商缓存和强制缓存,具体的特性如下:

  1. 协商缓存:
  • last-modified,If-Modified-since:绝对时间
  • Etag,If-None-Match:hash值
  1. 强制缓存:
  • pragma:强制缓存关键字,当设置值为no-catch 时不缓存
  • Expires:标识过期时间的绝对值
  • Catch-control:赋值形式如 public,max-age=time,是一个相对时间

当我们请求资源时,我们需要根据不同的情况判断缓存的优先级:

  • 如果存在强制缓存和协商缓存,强制缓存优先级>协商缓存
  • 强制缓存:Pragma > Catch-control > expires,Pragma是1.0的通用首部,现在已经不经常使用
  • 协商缓存:Etag > Lash-Modified

请求处理:

  • 如果我们使用到强制缓存Catch-control或者expires判断命中缓存,则返回304,并且在本地浏览器获取资源
  • 如果设置协商缓存,则请求服务器进行验证
  • 如果没有设置缓存,直接请求服务器资源

TCP三次握手

TCP通过seq序列号在传输层提供可靠的传送和接收,为了避免连接复用时无法识别序列号是否是失效的或者旧的序列号,所以需要seq来协商客户端和服务器seq序列号一致性,及初始化序列号。

三次握手发生在客户端和服务器交换数据之前,整个过程如下:

  1. SYN
    客户端发起随机序列号x,并且发送SYN分组,可能还有其他TCP标志和选项,第一次握手完成
  2. ACK
    服务器给x+1,然后选择随机序列号y,追加自己的标志和选项,返回相应第二次握手完成。
  3. ACK
    客户端给 x+1,y+1并发送握手期间的最后一个ACK,第三次握手完成。

TCP四次挥手

TCP的四次握手过程,是为了确保数据能够完整传输。

因为TCP是全双工的,因此每个方向需要单独关闭。TCP四次握手过程:

  1. 客户端发送一个FIN到服务器,请求关闭数据传输,客户端进入FIN_WAIT_1状态
  2. 服务器接收到客户端的FIN,向客户端发送一个ACK,ACK=FIN+SEQ,服务器进入CLOSE_WAIT
  3. 服务器发送一个FIN,用来关闭服务器和客户端的数据传输,服务器进入LAST_ACK状态
  4. 客户端接收到服务器发送的FIN,客户端进入TIME_WAIT状态,发送一个ACK到服务器,确认序号为接收序号+1,服务器进入CLOSE状态,四次握手完成

参考

  1. 用户在浏览器输入URL回车之后,浏览器都做了什么
  2. 《Web性能优化权威指南》
  3. 《HTTP权威指南》
  4. 以女朋友为例讲解 TCP/IP 三次握手与四次挥手
  5. DNS域名解析解剖

JavaScript的构造函数

JavaScript的构造函数

在Java语言中,我们使用构造函数是实例化对象的过程,在JavaScript语言中我们可以使用构造函数的方式创建对象,如:

let obj = new LanguageFun("javaScript") 

与面向对象的语言不同,JavaScript使用构造函数创建对象不是真正意义上的实例化,而是通过new操作符调用的构造函数。

构造函数的执行过程:

  1. 创建一个新的对象obj
  2. obj被执行原型链[[prototype]]连接
  3. obj绑定到函数调用的this上
  4. 没有其他返回对象,则返回obj

包括我们知道的内置函数Number(), String()都可以使用new操作符创建新的对象,这种函数在JavaScript中称为构造函数的调用。我们可以通过实例理解这个过程:

function LanguageFun(name){
    this.name = name
    this.sayHello = function(){
        console.log("Hello , ",this.name)
    }
}

let obj = new LanguageFun("javaScript") 
obj.sayHello(); // Hello ,  javaScript

new LanguageFun()执行过程:

  1. 对象创建:创建一个全新的对象: let obj = new Object();
  2. [[prototype]]连接:obj.proto = LanguageFun.prototype
  3. this绑定:this指向刚创建的obj对象
  4. 执行LanguageFun()函数
  5. 返回this对象

测试:

console.log(obj.__proto__ === LanguageFun.prototype); //true

兼容处理

当我们使用new操作符调用构造函数的时候没有问题,但是有时候我们会忘记使用new操作符,使用了普通函数的调用方式,产生异常,所以我们需要对JavaScript的构造函数进行兼容改造。

function LanguageFun(name){
    if(!(this instanceof LanguageFun)) {
        return new LanguageFun(name)
    }
    this.name = name
    this.sayHello = function(){
        console.log("Hello , ",this.name)
    }

}

let obj1 = new LanguageFun("javaScript") 
let obj2 =  LanguageFun("javaScript") 

obj1.sayHello(); // Hello ,  javaScript
obj2.sayHello(); // Hello ,  javaScript

JavaScript设计模式-职责链模式

JavaScript设计模式-职责链模式

职责链模式:解决了请求者和接受者之间的耦合关系,通过职责链分解整个请求过程,请求按照一定的顺序在多个过程中传递并在最后完成请求处理。

商场做会员促销活动,根据用户VIP等级会获得对于的折扣和优惠卷,假设目前只有A,B,C三个不同的等级,等级和优惠力度成正比且逐级递减。

// 促销活动配置文件
const VIP_LEVEL = {
    'A': {discount: 0.75, preferential: 200, unit:'元'},
    'B': {discount: 0.85, preferential: 100, unit:'元'},
    'C': {discount: 0.95, preferential: 10, unit: '元'},
}

// 折扣信息方法
let discountInfo = (grade) => {
    let data = VIP_LEVEL[grade];
    let info = `VIP_${grade},折扣:${data['discount']},优惠:${data['preferential']}`;
    console.info(info);
}

let vipLevel_A = (grade) => {
    if(grade === 'A') {
        discountInfo(grade);
    } else {
        vipLevel_B(grade)
    }
}

let vipLevel_B = (grade) => {
    if(grade === 'B') {
        discountInfo(grade);
    } else {
        vipLevel_C(grade)
    }
}

let vipLevel_C = (grade) => {
    if(grade === 'C') {
        discountInfo(grade);
    } else {
        console.log("暂时没有注册会员,请加入我们会员计划,参加更多优惠活动!")
    }
}

// 我们对外提供promotions公共方法,也可以独自调用vipLevel_*
let promotions = (grade) => {
    if(VIP_LEVEL.hasOwnProperty(grade)) {
        vipLevel_A(grade);
    } else {
        console.log("暂时没有注册会员,请加入我们会员计划,参加更多优惠活动!")
    }
}

promotions("A") // VIP_A,折扣:0.75,优惠劵:200 
promotions("B") // VIP_B,折扣:0.85,优惠劵:100 
promotions("C") // VIP_C,折扣:0.95,优惠劵:10 
promotions("D") // 暂时没有注册会员,请加入我们会员计划,参加更多优惠活动!

我们的需求实现之后,我们在代码内部书写了大量的固定调用逻辑,使得配置不够灵活,比如我们添加新的等级逻辑,就要大量的修改我们之前写的业务代码,可以使用职责链模式进行重构

const VIP_LEVEL = {
    'A': {discount: 0.75, preferential: 200, unit:'元'},
    'B': {discount: 0.85, preferential: 100, unit:'元'},
    'C': {discount: 0.95, preferential: 10, unit: '元'},
    'D': {discount: 0.98, preferential: 5, unit: '元'},
}

let discountInfo = (grade) => {
    let data = VIP_LEVEL[grade];
    let info = `VIP_${grade},折扣:${data['discount']},优惠:${data['preferential']}`;
    console.info(info);
}

let vipLevel_A = (grade) => {
    if(grade === 'A') {
        discountInfo(grade);
    } else {
        return 'nextSuccessor'
    }
}

let vipLevel_B = (grade) => {
    if(grade === 'B') {
        discountInfo(grade);
    } else {
        return 'nextSuccessor'
    }
}

let vipLevel_C = (grade) => {
    if(grade === 'C') {
        discountInfo(grade);
    } else {
        return 'nextSuccessor'
    }
}

let vipLevel_D = (grade) => {
    if(grade === 'D') {
        discountInfo(grade);
    } else {
        return 'nextSuccessor'
    }
}

Chain = function(fun){
    this.fun = fun;
    this.successor = null
}

Chain.prototype = {
    setNextSuccessor: function(successor) {
        return this.successor = successor;
    },
    passRequest: function() {
        let res = this.fun.apply(this, arguments);
        if(res === 'nextSuccessor' && this.successor) {
            return this.successor.passRequest.apply(this.successor, arguments);
        }
        return res;
    }
}

let chain_vip_a = new Chain(vipLevel_A);
let chain_vip_b = new Chain(vipLevel_B);
let chain_vip_c = new Chain(vipLevel_C);
let chain_vip_d = new Chain(vipLevel_D);

chain_vip_a.setNextSuccessor(chain_vip_b);
chain_vip_b.setNextSuccessor(chain_vip_c);
chain_vip_c.setNextSuccessor(chain_vip_d);

chain_vip_a.passRequest("A"); // VIP_A,折扣:0.75,优惠:200
chain_vip_a.passRequest("B"); // VIP_B,折扣:0.85,优惠:100
chain_vip_a.passRequest("C"); // VIP_C,折扣:0.95,优惠:10
chain_vip_a.passRequest("D"); // VIP_D,折扣:0.98,优惠:5

职责链特点

优点:

  • 整个请求分成多个请求过程,利于请求者和接受者解耦
  • 只需要定义第一步请求过程(不一定是职责链的第一步)就能够确保请求被处理,比较灵活

缺点:

  • 职责链过长导致的性能损耗
  • 增加单元测试工作任务
  • 不能保证职责链分解的所有过程都被利用,造成一点资源浪费

参考

深入理解JavaScript的Event-Loop机制

JavaScript 是单线程的,只有JS主线程(引擎线程)执行事件队列的事件。为了防止代码阻塞,JavaScript使用了异步执行机制。

参与JS代码执行过程的线程有4个:

  1. JS引擎线程:解析和执行JS脚本主线程

  2. 事件触发线程:浏览器内核进程,主要用于控制事件(比如:键盘事件),当监听到事件触发,事件触发线程会将,事件的处理函数push到事件队列,等待主线程执行。

  3. 定时器线程:管理setInterval和setTimeout,当定时器计时完毕,将回调函数push进事件队列等待执行。

  4. HTTP异步请求线程:通过监听XMLHttpRequest连接的readyState状态变更,将该状态的回调函数push到事件队列中,等待执行。

Event-Loop机制

Event-Loop是实现JavaScript异步的一种机制。

JavaScript 事件队列分为两种:宏任务(macro-task)队列和微任务(micro-task)队列。

  • 宏任务(macro-task)是离散的独立任务,比如创建DOM对象,执行全局JavaScript代码,宏任务执行完成之后可以执行其他事件,比如浏览器垃圾回收。

    常见的宏任务:I/O,setTimeout,setInterval,执行全局JavaScript代码,UI交互事件,postMessage,setImmediate(Node.js)

  • 微任务(micro-task)是更小的任务,在其他任务执行之前执行,比如Promise执行方法,微任务一般通过异步执行或者需要立即执行的并且不产全新的微任务的事件。微任务是在浏览器UI重新渲染之前执行。

    常见的微任务:Promise, MutationObserver,process.nextTick(Node.js)

事件循环通过两个原则处理浏览器事件,一是单线程处理方式,二是事件在执行过程中不会被其他事件中断(除非浏览器自己决定主动终端某个事件,比如浏览器主动关闭一些处理事件时间过长的事件进程,一般很少发生)。

事件循环过程:

  1. 事件循环首先检查宏队列列表,如果队列存在等待宏任务,则执行(2),否则直接执行(3)。
  2. 执行宏任务列表的第1个等待处理事件,执行完成从宏任务队列移除该事件,执行(3)。
  3. 执行微任务列表的第1个处理事件,处理完成从微任务列表移除该事件,如果还有等待的微任务,则重复(3)直到微任务列表为空,否则直接执行(4)。
  4. 检查是否需要更新UI视图,如果是则执行(5),否则返回(1)开始新的循环过程。
  5. UI页面渲染,返回(1)开始新的循环过程。

在事件循环一个完整的迭代过程中,宏任务最多只执行一次,微任务队列则全部被执行,微任务主要目的是为了在下一次UI重绘之前更新程序状态。

微任务优先处理权,微任务队列执行完成之前会阻止浏览器UI渲染。

UI渲染发生在两个宏任务之间,并且UI渲染开始时微任务队列为空。

JavaScript 的事件队列的执行和添加是两个完全独立的过程,确保在事件循环过程中将浏览器监听到的新事件添加到对应事件队列中去,当前执行的事件处理不受影响。

实例分析

console.log('js1');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('js2');

我们来分析上面代码执行过程:

  1. 执行 console.log('js1'),js引擎任务该任务同步宏任务 ,直接输出执行结果:js1;
  2. 执行setTimeout(function() {console.log('setTimeout');}, 0),js引擎认为是异步任务,开启定时器线程控制setTimeout,W3C规范规定setTimeout时间间隔最小为4ms,当计时器到4ms时将回调函数push 到事件队列,等待执行。js主线程向下执行;
  3. 执行Promise.resolve().then(function() {console.log('promise1');}).then(function(console.log('promise2');}),js引擎判断Promise为微任务,将该任务push进微笑任务列表,等待宏同步任务执行完毕执行。
  4. 执行console.log('js2'),js引擎判断该任务为宏同步任务,立即执行宏任务,输出:js2
  5. 依次执行微任务列表的所有回调函数,分别输出:promise1,promise2
  6. 微任务为空,执行下一个宏任务,定时器回到,输出:setTimeout
// 运行结果
js1
js2
promise1
promise2
setTimeout

参考

  1. JS浏览器事件循环机制
  2. JavaScript忍者秘籍(第二版)
  3. 详解JavaScript中的Event Loop(事件循环)机制
  4. js引擎的执行过程(二)

JavaScript设计模式-工厂方法模式

JavaScript设计模式-工厂方法模式

工厂方法模式:父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。

在IDC行业,我们经常需要创建多种不太类型的实例,不同的实例关注的侧重点不完全相同,但是我们可以在底层封装不同类型的创建过程,在客户端以相同的创建方式创建相关实例。

比如我们需要创建IDC,楼栋,设备四个不同维度的实例对象。

ObjectFactory.prototype = {
    IDC: function(data) {
        let object = new Object();
        object.type = data['type'];
        object.name = data['name'];
        object.customers = data['customers'];
        object.getBaseInfo = function() {
            console.log("IDC名称: ", this.name, '使用客户数量: ', this.customers)
        }
        return object;
    },
    BUILDING: function(data) {
        let object = new Object();
        object.type = data['type'];
        object.name = data['name'];
        object.positon = data['positon']
        object.getPositionInfo = function() {
            console.log("位置信息", this.positon)
        }
        return object;
    },
    DEVICE: function(data) {
        let object = new Object();
        object.type = data['type'];
        object.name = data['name'];
        object.getInfo = function() {
            console.log("设备类型: ", this.type, " 名称: ", this.name)
        }
        return object;
    }
}


let idc = ObjectFactory('idc', {customers:201, name:"大中华区IDC",type:"IDC"});
let building = ObjectFactory('building', {positon:"东南", name:"A楼",type:"building"});
let device = ObjectFactory('device', {name:"UPS",type:"device"});

idc.getBaseInfo();
building.getPositionInfo();
device.getInfo();

ES6装饰器Decorator的实现原理

ES6装饰器Decorator的实现原理

NOTE Decorators are an experimental feature that may change in future releases.

装饰器是类的一种附加功能,支持注释或修改类和类成员。目前处于ES6提案的第二阶段(Stage 2),可以借助babel等工具使用该实验性的特性。

Decorator

使用@ 操作符将装饰器添加到类或者类的方法作为修饰。

// 1.修饰类
function runBoolFun(target) {
    target.runBool= true;
    return target;
}

@runBoolFun
class Person{}
console.log(Person.runBool)

// 2.修饰属性
function readonly(target, name, descriptor) {
    discriptor.writable = false;
    return discriptor;
}

class Person{
    @readonly
    run() {
        console.log("跑的不是很快!");
    }
}

let person = new Person();
person.say = run() {
    console.log("跑的很快!");
}

person.run() // 跑的不是很快!

JavaScript没有类的概念,我们的class字符其实是JavaScript实现的一种语法糖,定义Person的类:

class Person{
    say() {
        return `Hello!`
    }
}

这个简单的Person类只包含一个say()方法,我们实现一个定义类的语法糖的核心原理:

function Person() {}
Person.defineProperty(Person.prototype, 'say', {
    value: function() {return `Hello!`},
    enumerable: false,
    configurable: true,
    writable: true
})

这是我们熟悉的ES5的用法,我们重新复习一下Object.defineProperty的方法。

Object.defineProperty(obj, prop, descriptor)

接受三个不同的参数:

  • obj:要定义属性的对象
  • prop:要定义或者修改的属性或者符号
  • descriptor:定于或者修改描述符

我们可以通过修改或者定义描述符修改对象属性的值,当我们使用装饰器修饰对象属性的过程,其实是执行下面的一段过程:

let descriptor = {
    value: function() {
        console.log("hello");
    },
    enumerable: false,
    configurable: true,
    writable: true
};
descriptor = readonly(Person.prototype, "say", descriptor) || descriptor;
Object.defineProperty(Person.prototype, "say", descriptor);

我们知道装饰器也是ES6提供的一个语法糖,当我们使用装饰器修饰类的时候,装饰器runBoolFun是一个纯粹的函数,类本身也是一个函数,target指向的是这个函数(类),执行过程如下:

Person = runBoolFun(function Person() { })

参考

JavaScript设计模式-装饰者模式

JavaScript设计模式-装饰者模式

装饰者模式:在不改变原对象的基础上,通过对其进行包装和扩展,使得原该对象能够支持更丰富的使用场景,可以动态的将责任附加到对象上。

装饰者模式是继承的一种取代方案,相比继承的实现方式来说装饰者模式更有弹性。

AOP(Aspect-Oriented Programming)是面向切面编程,利用AOP可以隔离不同业务模块,实现代码的低耦合,是面向对象(OOP)编程模式的一种补充和扩展。

我们可以使用JavaScript的装饰者模式实现AOP范式编程。

let spaceShuttle = {};

let addProperty = function(obj, param, val) {
    if(!(obj && param )) return;

    Object.defineProperty(obj, param, {
        writable: true, 
        value: val
    })
}

function launchFun() {
    console.log("发射。。。")
}

function runFun(){
    console.log("运行。。。")
}

function returnFun() {
    console.log("返航。。。")
}

addProperty(spaceShuttle, 'launch', launchFun)

addProperty(spaceShuttle, 'run', runFun)

addProperty(spaceShuttle,'return', returnFun)

let aop = function(funs) {
    if(!Array.isArray(funs)) reutrn;
    let len = funs.length;
    return function() {
        for(let i =0; i< len; i++) {
            let fun = funs[i];
            if(fun instanceof Function) {
                fun();
            }
        }
    }
}
let spaceShuttleFuns= [
  spaceShuttle['launch'], 
  spaceShuttle['run'], 
  spaceShuttle['return']
];
aop(spaceShuttleFuns)()

// 发射。。。
// 航天飞行器运行。。。
// 返航。。。

在上面的伪代码中,我们在spaceShuttle对象上,实现了航天飞行器三个不同业务launch(发射),run(运行),return(返航)方法,也可以调用addProperty()方法添加新的业务。

我们使用AOP范式执行我们的业务,只需要在spaceShuttleFuns数组参数添加对应不同的业务函数。

比如我们需要添加太空实验室对接业务:

addProperty(spaceShuttle,'docking', function() {
    console.log("太空舱对接。。。")
})

aop([spaceShuttle['docking']])()

// 太空舱对接。。。

参考

常见的排序算法

  • 二分法排序
let dichotomy = function(list) {
    let len = list.length;
    for(let i=0;i< len;i++) {
        let start = 0;
        let end = i-1;
        let mid = 0;
        let curr = list[i];
        while(start <= end) {
            mid = parseInt((start + end) /2);
            midVal = list[mid];
            if(midVal > curr) {
                end = mid -1;
            } else {
                start = mid +1 ;
            }
        }

        for(let j = i-1;j> end;j--) {
            list[j+1] = list[j];
        }
        list[end+1] = curr;
    }
    
    return list;
}
  • 冒泡排序
let bubbleSort = function(list) {
    if(!Array.isArray(list)) return;
    let len = list.length;
    for(let i = 0; i< len -1; i++) {
        let item = list[i];
        for(let j = i+1;j<len;j++) {
            let subItem = list[j];
            if( item > subItem) {
                list[i] = subItem;
                list[j] = item;
                item = subItem;
            }
        }
    }
    return list;
}

参考

JavaScript设计模式-单例模式

JavaScript设计模式-单例模式

单例模式

单例模式是只能实例化一次的对象类,又称为单体模式。

单例的应用

单例模式是JavaScript常见的设计模式,我们熟悉的Jquery库也是一个单例模式的实例,单例可以用来实现代码库,创建独立的命名空间,创建变量,管理模块,ES6语法中创建对象。

  • 创建常量
let CONFIG = {
    setting: {
        SIZE_MAX: '1000px',
        SIZE_MIN: '700px',
        COLOR_DEF: "#aaaaaa"
    },
    getConfig(attr) {
        let _attr = attr && attr.toUpperCase();
        return this.setting[_attr] || null;
    }
}
let color_def = CONFIG.getConfig('COLOR_DEF');

上面我们创建一个CONFIG对象,模拟通常我们在设计工具会遇到的配置文件,定义了默认的属性设置,当我们需要恢复设置的时候,会使用到的一个单例场景,本质上是利用单例实例一次,不能修改的原理,创建常量,同时可以避免污染全局变量的一种方案。

  • 命名空间的管理

在前端开发过程中,我们经常使用到一些公共的代码,我们可以将这些可以重用的代码组织在一起,封装成函数或者模块提供给所以人使用,比如在每个项目我们都需要提供一些基础的工具组件代码,可以使用单例模式进行封装。

let ToolComponent = {
    String: {
        toUpper: function(str) {
            return str && str.toUpperCase();
        },
        toLower: function(str) {
            return str && str.toLowerCase();
        }
    },
    Date: {
        getFullYear: function() {
            let date = new Date();
            return date.getFullYear();
        },
        getMonth: function() {
            let date = new Date();
            return date.getMonth();
        },

    }
}
let dateYear = ToolComponent.Date.getFullYear();//2019
let strLower = ToolComponent.String.toLower("ABC"); //abc

上面的代码只是为了模拟单例的使用场景,真是情况比这个更复杂,比如时间工具对象,我们根据业务需要封装返回任何形式的时间函数,我们可以使用单例模式对这个函数进行管理,确保我们的函数名称不会污染到全局作用域。

  • ES6创建对象
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getInfo() {
        console.info(`当前用户信息, 姓名:${this.name},年龄:${this.age}`)
    }
}
let zhangsan = new Person('zhangsan', 23);
let lisi = new Person('lisi', 27);
zhangsan.getInfo(); //当前用户信息, 姓名:zhangsan,年龄:23
lisi.getInfo(); // 当前用户信息, 姓名:lisi,年龄:27

ES6可以借助类和构造函数创建单例类,通过new关键字创建新的实例对象,各个实例互不影响。

  • 惰性单例
var createLoginLayer=(function(){
    var div;
    return function(){
        if(!div){
            div=document.createElement('div');
            //创建一个登录框
        }
        return div;
    }
})();

document.getElementById('loginBtn').onclick=function(){
    var loginLayer=createLoginLayer();
    loginLayer.style.display='block';
};

惰性单例:只有在使用的时候才创建实例对象。惰性单例是延迟创建的一种实践,在单例实例中比较重要。

参考

  1. 《JavaScript设计模式》
  2. 菜鸟教程
  3. Javascript单例模式概念与实例

JavaScript实现sleep方法

JavaScript是单线程语法,没有语言内置的休眠(sleep or wait)函数,所谓的sleep只是实现一种延迟执行的效果,无论是使用ES5,Promise,generator或者async await实现sleep,核心只是应用到了setTimeout方法。

  • setTimeout
let sleepFun = function(fun, time) {
  setTimeout(function() {
    fun();
  }, time);
}


let fun = () => console.timeEnd('time');
console.time("time")
sleepFun (fun, 1000); //time: 1000.423095703125ms

直接使用setTimeout实现sleep()的方法,兼容性是最好的,但是使用了回调函数的实现方式,代码的可读性和维护性不是很好。

  • Promise
let sleepFun = (time) => new Promise((resolve) => setTimeout(resolve, time));

let fun = () => console.timeEnd('time');
console.time("time");
sleepFun(3000).then(fun);

在ES6的语法中,我们借助Promise方法可以优雅的构建我们的sleep实现方法,避免了使用函数回调的使用方式。

Promise是sleep方法异步的实现一种方式,当我们需要同步执行的方式处理,可以使用generator和async await的语法实现。

  • generator
let fun = () => console.timeEnd('time');
function* sleepFun(time) {
  yield new Promise((resolve) => {
    setTimeout(resolve, time);
  });
}

console.time('time');
sleepFun(1000).next().value.then(fun); // time: 1011.2138671875ms

generator是将Promise异步编程方式流程化的一种语法糖,这样我们就可以使用同步的方式编写异步请求。但是generator语法的可读性被没有因此提高,无法在语法层面提供语义化的支持,我们可以使用async await重构sleep方法的实现。

  • async await
let sleepFun = function(timer) {
    setTimeout(function() {
        return true;
    }, timer)
}
let fun = function() {
    console.timeEnd('time');
}

async function  wait(timer) {
    await sleepFun(timer);
    fun();
}

console.time("time")
wait(2000);

async await实际上是generator和promise的语法糖,在提供同步编程方式实现异步调用的基础上,同时满足我们对sleep函数语义化的支持,也是常用的sleep的实现方式。

有了js版本的sleep(),我们就不需要调用服务端底层的线程休眠(如java的sleep())方法,也可以支持方法的暂停执行。

数组去重

数组是常见的数据结构类型,数组的下标是连续的,我们可以根据下标高效的读写数组元素,复杂度为O(1)。

数组在内存中占据一段连续的存储空间,当我们声明数组的容量大小的时候,无论内存是否空闲都预选分配了内存。导致系统内存没有充分利用。

为了解决内存利用率不高的问题,于是人们设计了动态分配内存的方式。初始化的内存分配不是很多,当分配的内存不够用的时候,我们在设置更多的内存(常见的设置之前的2倍大小),但是分配内存的过程需要额外的资源消耗,所以尽量一开始分配一个合理的内存初始值,内存增加可以分配更多的内存(>2倍)。

常见的数组操作很多,读取,插入,添加,删除,截取,排序,查询等等,我们这里处理最常见的需要注意性能的操作:数组去重。

数组去重的方法很多,我们需要根据不同的需求选择合适的去重算法。

  • ES6 new Set和解构

最简单的基本类型去重,但是环境要支持ES6。

let uniqueFun = function(arr) {
  return [...new Set(arr)];
}

let res = uniqueFun([1,3,4,5,63,2,3,5]);
  • 双层循环遍历

容易理解,需要创建结果数组对象,代码的性能鲁棒性不能保证,时间复杂度最高O(n*!(n-1)),所以数据量较大不建议使用。

let uniqueFun = function(arr) {
  let res = [];
  for(let i=0; len = arr.length, i<len ;i++) {
    let item = arr[i];
    let hasBool = false;
    for(let j=0;subLen = res.length, j<subLen;j ++) {
      if(res[j] === item) {
        hasBool = true;
        break;
      }
    }
    if(!hasBool) {
      res.push(item)
    }
  }
  return res;
}
let res = uniqueFun([1,2,2,3,5]);
  • indexOf

需要创建新的数组对象。算是双层遍历的一种优化,可以将复杂度优化到O(n)

let uniqueFun = function(arr) {
  let res = [];
  let len = arr.length;
  for(let i=0;i<len ;i++) {
    let item = arr[i];
    if(res.indexOf(item) === -1) {
      res.push(item);
    }
  }
  return res;
}

let res = uniqueFun([1,3,3,2,3,4,5,63,2,3,5]);
  • 对象键值对

需要创建新的普通键值对象和新的数组对象,复杂度优化到O(n)。

let uniqueFun = function(arr) {
  let obj = {};
  let res = [];
  let len = arr.length;
  for(let i=0;i<len ;i++) {
    let item = arr[i];
    if(!obj[item]) {
      obj[item] = item;
      res.push(item);
    }
  }
  return res;
}

let res = uniqueFun([1,3,3,2,3,4,5,63,2,3,5]);
  • 双层遍历2

不需要创建新的对象数组,直接在原来的数组对象上操作。

let uniqueFun = function(arr) {
  for(let i=0; len = arr.length, i < len-1;i++) {
    let item = arr[i];
    for(let j = i + 1;  j < len ;j++) {
       let subIndex = arr.indexOf(item,  j);
       if(subIndex > -1) {
          j = subIndex;
          arr.splice(subIndex, 1);
          len = len -1; 
       }
    }
  }
  return arr;
}

JavaScript柯里化

函数柯里化(curry)可以简单的理解是将具有多个参数的函数,转换成具有较少的参数的函数的过程,具体将具有多个参数的函数转换成一系列嵌套函数,使用一部分参数调用柯里化函数返回一个新函数,每次返回的函数处理剩余的参数。

从一个简单函数理解柯里化过程

我们通过一个常用的函数举例说明柯里化函数的使用。

function multiply(a,b,c){ 
    return a * b * c; 
}

multiply(2,2,5// 20

这个简单的函数定义了三个参数,参数相乘返回运算结果。

我们将上面的函数重构成柯里化结构的函数,用以说明柯里化的使用方法。

function multiply(a,b,c){ 
    return a=> {
      return  b=> {
        return c => {
          return a * b * c;
        }
      }
    }
}

multiply(2)(2)(5) // 20

通过柯里化函数,我们将一个功能改造成一系列的功能,将之前需要一次传递的多个参数,分为三次传递,每次传递的函数作为下一个函数的内链调用。

为了便于理解函数柯里化的调用,我们可以将上面的柯里化函数multiply(2)(2)(5) 调用过程分开书写。

const multiply_fir = multiply(2);
const multiply_sec = multiply_fir(2);
const result = multiply_sec(5);

console.log(result ); // 20

实现原理

理解了返回函数的内链调用的过程,现在我们开始实现柯里化函数的内部原理:

let curryFun = (fun, length, ...args) => {
    let len = args.length;
    return len === length ? fun(...args) : curryFun.bind(null, fun, len, ...args);
} 

// 测试
let addFun = curryFun(function(){
    let sum = 0;
    let args = [].slice.call(arguments, 0);
    let len = args.length;
    for(let i=0;i<len;i++) {
        sum += args[i];
    }
    return sum;
})
let sun = addFun(2,12,19)(198)(100)();
console.log(sun); //331

柯里化的应用

柯里化函数是函数式编程的一种很实用的实践。函数式编程是一种重要的编程范式,最重要的特点是函数作为第一等公民,并且强调函数本身的纯粹性,对于相同的输入参数返回相同的结果没有其他副作用。

所以我们创建的柯里化函数本身是一个纯函数,并且具有自己的特征,可以创建有意义的代码模块。

  • 代码的复用

当我们的函数参数大部分情况下相同的时候,我们可以利用柯里化解决公共参数复用的问题。

const obj = { name: 'test' };
const foo = function (prefix, suffix) {
    console.log(prefix + this.name + suffix);
}.bind(obj, 'currying-');

foo('-function'); // currying-test-function

bind方法将第一个参数设置成函数执行上下文,其他的参数传递给调用方法。

  • 函数的合成

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)

// ES5写法
const compose = function (f, g) {
  return function (x) {
    return f(g(x));
  };
}
  • 减少代码的中间变量

通过封装一系列处理步骤,可以减少对中间变量的操作,也是一种代码复用。比如我们需要操作一个数组,返回的对象是原数组每个元素上+1后的新的数组,这个需求很简单:

const list = [0, 1, 2, 3];
const list1 = list.map(elem => elem + 1); // => [1, 2, 3, 4]

当我们需求发生变化,需要增加+2的操作结果:

const list = [0, 1, 2, 3];
const list1 = list.map(elem => elem + 1); // => [1, 2, 3, 4]
const list2 = list.map(elem => elem + 2); // => [2, 3, 4, 5]

我们在维护这样一系列的代码的时候,很容易想到将list.map(elem => elem + 2)这样一个过程,进行封装,我们只需要传递我们需要操作的参数即可,这样可以提高代码的可读性。

我们的代码结构如下:

const plus1 = plus(1);
const plus2 = plus(2);

const list = [0, 1, 2, 3];
const list1 = list.map(plus1); // => [1, 2, 3, 4]
const list2 = list.map(plus2); // => [2, 3, 4, 5]

我们只需要实现plus()函数即可完成代码的封装,提高代码的维护性和可读性,JS柯里化可以实现我们的需求。

function plus(a) {
  return function(b) {
    return a + b;
  }
}

柯里化的上下文堆栈

首先我们需要了解JavaScript函数的执行机制的一些基础内容:JS是通过执行上下文堆栈管理函数的执行和变量的作用域。JS的函数调用其实是在函数执行上下文堆栈上创建新的记录(帧)。

每个执行上下文帧都需要一定的内存空间,空帧大约需要48字节,每个参数和局部变量一般需要8字节。

函数柯里化是将函数一次执行的过程,变成嵌套函数执行多次。根据JS的执行机制我们可以能够理解函数柯里化,使用到了更多的嵌套函数,导致更多的执行上下文堆栈开销。

JS是单线程的,有且只有一个全局上下文,全局上下文共享,函数在调用的时候首先会在暂停全局上下文,创建新的上下文堆栈,使得新上下文处于激活状态。通过scopeChain创建到父类上下文的链接。激活上下文执行完毕会被弹出,父类上下文重新处于激活状态。

有了这些基础知识我们可以利用JS执行机制,解释 multiply(a, b, c)柯里化前后上下文堆栈的创建过程:

  1. multiply(a, b, c) 非柯里化
  • 执行multiply函数,首先暂停全局上下文
  • 在全局上下文堆栈创建新的上下文
  • 新的上下文通过scopeChain关联到全局上下文
  • 激活新的上下文
  • 新的上下文堆栈执行完毕
  • 新的上下文被弹出
  • 全局上下文堆栈处于激活状态

当函数调用创建的上下文处于激活状态是的上下文堆栈情况:

堆栈层级 上下文 状态
2 (栈顶) multiply 激活
1 全局上下文 被暂停
  1. multiply柯里化

同上的分析过程,我们可以判断multiply柯里化函数在multiply_sec(5)运行时的堆栈情况。

堆栈层级 上下文 状态
4(栈顶) multiply_sec 激活
3 multiply_fir 被暂停
2 multiply 被暂停
1 全局上下文 被暂停

通过对上下文的创建过程的分析,我们可以理解嵌套深的函数会创建更多的上下文帧,不合理的柯里话函数会导致更多的内存消耗,我们在使用柯里化的时候需要了解清楚函数执行上下文的执行原理,合理的规划实现方式。

我们也可以重新定义JS的柯里化:柯里化是一种词法作用域,返回的函数是一个接受后续参数的包装器。

参考

JavaScript的深拷贝和浅拷贝

在前端开发中,JavaScript的对象拷贝使用场景很多,比如:复制数组对象,按照对象模板创建新的独立对象,文本编辑器定时缓存内容比对等,我们使用的Jquery的extend()和Loadsh的cloneDeep()都实现了类似的功能。

浅拷贝:拷贝对象的引用,拷贝后的引用因为指向同一个实例(内存地址),所有彼此之间相互影响。
深拷贝:通过递归遍历的方式,将所有的可枚举属性重新拷贝赋值,即分配新的内存地址,所有的引用之间互不影响。

JavaScript没有提供深拷贝的内置方法,我们可以使用第三方库(如Loadsh)来实现相关功能,也可以自己实现深拷贝方法。

浅拷贝

  • Object.assign() 和 {...object}

Object.assign() 将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象。

let student = {
    name: 'obj',
    school: {
        class:"环化202003",
        profession:"环化专业"
    }
}

let employee = Object.create(student);
employee.name = "jser";
employee.school.class = "计算机202001";

console.log(student);
// {
//     name: "obj",
//     school:{
//         class: "计算机202001",
//         profession: "环化专业"
//     }
// }

我们创建了employee对象,并且修改了对象的name和school.class属性的值,但是Object.assign()只复制第一层,第二层只是对引用的拷贝,所以是一种浅拷贝的方式。

使用{...object} 同上。

  • Array.prototype.slice()和Array.prototype.concat()
let arr_img = ['pic_img',{pic:'img_人物'},{pic:'img_游戏'}];
let arr_img_game = Array.prototype.slice.apply(arr_img);

console.log(arr_img === arr_img_game); //false

arr_img_game[0] = 'pic_img_game';
arr_img_game[1]['pic'] = 'img_游戏人物';

console.log(arr_img); // ['pic_img',{pic:'img_游戏人物'},{pic:'img_游戏'}]
console.log(arr_img_game); // ['pic_img_game',{pic:'img_游戏人物'},{pic:'img_游戏'}]

Array.prototype.slice()也是只拷贝对一层数据,第一层之后的数据复制引用。

使用Array.prototype.concat()同上。

  • for...in 遍历
function clone(obj) {
    let _obj = {};
    for(let key in obj) {
        _obj[key] = obj[key];
    }

    return _obj;
}

深拷贝

  • JSON.stringify() 和 JSON.parse()
let obj = {
    name:'obj',
}

function clone(object) {
    return JSON.parse(JSON.stringify(obj));
}

// 测试
let newObj = clone(obj);
newObj.name = 'newObj';
console.log(obj); //{name:"obj"}
console.log(newObj); //{name:"newObj"}

在实际开发中也是一种常见的使用方法,因为它足够简单,但是这个方法有一定的局限性,对于一些复杂的对象:属性值为Symbal,函数,正则时,使用 JSON.parse(JSON.stringify())都不能正确的执行拷贝;当解析的时循环引用时,会出现解析错误。

我们在常见的开发过程中,如果json对象仅包含基本类型的属性值,可以使用这种方式。

  • 递归

核心原理:

function deepCloneFun(obj) {
    let _obj = obj.constructor === Array ? [] : {};
    for(let key in obj) {
        let item = obj[key];
        _obj[key] = (typeof item === 'object') ? deepCloneFun(item) : item;
    }
    return _obj;
}

我们可以通过包含引用属性school的student对象进行测试。

let student = {
    school: {
        class:"环化202003",
        profession:"环化专业"
    }
}
let employee = deepCloneFun(student);
employee.school.class = "计算机202001";
console.log(student); // {school: {class:"环化202003", profession:"环化专业"}}
console.log(employee) // {school: {class:"计算机202001", profession:"环化专业"}}

其中Symbal,函数,正则都会牵扯其他处理,但是深入拷贝原理都是一样的:使用递归遍历所有可以枚举的属性,重新赋值分配内存地址,这里我们只实现数组和字符属性的深拷贝,用来了解深拷贝的具体过程。

JavaScript设计模式-观察者模式

JavaScript设计模式-观察者模式

观察者模式又称为发布-订阅者模式,用来处理消息对象和消息观察者之间的耦合关系。

观察者模式的消息对象,一般包括三个主要的方法,分别是消息发布,消息订阅和取消订阅。为了防止消息队列被篡改,需要用到闭包创建私有变量存储消息队列容器。

let Observer = (() => {
    var _message = {};
    return {
        subscribe:() => {}, //订阅
        emit: () => {}, //发布
        remove:() => {} //移除订阅
    }
})()

这段代码摘录自《JavaScript设计模式》,涵盖了观察者模式对象的基本骨架,我们需要页面加载完成的时候创建出观察者对象,所以使用了立即执行函数。

下面我们需要依次实现订阅者模式的主体方法。

消息订阅

订阅者模式,首先需要保证消息对象是可以被订阅的,同时满足多个不同模块订阅同一个消息,我们使用type字段区分不同的订阅模块,使用数组存储消息的订阅队列。

subscribe: (type, fun) =>  {
    if(typeof _message[type] === 'undefined') {
        _message[type] = []
    }
    _message[type].push(fun)
}

发布消息

观察者模式消息的发布,首先需要验证消息类型是否已经被注册过,注册过的消息类型直接添加消息类型对应的消息队列,否则需要先创建该消息类型对应的消息队列,然后添加消息。

emit: (type, param) => {
    if(!_message[type]) {
        return false;
    }

    let obj = {
        type: type,
        args: param
    }

    let len = _message[type].length;
    for(let i=0; i<len; i++) {
        _message[type][i].call(this, obj);
    }
}

取消订阅

与消息发布类似,取消订阅操作需要首先对消息队列的消息列表进行验证,确保该类型的消息队列的存在性,否则没有意义。

remove: (type, fun) => {
    if(!Array.isArray(_message[type])) {
        return false;
    }
    let len = _message[type].length - 1;
    for(i = len; i>= 0;i--) {
        let arr = _message[type];
        if(arr == fun) {
            arr.splice(i, 1)
        }
    }
}

测试

/**
 *  控制台打印的内容:
 *  订阅的消息类型:node, 消息内容:node 设计模式
 *  >>>>>>> 订阅操作Error:没有注册js类型的事件 !!!
 *  >>>>>>> 取消订阅操作Error:没有注册js类型的事件 !!!
 */


let Observer = (() => {
    var _message = {};
    return {
        subscribe: (type, fun) => {
            if(typeof _message[type] === 'undefined') {
                _message[type] = []
            }
            _message[type].push(fun)
        },
        emit: (type, param) => {
            if(!_message[type]) {
                console.error(`>>>>>>> 订阅操作Error:没有注册${type}类型的事件 !!!`);
                return false;
            } 
            let obj = {
                type: type,
                args: param
            }

            let len = _message[type].length;
            for(let i=0; i<len; i++) {
                _message[type][i].call(this, obj);
            }
        },
        remove: (type, fun) => { 
            if(!Array.isArray(_message[type])) {
                console.error(`>>>>>>> 取消订阅操作Error:没有注册${type}类型的事件 !!!`);
                return false;
            }
            let len = _message[type].length - 1;
            for(i = len; i>= 0;i--) {
                let arr = _message[type];
                if(arr[i] === fun) {
                    _message[type].splice(i, 1)
                }
            }
        }
    }
})()

Observer.subscribe('node', val => {
    let data = `订阅的消息类型:${val['type']}, 消息内容:${val['args']['msg']}`;
    console.log(data)
})
Observer.emit('node', {
    msg: 'node 设计模式'
})
Observer.emit('js', {
    msg: 'javaScript 设计模式'
})
Observer.remove('js', val => {
    let data = `订阅的消息类型:${val['type']}, 消息内容:${val['args']['msg']}`;
    console.log(data)
})

因为我们订阅了node类型的事件,所以当发布node事件的时候我么可以观察到该事件,我们订阅队列不包含js事件类型所以观察不到js事件。

订阅者模式可以理解为观察对象的一个函数回调,但是又不完全一样。因为事件观察者模式处理的是观察者和被观察对象一对多的关系,函数回调是观察者模式的一种具体实现,但是函数回调处理的是一对一的关系。

参考

  1. 《JavaScript设计模式》
  2. 菜鸟教程

Jsonp原理和实例

在浏览器的web端,img,script,style等标签是少数几个不受同源策略的影响。

Jsonp(Json with padding)是一种简单的处理跨域的解决方案,原理就是利用script可以直接请求第三方进行跨域请求的特点,动态的创建<script>元素,script元素的src属性设置跨域请求资源url。

形式上jsonp是包涵在函数回调中的json,如:

function callback(response) {
    var info = response && response.info;
    console.log("根据名称查询到的信息是:" + info)
}

callback({name:'jsonp'})

我们可以看到jsonp包涵了回调函数和数据,回调函数是请求完毕并响应到页面立即调用的函数,参数就是我们传入callback回调函数的参数,这里是 {name:'jsonp'}

jsop的特点就是使用简单,但是jsonp的弊端就是绕过了浏览器的同源策略,必须确保第三方资源能够安全准确的运行我们的回调函数,第一个问题是第三方资源的不安全会导致我们的程序出现安全漏洞,二是jsonp的失败状态不容易检测。

我们看一段简单的实例了解实际的jsonp如何使用:

在localhost域的index.html页面,请求其他域otherUrl的资源index.js

function jsonp(res) {
    let script = document.createElement('script');
    let url = res.url + '?callback=' + res.callback.name;
    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script); 
}

function callbackFun(res) {
    console.log('msg', res && res['msg']);
} 
jsonp({
    url: 'otherUrl',
    data: { msg: 'val'},
    callback: callbackFun
});

在其他域otherUrl的app.js文件中,我们定义了服务器的内容:

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
    if(ctx.method === 'GET' && ctx.url.indexOf('?callback') === 1) {
        let callbackFun = ctx.query.callback || 'callback';
        let body = {
            msg: 'jsonp请求成功!'
        }
        ctx.type = 'text/javascript';
        ctx.body = `;${callbackFun}(${JSON.stringify(body)})`;
    } else {
        ctx.body = '测试!!!';
    }
});

app.on('error', err => {
    log.error('server error', err)
});

app.listen(3000, ()=> {
    console.info("node server start...")
});

当资源请求完毕,立即执行页面定义的回调函数callback,打印jsonp。

jsonp是动态的去创建script元素,所以jsonp仅仅支持get请求。

JavaScript设计模式-命令模式

JavaScript设计模式-命令模式

命令模式:通过将请求和实现解耦,单独封装成对象,从而实现不同的请求和客户端的实现参数化。

命令模式是一种常见是设计模式,即使你不知道它准确的概念。

打印模板

我们曾经封装一份打印多设备报表,支持多页,分类,标题,页面均可配置的开发需求,就使用到了命令模式的相关**,下面我用一段伪代码实现相关的一些业务,为了说明问题,假设我们的数据量不超过单页,标题,页码均支持。

let PageTemplate = (function(pageInfo) {
    let Template = {
        title: function(titVal) {
            let temp_tit = `<p class="title">${titVal}</p>`;
            return temp_tit;  
        },
        content: function(val, paramsArray, type) {
            if(!(val && paramsArray instanceof Array )) return;
            let temp_content = '';
            let paramsLen = paramsArray.length;
            let comp_lines_title  = '';
            for(let pi = 0; pi< paramsLen;  pi++) {
                let param = paramsArray[pi];//获取每个json的位置
                comp_lines_title += `<span>${param}</span>`;
            }
            comp_lines_title = `<li class='lines-title'>${comp_lines_title}</li>`;

            if(val instanceof Array || type === 'list') {
                let len = val.length;
                let temp_lines = comp_lines_title;
                for(let i=0;i<len;i++) {
                    let lowValue = val[i]; //获取每行的值
                    let temp_line = ''; //行元素和值组成的行模板
                    for(let pi = 0; pi< paramsLen;  pi++) {
                        let param = paramsArray[pi];//获取每个json的位置
               
                        let itemByLineVal = lowValue[param];
                        let itemByLine_temp = `<span>${itemByLineVal}</span>`;
                        temp_line += itemByLine_temp;
                    }
                    temp_lines += `<li class='line'>${temp_line}</li>`;
                }
                temp_content = `<ul class="content">${temp_lines}</ul>`;
            } else if(type === 'json') {
                let temp_by_json = ''; 
                for(let pi = 0; pi< paramsLen;  pi++) {
                    let param = paramsArray[i];
                    let item_val = val[param]; //获取参数对于的值
                    let item_temp = `<li><span>${item_val}</span></li>`;
                    temp_by_json += item_temp;
                }
                temp_content = `<ul class='content'>${temp_by_json}</ul>`;
            }
            return temp_content;
        },
        pageNumber: function(num, unitBool) {
            let temp_page_number = '';
            let pageNumberVal = unitBool===false?`第 ${num} 页`: `${num}`; 
            temp_page_number = `<p class="pageNumber">${pageNumberVal}</p>`
            return temp_page_number;
        }
    }

    return function(pageInfo) {
        if(!(pageInfo && pageInfo['content'] && pageInfo['pageNum'])) return;
        let title = pageInfo['title'];
        let content_val = pageInfo['content']['val'] 
        let content_params = pageInfo['content']['params'];
        let content_type = pageInfo['content']['type'];
        let num = pageInfo['pageNum']['num'];
        let unitBool = pageInfo['pageNum']['unitBool'];
        let htmlString = `
            ${Template.title(title)}
            ${Template.content(content_val, content_params, content_val)}
            ${Template.pageNumber(num, unitBool)}
        `;
        return htmlString;
    };
})()

let page = {
    title: '测试标题',
    content: {
        val: [{
            name:"javaScript",
            type:"web"
        },
        {
            name:"node",
            type:"server"
        }],
        params: ['name', 'type'],
        type: 'list'
    },
    pageNum: {
        num:1,
        unitBool: true
    }
}
let temp = PageTemplate(page)

我们使用了PageTemplate()函数拼接了一段HTML代码,在PageTemplate内部,我们封装了三个具体的拼接逻辑,title()函数我们处理标题,content()函数我们处理内容,pageNumber()函数处理页码。

在客户端我们实现了参数化之后,所以复杂了处理逻辑封装到了单独的对象,我们每次需要创建不同的HTML页面,只需要配置参数对象,不需要考虑具体的实现逻辑。

参考

Git使用个人笔记

根据个人使用情况,不定时更新

撤销 git add

  1. 被git add 的文件退出暂存区, 但是修改保留
git reset --mixed
  1. git reset 默认 --mixed,所以可以使用git reset HEAD
git reset HEAD

代码找回

  1. 本地代码commit 但是未提交(reset --hard等操作造成代码丢失)时,代码找回
git reflog 
  1. 撤下到之前状态
git reset --hard HEAD^ //上一个commit 
git reset --hard id

更新master到gh-pages分支

git checkout gh-pages // go to the gh-pages branch
git rebase master // bring gh-pages up to date with master
git push origin gh-pages // commit the changes

git放弃本地,强制覆盖本地代码

$ git fetch --all
$ git reset --hard origin/master 
$ git pull

删除远程分支文件夹,保留本地文件夹

git rm --cached -r xxx
git commit -m "remove file from remote"
git push -u origin master

发布项目到git-pages

git branch gh-pages  
git checkout gh-pages  
git rm -rf .  
git add .  
git commit -m "git pages"  

Gulp的工作原理

Gulp的工作原理

Gulp是基于NodeJS流处理的前端自动化构建工具。

原理

核心原理虽然是通过Node的两个重要模块:FileSystem 和Transform Stream 实现对文件和数据流的操作,并且借鉴了Unix的管道(pipe)的**,使得处理过的对象从上一个流安全的流入下一个流。但是Gulp没有直接使用这两个模块,而是在FileSystem 和Transform Stream的上面包装了一层vinyl,使流的操作更加简单。

  • FileSystem

FileSystem给Gulp提供了文件读写的能力。

  • Transform Stream

Node的Stream分为4中不同的类型,分别是:Readable(只读)、Writable(只写)、Duplex(双向)、 Transform(转换)。其中Gulp使用Transform,Transform也是Duplex的,说明Transform具有
读写流的能力,能够对输入的流进行操作转换,输出结果流。

比如Gulp下面的API:

gulp.src()
gulp.watch()
gulp.dest()

返回的都是Transform Stream对象,准确的说是包装过的Vinyl Stream实例对象,这些实例对象通过管道通信。

  • 管道

管道Stream操作是一个很重要概念,Node的Stream操作使用方法pipe(),传入的参数是目标流,返回的是目标流,使用形式如下:

src.pipe(dst)

其中src是源,也可以理解为上一步的输出流,输出流通过管道流入到dst返回。

  • 内存操作

Node的Stream操作在内存中进行,使用Stream进行多步骤操作不需要创建中间件,可以节省额外的src和dst,减少不必要的内存开销,提高构建性能。

Gulp实际应用

下面是很早之前使用的一段gulp.js文件,用来处理前端TypeScript文档自动生成,图片图片资源压缩成雪碧图,生成的雪碧图css文件更改路径。

'use strict';

let gulp = require('gulp');
let typeDoc = require("gulp-typedoc"); // 生成TypeScript文档
let gulpSequence = require('gulp-sequence'); //按照顺序逐个执行任务
let connect = require('gulp-connect'); // 启动node服务
let open = require('gulp-open'); //打开浏览器或者文件
let buffer = require('vinyl-buffer'); // 将Stream就转换为Buffe
let csso = require('gulp-csso'); // css压缩
let imagemin = require('gulp-imagemin'); //图片压缩
let merge = require('merge-stream'); // 一个任务执行多个流来源
let script = require('gulp.spritesmith'); //雪碧图

let _openFileUrl = 'src/app/*';
let _port = 8000;
let _baseUrl = 'http://localhost';
let serConfig = {
  root: 'docs',
  port: _port,
  open: {
    browser: 'Google Chrome'
  },
  livereload: true
};

let openBroweConfig = {
  uri: _baseUrl + ':' + _port + '/globals.html',
  app: 'chrome'
};

let docConfig = {
  "mode": "file",
  "out": "docs",
  "theme": "default",
  "ignoreCompilerErrors": "true",
  "experimentalDecorators": "true",
  "emitDecoratorMetadata": "true",
  "target": "ES5",
  "moduleResolution": "node",
  "preserveConstEnums": "true",
  "stripInternal": "true",
  "suppressExcessPropertyErrors": "true",
  "suppressImplicitAnyIndexErrors": "true",
  "module": "commonjs"
};

gulp.task('serve', function () {
  connect.server(serConfig);
});

gulp.task('open', function(){
  gulp.src(__filename)
    .pipe(open(openBroweConfig));
});

gulp.task('typeDoc', function(){
  return gulp
    .src([_openFileUrl])
    .pipe(typeDoc(docConfig))
});

gulp.task('sprite', function() {
  let imgUrl = './img/**/*.png';
  let imgOutUrl = './imgs/sprite';

  var spritData = gulp
    .src(imgUrl)
    .pipe(script({
      imgName: 'sprite.png',
      cssName: 'sprite.css'
    }));

  var imgStream = spritData.img
    .pipe(buffer())
    .pipe(imagemin())
    .pipe(gulp.dest(imgOutUrl));

  var cssStream = spritData.css
    .pipe(csso())
    .pipe(gulp.dest(imgOutUrl));

  return merge(imgStream ,cssStream);
});

gulp.task('default', gulpSequence('typeDoc', 'serve' ,'open'));

参考

浏览器渲染机制

浏览器渲染机制是前端性能优化一个重要的内容。了解和学习浏览器渲染机制,有利于优化我们的前端代码,提高网页性能。

不同的浏览器渲染机制不完全相同,我们以最常见webkit渲染引擎为例学习浏览器渲染机制(chrome和safari浏览器都是基于webkit内核开发,Google推出的Chrome工程版chromium,chromium在架构上使用了webkit的排版引擎)。

渲染路径

浏览器的关键渲染路径,指的是浏览器从获取到服务器资源,经过解析,构建,渲染,绘制等到最后请求内容呈现到Web页面的整个过程。这个过程可以概况为以下几个主要的步骤:

  1. 解析HTML构建 DOM tree(DOM树)
  2. 解析CSS构建 CSSOM tree(CSS规则树)
  3. DOM tree 和 CSSOM tree 合并生成Rende tree(渲染树/呈现树)
  4. 计算每个节点相对于浏览器窗口的位置和大小布局
  5. 绘制
  6. 合成线程获取DOM的多个层级,将每个层级划分成块图。通常大小256*256
  7. 合成线程将块图转化位图,在GPU中完成,存储在GPU内存中
  8. 多个位图在GPU 中复合成最终生成的内容,绘制在页面上

重绘和重排

浏览器的渲染路径不一定全部都会执行,比如当我们使用JavaScript脚本动态的修改CSS样式的位置属性时,浏览器会重新更新CSSOM 规则tree,重新布局,绘制,渲染到页面上,这个过程发生了浏览器的重排和重绘。

  • 重排:当呈现树(Render树)中的元素更新了位置或者大小(位置,尺寸,显示或者隐藏)等,会重新构建更新区域的CSSOM树,或者当DOM节点发生更改同样会引起浏览器重排,重新构建更新区域的DOM树,然后CSSOM规则树和CSS样式合并生成渲染树,Rende tree重新布局,绘制到页面。
  • 重绘:当页面只是修改了一些例如:color等属性,浏览器的渲染机制不需要重新构建DOM tree和CSSOM tree,只是发生后面的过程:重新构建渲染树,布局,绘制到页面。

常见的引起重排的一些操作或者CSS属性:

  • DOM操作:DOM的显示,隐藏,增加,删除
  • 位置:top,left,margin-left...
  • 大小:width,height
  • 浏览器的大小的更改和滚动

浏览器的重排和重绘都会触发新的渲染过程,消耗浏览器的内存资源,减少重绘和重排的频率和成本,可以提高网页的性能。

相对于重排,重绘不需要DOM tree或CSSOM tree的重新构建,重排发生重绘一定发生,反过来则不成立,所以在前端开发过程中尽量减少发生重排的频率,比如:DOM元素多个属性的修改,可以通过更改Class声明的方式一次处理多,减少重排的次数,也是性能优化的一个常用的措施。

进程和线程

进程是CPU分配资源的基本单位,线程是CPU调度的最小单位,一个进程程可以存在一个或者多个线程,每个进程的线程(一个或者多个线程)共享该进程的内存空间。

现代浏览器为了提高性能一般都会采用多进程架构,以Chrome浏览器为例:

  • Browser(浏览器):浏览器的控制和资源管理,负责各个便签的创建和销毁,下载,书签,网络请求和资源的访问
  • GPU:独立于其他的进程,它被分成多个不同的进程,专门处理GPU进程,比如3D绘制时的硬件加速
  • Renderer(渲染器):资源的解析,重构,布局,渲染等
  • 插件:网站插件的管理,比如Flash插件
  • 扩展:自定义或者第三方的插件扩展管理
  • 最版本的Chrome做了优化:每个跨网站 iframe 运行单独的渲染器进程

在浏览器渲染过程中,需要多个进程间协作完成:

  • Layer Compositor主要运行在渲染器进程的 Compositor 线程
  • Blink主要运行在渲染器进程的Renderer线程,Blink高度优化主线程的性能采用了单线程的架构,所以我们说JS是单线程运行
  • Display Compositor 主要运行在 Browser 进程的 UI 线程。

Renderer(渲染器)进程是浏览器的主进程,负责浏览器整个渲染过程 ,它是由多个主要组成的线程,不同的线程负责内容不同:

  • GUI渲染线程:负责DOM tree和CSSOM tree的构建,将解析完成的DOM tree 和COSSOM tree结合构建渲染树,然后布局绘制页面,发生重排时重新渲染。
  • JS引擎线程:执行JS代码
  • 事件触发线程:用户事件,点击,双击,输入等事件监听和处理线程
  • 定时器触发线程:定时器有单独的处理线程
  • HTTP异步请求线程:Ajax触发的HTTP请求由HTTP异步线程处理

JS引擎线程和GUI渲染线程是互斥的,当加载JavaScript代码的时候GUI渲染线程会被挂起,因为JS代码可能会动态的修改DOM元素的显示隐藏,大小,颜色等属性,发生重排和重绘引起重新渲染。

分析渲染过程中线程和进程的分析,我们可以利用渲染原理做一些性能优化:

提前加载CSS样式资源,更快的完成CSSOM tree的构建
减少CSS文件的大小和使用规则,规则越简单的CSS样式,布局越快
JS文件最后加载,或者使用defer,async属性设置JS文件异步加载,否则会影响GUI的渲染
合理的使用缓存,比如我们动态的创建模板,并且设置了多个样式属性,可以缓存该模板,下次使用不需要重新创建和属性设置,重排频率减少到一次,节省GUI渲染线程因为执行多次渲染的额外性能开销

参考

JavaScript设计模式-桥接模式

JavaScript设计模式-桥接模式

桥接模式:在系统沿着多个维度变化,使得实现业务代码和抽象部分代码进行解耦。

组件开发

在开发Web编辑器开发过程中,我们需要支持不同组件的动态生成,样式属性绑定和业务接口绑定,根据功能的不同,基础组件(如图片等)只需要绑定样式属性,业务组件(如列表等)需要绑定样式属性和后台业务接口。

根据业务背景我们尝试开发两种不同需求的组件开发DEMO版本:图片组件ImgComponent和人员列表组件PersonListComponent,其中:

  • ImgComponent:color和border-round样式的设置
  • PersonListComponent:color样式设置和api后台接口
class StyleColor {
    constructor(color) {
        this.color = color;
    }
    info() {
        console.log(`color is ${this.color}`)
    }
}

class StyleBorderRound {
    constructor(borderRound) {
        this.borderRound = borderRound;
    }
    info() {
        console.log(`border-roundis ${this.borderRound}`)
    }
}

class ApiInterface {
    constructor(api) {
        this.api = api;
    }

    info() {
        console.log(`Api is ${this.api}`)
    }
}

class ImgComponent {
    constructor(color, borderRound) {
        this.style_color = new StyleColor(color);
        this.style_border_round = new StyleBorderRound(borderRound);
    }
    
    initDate() {
        this.style_color.info();
        this.style_border_round.info();
    }
}

class PersonListComponent {
    constructor(color, api) {
        this.style_color = new StyleColor(color);
        this.api = new StyleColor(api);
    }

    initData() {
        this.style_color.info();
        this.api.info();
    }
}

let imgComp = new ImgComponent('red', '50%');
imgComp.initDate();

let personListComp = new PersonListComponent('blue', 'api/persons');
personListComp.initData();

我们尝试将组件拆分成独立的类,每种属性,API接口都由单独的类创建,当我们创建具体的组件的时候,我们根据需要自由组合属性设置和API接口,用来达到组件实现也公共抽象类解耦的目的。

JavaScript的防抖和节流

JavaScript的防抖和节流

函数的防抖和节流是性能优化的一种重要的手段,在一些场景比如:在高频搜索,实时检测,非法字符验证,表单验证,DOM元素拖拽等经常使用。

函数防抖

函数防抖: 在高频触发的场景中,任务在超过指定时间间隔才被执行。

防抖函数的核心代码:

function debounce(fun, time) {
    var timer = null;
    return function() {
        var that = this;
        var _arguments = arguments;
        if(timer) {
            clearTimeout(timer);
            timer = null;
        }

        timer = setTimeout(function() {
            fun.apply(that, _arguments)
        }, time)
    }
}

测试:高频搜素可以使用防抖函数进行优化处理。

let searchFun = function() {
    console.log(`searchFun _ ${new Date().getSeconds()}`)
}

let timer = setInterval(debounce(searchFun, 2000), 2010)

函数节流

函数节流:单位时间间隔内只执行一次。

function throttle(fun, timeGap) {
    let lastTime = null;
    return function() {
        let nowTime = new Date();
        if(!lastTime || nowTime - lastTime > timeGap) {
            fun();
            lastTime = nowTime;
        }
    }
}

测试:在实时编辑器的使用过程中,我们需要实时进行数据备份,可以节流函数进行性能优化。

let savePage = function() {
    console.log(`savePage _ ${new Date().getSeconds()}`)
}

let timer = setInterval(throttle(savePage, 2000), 10)

阅读计划和TODO List

阅读计划和TODO List

文学书单

序号 书名 状态 评分
1 贫穷的本质 end 3.5
2 十字军西征 start 2
3 雍正王朝 complete 4
4 冰与火之歌(一) complate 5

技术书单

深入阅读是进步的开始
编号 图片 书名 状态 描述 推荐
1 《JavaScript 忍者秘籍 end 内容对于2019年的前端开发来说比较基础,也比较经典,John的书最大的特点就是能简单的把事情讲清楚 4.5
2 《Web性能权威指南》 complete 这本书最大的特点就是能够针对性的讲解web性能优化,介绍TCP/UDP基础和涉及的优化措施,针对于http1.1,http2.0,WebSocket,WebRTC等应用层协议分别讲解,深度不够,但是作为主前端开发了解很多网络知识,尤其是涉及网络优化层面的 4
3 《白帽子讲Web安全(纪念版)》 complete 前端web安全开发入门书籍,从原理上来讲解所以有很多代码,内容其实并不系统,需要前端有一定的经验,要不然不好理解这些代码的含义,基础的东西但是没有深度,算是一个梳理,作为前端了解一下还是比较合适,目前服务器的还没有学习,客户端漏洞大概看了两遍,前端开发比较推荐的一本书,因为没有其他渠道系统的科普这些知识 3
4 《HTTP权威指南》 complete 大部分内容都做了了解,比较全面,HTTP,HTTP2.0,HTTPS不同版本的协议讲解的很仔细,Web缓存,Cookie,前端权鉴,报文,URL等于内容在前端开发过程比较重要,其他部分还没了解 4
5 《你不知道的JavaScript(上)》 end 上看常新的经典,上部是这个系列最基础也是最经典的一本书,内容不是很多但是是JS语言基石,重点讲解了作用域,闭包,this,原型对象和原型链的用法,内容相对比价深入需要一点JS本身的语言基础,比如作用域的内容从JS引起编译过程去理解作用域的概念,容易接受作用域是通过一套严格的语言规则,去维护声明的变量的查询和当前执行代码对这些声明的变量的访问权限 4.5
6 《JavaScript函数式编程指南》 start 学习函数编程范式,目前重点了解了函数柯里化,其他的慢慢了解,不是一个入门的书籍 3
7 《剑指Offer》 end 复习一下数据结构和算法的内容,对于开发者来说是非常必要,复习了两遍现在lc上剑指刷了两遍
  • 注意 : start 开始,complete 部分完成,end 完全结束

(二)TODO List

记下来万一忘记了
  1. SVG 应用开发

图表控件的开发

资料

  1. Canvas
    动画,可视化,尝试开发比较有意思的功能

update history

  • canvas-excel:canvas 实现Excel 简单功能的开发
  • M-charts:canvas实现简单的 charts demo
  1. 可视化

可视化工具库开发,tableeu作为终极目标
自动驾驶标注工具
3d可视化自动标注平台

资料

  1. 前端视频
    个人视频播放器开发

资料

  1. Python和数据分析
    全栈

资料

  1. 算法
    复习基本的数据结构和算法,leetcode捡起来

  2. 阅读
    相对于豆瓣和知乎,更需要大量深入的阅读,调整心态听说可以增加阅历

博客

优质的内容比较少,学会选择
  1. 今天看了一篇SVG的对比文章,应该算是做到了极致优化

Android微信上的SVG

前端实现 SVG 转 PNG
2. WebGL学习资料

GLSL基础

  1. 浏览器原理

浏览器原理
chromium缓存文档
了解资源的加载时序
16帧渲染
Chrome性能优化

  1. 性能测试

https://gtmetrix.com/
https://www.webpagetest.org/result/200608_A4_1bfbb7b52a274aefc774b462638b1630/
网站Web性能测试网站

  1. 前端性能优化

https://github.com/laoqiren/web-performance

JavaScript设计模式-迭代器模式

JavaScript设计模式-迭代器模式

迭代器模式:可以顺序的访问聚合对象内部的元素,不暴露对象内部结构。

我们实现一个自己的迭代器,理解其中的具体原理:

let eachFun = (function() {
    let data = [1,2,3,4,10,5];
    let len = data.length;
    let currentIndex = 0;
    return {
        hasNext: function() {
            return currentIndex < len;
        },
        pre: function() {
            let val = null;
            if(currentIndex > 0) {
                val= data[currentIndex-1];
                currentIndex -= 1;
            }
            return val;
        },
        next: function() {
            let val = null;
            if(this.hasNext()) {
                val = data[currentIndex];
                currentIndex += 1;
            }
            return val;
        },
        current: function() {
            return data[currentIndex];
        },
        first: function() {
            currentIndex = 0;
            return data[currentIndex];
        },
        last: function() {
            currentIndex = data.length-1;
            return data[currentIndex];
        }
    }
}())

let bool = eachFun.next();

console.log(eachFun.first())
console.log(eachFun.last())

我们对外提供自己封装的first(),last()等方法进行迭代操作,屏蔽了内部的具体实现方式。

JQuery迭代器访问原理

JQuery的迭代器使用方式:

$.each([1, 2, 3], function(value, index) {
   console.log(value, index)
})

我们自己动手实现JQuery的迭代器的过程:

Array.prototype.eachFun = function(callBack) {
    let that = this;
    for(let i=0; i < that.length;i++) {
        let item = that[i];
        console.log(item, i);
    }
}

let list = [1,33,4,23432,"test"];

list.eachFun(function(value, index) {
    console.log(value, index)
})

重构

在第一个例子中,我们的将迭代对象写在迭代器内部,无法通用,所以我们可以借鉴JQuery迭代器的实现原理,重构我们自己的迭代器。

Array.prototype.eachFun = function() {
    let data = this;
    let len = data.length;
    let currentIndex = 0;
    return {
        hasNext: function() {
            return currentIndex < len;
        },
        pre: function() {
            let val = null;
            if(currentIndex > 0) {
                val= data[currentIndex-1];
                currentIndex -= 1;
            }
            return val;
        },
        next: function() {
            let val = null;
            if(this.hasNext()) {
                val = data[currentIndex];
                currentIndex += 1;
            }
            return val;
        },
        current: function() {
            return data[currentIndex];
        },
        first: function() {
            currentIndex = 0;
            return data[currentIndex];
        },
        last: function() {
            currentIndex = data.length-1;
            return data[currentIndex];
        },
        len: function() {
            return len;
        }
    }
}

let list = [5, 90, 1000, 2];

let json = list.eachFun();
console.log(json.next())

前端开发涉及的Web安全

前端开发涉及常见的Web安全漏洞有:浏览器Web安全,跨站脚本攻击(XSS),跨站请求伪装(CSRF),点击劫持,HTTP劫持,DNS劫持,文件上传漏洞等,以跨站脚本攻击漏洞最为常见,作为前端开发需要了解一些基本的Web安全漏洞和相关的防御措施。

浏览器安全

  1. 同源策略

同源策略:是浏览器安全功能的基本措施,限制了来自不同源的脚本和document对当前对象的一些属性的获取或者设置。同源策略的影响因素:域名或者IP,子域名,端口,协议。

URL 结果 描述
https://github.com T
https://github.com/bojue T
http://github.com F 协议不同
https://github.com:4000 F 端口不同
https://me.github.com F 子域名

不是浏览器所有的元素收到同源策略的约束。srcipt,img,iframe,link等DOM标签可以通过src属性加载跨域资源,但是同时浏览器限制了JavaScript的权限,不能对读写返回的内容。

XMLHttpRequest对象本身不能进行跨站请求,但是可以通过设置HTTP header报文的方式,获得跨站资源请求的能力,这个策略基础是:浏览器上JavaScript没有访问HTTP header的权限。涉及的请求首部:Origin,响应首部:Access-Control-Allow-Origin。

同源策略也可能存在漏洞,比如IE8的CSS跨域漏洞。

  1. 沙箱(SandBox)

SandBox是提供一种设置访问内容范围的措施,确保沙箱内部的进程只能访问被分配的资源,不能访问未被分配的资源,从而对系统本身提供安全保护。

现代浏览器是一般基于多进程架构,以Chrome为例主要有browser进程,renderer进程,插件进程和扩展进程。不同的进程各自分工通过特殊接口通信,browser进行管理浏览器前端页面,renderer进程管理page tab,browser进程管理renderer进程的资源分配,chrome的SandBox用来保护renderer对象,确保renderer进程的访问权限只能访问被分配的资源。

  1. 恶意网站拦截

对于恶意网站的定义,那些对于普通网民或者社会有害的网站都是恶意网站,仿冒,木马,盗号,博彩,色情这些常见的恶意网站,一般浏览器都有恶意网站举报功能,对于确认的恶意网站浏览器厂商会维护一份恶意网站黑名单,浏览器定时获取黑名单的最新目录进行拦截操作。

跨站脚本攻击(XSS)

跨站脚本攻击(XSS),全称Cross Site Script,为了区层叠样式表CSS使用了XSS简称。

跨站脚本攻击的产生是因为Web页面被植入了恶意的JavaScript代码,当用户浏览页面时诱导用户进行点击等操作后,这些恶意代码被执行,从而完成攻击目的。XSS按照不同的效果分类:

  1. 反射型XSS

将用户输入“反射”到浏览器上成为反射型XSS,也成为非持久性的XSS,比如:

<div><script>alert("xss")</script></div>

  1. 存储型XSS

相对于非持久性XSS,存储型的XSS是将用户输入进行数据持久化操作,具有一定的稳定性。

  1. 常见的XSS攻击

a. Cookie劫持

存储型XSS常见的安全漏洞有Cookie劫持,因为Cookie保存用户的登录凭证,如果获取到用户当前的Cookie可以绕过登录密码进行Web操作,具有很大的破坏力。可以通过设置Cookie过期时间,设置Cookie的HttpOnly标识,绑定客户端IP等操作封装Cookie劫持。

b. 模拟HTTP请求

可以通过模拟GET,POST请求请求或者操作服务器资源也是XSS漏洞的一种情况,通过操作DOM页面的操作完成对服务器的攻击。

c. 获取用户浏览器信息

通过JavaScript获取用户浏览器UserAgent获取用户浏览器准确信息,因为不同的浏览器都有自己特定的一些属性或者方法,使用浏览器版本特征判断可以准确的获取用户的浏览器信息,因为UserAngent对象是可以伪造的。

通过判断不同的操作系统,不同的浏览器以及版本信息,攻击者可以更精准的实施安全攻击。

XSS的防御:

a. 前端输入输出进行检查,对于特殊字符进行过滤,转码等操作

b. 使用带有HttpOnly的Cookie

c. 富文本编辑器禁止使用事件,可以实现跨域操作的inframe,form,base等危险的标签也会被禁止,比如github上<base> 元素字符串格式也会被转义。

跨站点请求伪装(CSRF)

 跨站点请求伪装 (CSRF即Cross-site request forgery)利用用户身份,执行非本意的操作。

CSRF是一种挟持了用户身份,在已经登录的Web应用上执行非本意的攻击操作。它的请求是跨域并且利用登录Cookie,token等信息伪造的。

  1. Cookie的获取

CSRF漏洞的第一步是伪造,在Web应用的删除等操作是需要登录认证才可以执行,所以需要获取用户的登录信息比如Cookie。

浏览器的Cookie策略分为两种:Sesstion Cookie和Third-party Cookie。Sesstion Cookie是临时性Cookie,保存在浏览器进程的内存中,打开新的Tab页面依旧可以在内存中获取Sesstion,生命周期在浏览器关闭时失效;Third-party Cookie又称为本地Cookie,需要在服务器设置Expire时间,保存在本地。

一些浏览器因为同源策略的原因,限制的了img,iframe等元素对Third-party Cookie的发送,比如IE浏览器,但是Firefox浏览器可以允许发生Third-party Cookie,在Firefox浏览器可以通过iframe等元素获取Third-party Cookie。比如

<iframe ="http://www.a.com"></iframe>

在火狐浏览器上,我们在b网站获取a网站的内容,同时获取到Cookie的内容。

CSRF的防御:

a. 验证码:目前相对比较有效的防止CSRF漏洞的方式,通过强制的人机交互验证,更大程度的限制用户在不知情的情况下进行Web应用操作。

b. Referer Check:另外一种防止CSRF的思路,获取请求的“源”信息以判断请求是否合法,但是在实际应用中可能因为用户隐私设置,HTTPS跳转HTTP而导致获取不到相关参数,作为CSRF漏洞防御的一个补充措施。

c. Token认证:比较通用的预防措施,Token本身是一个不容易被猜中的随机数,分别保证在Cookie和和form中,通过判断Cookie的token和form的token是否一致。

点击劫持(ClickJacking)

点击劫持(ClickJacking)是一种视觉欺骗,使用iframe构建欺骗的DOM诱导用户非本意操作。

常见的点击劫持:

  1. 点击劫持
    在Web页面使用iframe构建透明的页面,完全覆盖当前页面,当用户操作点击页面的时候触发iframe构建的页面,可以发送攻击者需要的数据完成攻击。

  2. 图片覆盖
    使用ifame构建完整的图面覆盖当前页面的图片,当用户点击页面图片的时候,触发攻击代码,也可能构建新的电话号码覆盖网站原本提供的联系电话,导致用户上当受骗。

  3. 拖拽劫持
    iframe构建可以拖拽的元素,当用户拖拽元素的过程中触发拖住事件,敏感信息被发生到攻击者服务器。

  4. 触屏劫持
    发生在智能手机上的劫持漏洞,原理类似PC端,使用iframe构建元素欺骗用户。

点击劫持的防御:

点击劫持本质上是一种视觉上的欺骗,使用iframe构建欺骗DOM,用户操作这些DOM元素触发攻击事件,所以点击劫持的防御可以禁止使用iframe,常见的处理方式有两种。

a. frame busting:禁止iframe嵌套。

b. X-Frame-options:处理点击劫持的HTTP首部,可以配置是否使用iframe,以及配置iframe的来源。

HTTP劫持

HTTP劫持多见于网络运营商的操作,因为HTTP是明文传输,所以当运营上获取到网络链路上的HTTP请求时,在相应报文内容添加广告代码,当页面存在广告小弹窗说明页面目前被劫持,也可能是网络运营商的网络被攻击。

HTTPS是加密传输所以可以有效防止HTTP劫持。

HTTPS可以防止HTTP劫持。

DNS劫持

DNS劫持分为两种情况,一种是域名无法解析,则页面被路由到错误纠正页面,比如电信首页;第二种是域名解析被恶意路由到其他页面,域名是A网站结果被恶意劫持跳转到B网站导致无法访问A网站。

常见的钓鱼网站原理,其中灰色产业涉及违法问题现在已经被严厉监管。

文件上传漏洞

因为文件上传可能直接上传可执行脚本文件,所以需要处理文件上传验证以确保上传的服务器的文件是可信的,在Gmail文件上传使用文件类型检查文件格式,提供文件格式黑名单进行过滤,QQ邮箱直接使用文件后缀判断文件格式,提供白名单上传也就是指定的文件格式才可以被上传(Qmail是文件后缀判断,白名单处理是猜测的,之前做过类似处理对比过两种不同的文件处理方式)。

参考

  1. 前端安全知多少
  2. 浏览器沙箱到底是什么
  3. 浅谈XSS攻击那些事
  4. 白帽子讲Web安全(纪念版)
  5. 前端安全之CSRF攻击
  6. 运营商的HTTP劫持具体使用的什么技术方案,使用通用服务器能否实现相似功能

理解JavaScript闭包

闭包(Closure)又称为词法闭包或者函数闭包,函数和函数的词法环境共同构成闭包。函数内部可以访问外部函数作用域。函数内部词法环境的变量被引用后,可以在这个词法环境之外使用。

词法作用域

在深入学习闭包之前,我们需要了解与闭包相关的基本知识,词法作用域。

JS的作用域的概念:引擎用来管理当前作用域和嵌套的子作用域中根据标识符名称进行变量查找的一套规则。

一般语言在编译的编译过程主要分为三步:

  1. 分词:将字符串组成的JavaScript代码分解成有意义的代码块(词法单元)。
  2. 解析:将词法单元流解析成抽象语法树(AST)。
  3. 代码生成:将AST转化成可执行的代码。

词法作用域是发生在编译第一阶段即词法阶段,词法作用域代码是由定义变量,函数和块作用域的位置所决定的。我们可以通过JavaScript函数实例理解词法作用域:

function fun(a) {
   var b = a + 2;
   function secFun(c) {
      console.log(a, b , c)
   }
   secFun(b + 4)
}
fun(1) ; // 1, 3, 7

在函数执行过程中,函数创建了逐级嵌套的作用域:

  1. 首先是一个全局作用域,包含一个标识符:fun
  2. 执行fun函数,这时候我们在fun函数内部创建了新的作用域:包含三个标识符:secFun,a,b,
  3. 执行secFun函数,我们在secFun函数内部创建新的作用域:包含一个标识符:c

我们可以看到,JavaScript的词法作用域在编译之前已经确定了,由代码书写的位置决定。

什么是闭包

下面的函数是一个完整的闭包:

function closureFun() {
   var name = "closureFun";
   function getName() {
      console.log(name)
   }
   return getName;
}
var nameFun = closureFun();
nameFun();

我们结合JS的词法作用域的内容,分析一下闭包的执行过程:

  1. 全局作用域,包含两个标识符:nameFun ,closureFun
  2. 执行closureFun创建新的作用域,包含两个标识符:name ,getName,通过scopeChain关联到全局作用域
  3. 以值的形式返回内部标识符getName的函数,赋值给变量nameFun
  4. 执行nameFun,查询执行标识符getName,实际上调用的是内部函数getName
  5. getName被执行,创建新的作用域,包含一个表示符:name,通过scopeChain关联到函数closureFun的作用域
  6. name变量在赋值的时候,通过scopeChain(作用域链)查询,当前作用域没有声明该变量,则沿着作用域链向上逐级查询,执行到closureFun函数作用域,查询到变量name,打印执行结果。

我们知道,我们在执行函数的时候,会创建一个新的作用域,称为私有作用域,当函数执行完毕之后为了节约内存JS引擎会将这个私有作用域会被销毁,定义在私有作用域的函数和变量都会被清除。

但是在定义函数词法作用域以外执行函数,可以保持函数内部定义的私有作用域,形成一个闭包。更直观的理解,我们可以在函数closureFun外面访问到函数内部定义的变量。

我们也可以这样理解闭包:访问并记住词法作用域的函数叫闭包。

闭包的应用

在前端开发过程中,我们经常使用的闭包应用包括:匿名立即执行函数,存储变量,封装私有变量。

  • 匿名执行函数
<ul>
   <li>dom1</li>
   <li>dom2</li>
   <li>dom3</li>
</ul>

上面的html代码中,我们设定了一个常见的需要,我们需要当我们点击li元素的时候,获取当前li元素的下标,因为根据li元素的名称可以获取li元素的理解,所以我们的需求可以抽象:

  1. 获取li元素的集合
  2. 遍历集合给每个元素绑定click事件
  3. 获取当前的元素下标index即可

根据上面的需求转化,我们很容易写出来下面的解决方案:

let doms = document.getElementsByTagName('li');
for (var i = 0; i < doms.length; i++) {
    doms[i].onclick = function() {
        alert(i)
    }
}

当时我们点击DOM元素的时候,发现这个是行不通的方案,我们每次获取到的下标都是i变量最后的值。我们获取到的下标i是一个引用值,执行循环运行完成的值。

我们可以使用闭包,完成上面的需求:

let doms = document.getElementsByTagName('li');
for (var i = 0; i < doms.length; i++) {
    (function(index) {
        doms[index].onclick = function() {
            alert(index)
        }
    })(i)
}

我们通过匿名执行函数,每次遍历获取当前的下标i,匿名函数在内部的作用域获取标识符index,保存下标的副本到变量index,这样每个匿名函数都有一个内部的变量存储执行时的下标i的值。

  • 变量存储和管理

在我们开发过程中,我们可以使用闭包的特性创建常量:

const person = () => {
    let name= "javaScript"
    return () => {
        return name;
    }
 }
let personName= person ();
personName() // javaScript
personName() // javaScript

这样我们无论如何去调用personName函数,始终获取到name的变量值,并且无法修改,这样我们就可以在JS开发过程中使用闭包来完成常量的封装。

const personRun = () => {
    let stepCount = 0;
    return () => {
        return ++stepCount
    }
 }
let run = personRun();
run() //1
run() //2

我们可以使用personRun 函数词法作用域内的变量stepCount ,personRun函数运行,返回引用了stepCount变量的内部函数本身,赋值给外部run标识符,我们可以通过调用run函数完成对stepCount变量的管理。

  • 封装私有变量
const jsVersion = () => {
    let _version = 'ES5';

    _getVerson = () => _version;
    _setVerson = (version) => {
        _version = version
    }

    return {
        setVersion: _setVerson,
        getVerson: _getVerson
    }
}
let jsv = jsVersion();
jsv.getVerson() //ES5
jsv.setVersion('ES6')
jsv.getVerson() //ES6

封装私有变量是闭包的一个很实用的应用,也可以理解成闭包的对变量的一种管理,原理是在闭包创建的词法作用域内,外部无法直接访问词法作用域内部定义的变量,也就是说词法作用域定义的变量对外部是完全屏蔽的,相当于强语言类型的私有变量的概念,我们可以通过对外提供接口的方式操作内部封装的私有变量。

我们需要明白闭包使用是有代价的,因为闭包内变量的引用无法被自动释放,所以容易造成内存泄漏问题。

参考

ES6语法中类(class)的实现原理

JavaScript语言不同于其他的类C语言,没有提供类的概念,但是可以提供类似的语法糖来实现JS面向对象的编程范式,本质上不是严格意义上的类

我们创建一个Person对象,包含两个属性name,age和一个普通的方法run()和静态方法say()。

ES6 class

class Person {
     constructor(name, age) {
    	this.name = name;
      	this.age = age;
    }
  
    static run() {
      console.log("run")
    }
    say() {
    	console.log("hello!")
    }
}

Person.run();

通过 static 关键字定义静态方法,静态方法只能通过类本身去调用,不能通过实例来调用。

ES6构造函数

var Person = function () {
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    Person.run = function run() {
        console.log("run");
    };

    var _proto = Person.prototype;

    _proto.say = function say() {
        console.log("hello!");
    };
    return Person;
}();

ES5原理

function _defineProperties(target, props) { 
    for (var i = 0; i < props.length; i++) { 
        var descriptor = props[i]; 
        descriptor.enumerable = descriptor.enumerable || false; 
        descriptor.configurable = true; 
        if ("value" in descriptor) descriptor.writable = true; 
        Object.defineProperty(target, descriptor.key, descriptor);
    } 
}

function _createClass(Constructor, protoProps, staticProps) { 
    if (protoProps) _defineProperties(Constructor.prototype, protoProps); 
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor; 
}

var Person = function () {
  function Person(name, age) {
    if(!(this instanceof Person)) {
        throw new TypeError("Cannot call a class as a function")
    }
    this.name = name;
    this.age = age;
  }

  _createClass(Person, [{
    key: "say",
    value: function say() {
      console.log("hello!");
    }
  }], [{
    key: "run",
    value: function run() {
      console.log("run");
    }
  }]);

  return Person;
}();
  • constructor我们直接声明Person函数对象,并完成属性name和age的绑定
  • this instanceof Person :我们判断构造函数是否是通过new操作符调用

这里我们参考了bable.js的转义代码:_createClass和_defineProperties两个函数。

_createClass(Constructor, protoProps, staticProps)

  • Constructor:需要设置属性的对象
  • protoProps:直接绑定在对象上的属性或者方法
  • staticProps:绑定在原型链上的属性和方法
  • protoProps和staticProps都是数组

_defineProperties(target, props)

  • target:需要定于属性的对象或者对象的prototype属性
  • props:对象需要设置属性列表

参考

CSS的BFC(块级上下文)特性

CSS的BFC(块级上下文)特性

BFC(Block Formatting Context)又称块级上下文,是块盒子的渲染和显示的独立环境,不同的BFC布局互不影响,BFC内部布局不会影响到外部,反之同样成立。

BFC的布局特点

BFC包含了创建它的元素内部的所有的内容。在BFC元素中:

  • 它的子元素会一个接着一个布局
  • 垂直方向子元素的起点是BFC元素的顶部
  • 两个相邻元素之间的垂直距离取决于它们之间的垂直外边距(margin)特性,因为在BFC子元素的垂直外边距会折叠。
  • 每一个子元素的左外边和BFC块元素的左外边相接触,除非子元素创建新的BFC块

如何触发BFC

  • 根元素
  • 浮动元素(float 不为none)
  • 绝对定位(postion:absolute / fixed)
  • 行内元素(display:inline-block)
  • 表格元素
  • overflow不为visible
  • 现代自适应布局(display:grid/flex/inline-grid/inline-flex)
  • 多列容器(column-count不为auto /column-width不为auto)

作用

  • 防止和浮动元素重叠
<body>
    <div style="float:left">left</div>
    <p style="overflow:hidden; _zoom:1">
        rigint
    </p>
</body>
  • 清除元素内部浮动
    设置父类元素为BFC就可以清除子元素浮动:设置样式overflow: hidden,对于IE6加上zoom:1
  • 处理元素margin折叠问题
    在同一个BFC元素内,相邻元素或者嵌套元素会发生margin重叠,相邻元素可以设置为不同的BFC;嵌套元素可以设置父类为BFC元素来防止margin折叠

参考

HTTP报文详解

HTTP报文详解

HTTP是超文本传输协议(HyperText Transfer Protocol),处在网络四层架构的应用层。

HTTP报文由简单字符串组成的文本,HTTP报文是对HTPP请求的额外描述,方便客户端和服务器解析,开发者可以通过报文快速获取当前状态信息,报文分为请求报文和相应报文。

报文流

报文是流动在客户端和服务器之间的数据块,数据块以文本形式的元信息开头,这些信息对报文内容和含义的描述。

报文使用流入和流出表示报文的方向,发起端作为报文的上流,相应端作为报文的下游。报文总是从发起端流入相应端。在请求发起阶段客户端是报文的发起端,在请求相应阶段客户端作为报文的相应端。

报文组成

一个完整的报文由起始行,首部,可选的报文主体3部分组成:

  • 起始行

报文分为请求报文和相应报文,起始报文是报文的第一行字符串,起始报文说明报文要处理的内容,相应报文返回服务器发生了什么。

  • 首部字段

完整的报文包含0个或者多个分行的ASCII文本首部字段组成,为了方便服务器解析,首部字段由:隔开的键和值两部分组成,首部以一个空行结束。首部字段可以为空。

  • 报文主体

报文主体和首部用空行隔开,报文主体是服务器相应的资源,可以是任何形式的数据,包括字符串,html等文本形式,也可以是二进制形式,文本形式可以为空,比如HTTP协议的OPTIONS方法就是一个返回主体为空的请求。

报文语法

报文根据请求报文和相应报文存在不同的报文语法,每个括号词汇所表示的是一个独立的报文组件。

  • 请求报文

      <method><request-URL><version>
      <headers>
      <entrty-body>
    
  • 相应报文

      <version><satatus><reaspon-pharse>
      <headers>
      <entrty-body>
    
报文组件 名称 描述
method 方法 客户端发起的请求方式,比如GET,PUT,POST,DELETE
request-URL 请求URL 请求资源的绝对路径
version HTTP� 版本 常见的版本:HTTP1.0, HTTP1.1,HTTP2.0
status 状体码 服务器相应的状体码,用3位数字描述的请求结果,常见的200表示请求成功,404未找到资源,304本地缓存
reason-pharse 原因短语 描述status的原因文本,对开发者提供的可读性友好的请求状体描述
headers 首部 包含0个或者多个首部,与主体使用空格隔开,HTTP0.9版本不包含首部,HTTP1.1版本有效请求和相应必须包含特定的首部
entity-body 实体主体部分 任意格式的数据组成,不是必须的

方法

HTTP 请求报文起始行以方法开头,方法用来告诉服务器要做什么操作。

方法名称 描述
GET 请求指定资源,仅可以用来获取资源
HEAD 和GET相同,只包含响应首部,没有响应主体
POST 提交指定的资源,导致服务器状态发生变化
PUT 提交指定之言,取代服务器现有资源
DELETE 删除指定资源资源,特殊资源删除操作之前,一般会有权限验证前一步操作
OPTIONS 获取目的资源所支持的通信选项, 可以支持一个特定URL,也可以支持整个网站
CONNECT 建立一个到由目标资源标识的服务器的隧道
PATCH 方法用于对资源应用部分修改
TRACE 沿着到目标资源的路径执行一个消息环回测试

HTTP支持方法的扩展,可以根据HTTP规范扩展适用自己的额外的自定义方法,一般很少用到。

状态码

状态码存在响应报文返回客户端,由3位数字组成,方便处理服务器响应状态,数字状态码一般伴随文字表述短语存在。

目前已经分配的状态码:

整体范围 已定义 描述
100 ~ 199 100 ~ 101 信息提示
200 ~ 299 200 ~ 206 成功
300 ~ 399 300 ~ 315 重定向
400 ~ 499 400 ~ 415 客户端错误
500 ~ 599 500 ~ 505 服务器错误

状态码摘取(CMD: HTTP响应状态):

状态码 描述
100 连接正常,客户端可以正常请求,请求完毕则忽略。
101 服务器正在响应客户端Upgrade请求头,进行协议切换。
200 响应成功。
201 资源创建成功,比如使用POST方法成功创建新的对象并返回响应。
202 服务器接收到请求,但是尚未处理,处理结果未知。
203 请求被成功响应,获取的负载资源被代理服务器修改。
204 成功状态,但是客户端不需要更新页面,204响应结果可以通过配置响应首部ETag进行缓存。
206 通知客户端重置文档视图,比如刷新页面,重置表单。
300 重定向响应,标示又多种可能响应,服务器可能通过提供Location首部,提供优先选择。
301 永远重定向,由Location首部字段指定永远重定向URL,搜索引擎会根据响应更新。
302 暂时重定向,由Location首部字段指定URL,搜索引起不响应更新。
303 重定向状态,通常用作POST,PUT操作的返回结果。
304 使用缓存的内容。
305 永远重定向,资源被永远移动到Location指定的URL上,浏览器会进行重定向,搜索引起会更新URL连接。
400 语法无效,导致服务器无法正确识别。
401 客户端错误,状态码和WWW-Authenticate一起发送,要求身份认证。
403 客户端错误,没有权限访问,不能再继续验证,即永久禁止。
404 服务器无法找到资源。
405 禁止使用方法。
410 服务器资源丢失,并且永远丢失。
500 服务器内部错误。
501 请求方法服务器不支持。
502 表示代理服务器从上游服务器接收的响应无效。
503 服务器暂时处于请求处理状态。
505 表示服务器不支持请求使用的HTTP版本。

原因短语

原因短语和数字状态码成对出现,为响应报文数字状态码提供了文本格式,使用短语描述状态,HTTP规范没有具体要求,开发过程中一般会使用见名知义的原因短语,用来给开发者和用户友好的提示。

CMD网站简单节选一些友好的原因短语:

状态码 原因短语 描述
100 Continue 连接状态目前为止可行的
101 Switching Protocol 协议切换
200 OK 请求成功
201 Created 创建成功
300 Multiple Choice 重定向,有多个可能响应,可能存在优先选择
404 Not Found 资源没找到
502 Bad Gateway 服务器错误的响应

在Chrome开发过程中,可以使用开发者模式查看请求操作的状态码,可以通过Status Code 字段查看数字状态码和原因短语,下面是使用掘金首页查看的信息:
掘金首页GET请求状态码

版本号

HTTP版本号使用HTTP/x.y的形式进行表述,类似于现在的NodeJs版本管理方案,x.y不是小数,x,y都是独立的数字,HTTP版本向下兼容。

HTTP版本升级伴随着性能优化或者特征扩展,以支持更多功能。

最早的版本是HTTP/0.9,仅支持GET方法,没有版本号,首部信息,媒体内容仅支持HTML文档,没有其他MIME类型,很快被HTTP/1.0取代。

目前常用的HTTP版本是HTTP1.1,相对于之前的版本HTTP1.1扩展了更多的首部以支持更多的功能,引入了性能优化措施,校正了结构缺陷,删除了不友好的特性。

HTTP首部

HTTP首部是添加在请求报文和相应报文的键值对列表,描述请求和相应的额外信息。

HTTP首部可以分为:

  • 通用首部:可以使用在请求报文和响应报文中的首部字段
  • 请求首部:仅使用在请求报文中的首部字段
  • 响应首部:仅使用在响应报文中的首部字段
  • 实体首部:描述实现信息的首部字段
  • 扩展首部:自定义的首部字段,HTTP首部支持扩展首部

首部举例(CDM 首部列表

首部字段 首部类型 描述
Accept: text/html 通过首部 接收HTML文档,text/html是 MIME 类型的具体一种
Accept: image/* 通用首部 接收所以的图片MIME类型
Connection: keep-alive 通用首部 Connection 决定当前的事务完成后,是否会关闭网络连接,keep-alive表示继续打开连接
From: email地址 请求首部 发送用户Eamil地址
Content-Language 实体首部/内容首部 描述用户语言,常用于多语言处理
Set-Cookie: cookie-name = cookie-value 响应首部 服务器向客户端发送cookie

参考

  1. HTTP报文详情
  2. MDN: HTTP
  3. HTTP权威指南

常见的前端鉴权方式

前端鉴权是Web开发重要的环节,在涉及到登录业务的所有站点中,都存在前端鉴权的过程,常见的前端鉴权环节有:基本认证,Session+Cookie,Token,点击登录SSO,OAuth五种方法。

Session + Cookie

HTTP协议无状态,每次请求服务器需要判断具体用户等当前状态,可以使用Session+Cookie的方案解决这样的需求。

流程:

  1. 用户登录成功,创建唯一sessionId,并保存到服务器和cookie
  2. 响应首部携带cookie到客户端
  3. 客户端请求携带cookie到服务器
  4. 判断cookie中的sessionId是否正确
  5. 验证成功,则继续业务请求

特点:

  • sessionId存储在服务器,服务器内存压力增加
  • cookie中保存sessionId的内容,容易被截取进行CSRF攻击
  • 服务器端集群时,sessionId难以共享

Token

Token也是一种很常见的前端鉴权方案,经常使用JWT(json web token)创建Token实例,JWT支持JSON数据格式进行跨语言构建,构建消耗计较小

流程:

  1. 用户登录成功,创建token
  2. token返回客户端,由客户端保存
  3. 客户端请求,携带token
  4. 服务器验证token正确性:是否正确,是否过期

特点:

  • 服务器不需要存放token
  • token不存放在cookie,相对比较安全
  • 可以使用动态token
  • 对服务器集群没有影响

SSO 单点登录

SSO(Single Sign On),在多个应用系统中,我们只需要一次登录认证过程,就可以相互任务的系统。

流程:

  1. 用户登录的信息,生成一个统一的token凭证
  2. 保存在Redis中,设置过期时间,统一管理
  3. 其他系统的用户认证请求SSO系统进行登录,得到SSO的token凭证添加到Cookie,
  4. 每次请求携带Cookie,服务端的拦截器首先拦截验证token,判断是否登录

与Session不同的是:

  • 在session中,我们的用户登录保存在session中,现在我们保存在redis中
  • redis只存储一个统一的token作为SSO认证系统,其他系统通过SSO进行登录认证

OAuth

OAuth是授权框架,目前包含了OAuth1.0和2.0两个版本。因OAuth2.0相对于OAuth1.0更简单,更安全,支持网站更多,所以我们只讨论OAuth2.0版本。

流程:

  1. 第三方应用请求用户授权
  2. 用户同意授权,并返回凭证(code)
  3. 第三方应用通过code向授权服务器请求授权
  4. 授权服务器验证code通过,返回一个同意授权凭证(Access Token)
  5. 第三方请求通过Access Token向资源服务器请求资源
  6. 资源服务器验证Access Token,如果成功则返回请求资源

参考

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.