GithubHelp home page GithubHelp logo

blog's Introduction

Hi there 👋

😄 程序员Zwen的编程记录


blog's People

Contributors

dependabot[bot] avatar kkxiaojun avatar zhaobulang avatar

Stargazers

 avatar

Watchers

 avatar  avatar

blog's Issues

underscore源码

underscore

void 0

  1. void expression均为undefined
  2. window下的undefined是可以被重写的,于是导致了某些极端情况下使用undefined会出现一定的差错
  3. 防止undefined被重写
  4. 非严格模式下,undefined是可以重写的,严格模式则不能重写

排序算法js实现

排序算法js实现

要使用的工具函数

function isArray(arr){
    if (typeof Array.isArray === 'function'){
        return Array.isArray(arr);
    } else{
        return Object.prototype.toString.call(arr) === '[object Array]'; 
    }
}

冒泡排序

原理

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成

步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现

// 对还未排序的全部数,自左向右对相邻的两个数进行对比,较大的数往下沉,较小的数往上冒泡
function bubbleSort(arr) {
    if (!isArray(arr) || !arr.length) {
        return;
    }
    let temp = null, len = arr.length;
    for (let i = 0; i < len - 1; i++) { // 每循环一次,就确定一个相对最大元素
        for (let j = 1; j < len -i; j++) { // 内层:有i个已经排好序,剩下:len - 1 -i 个需要排序 
            if (arr[j-1] > arr [j]) {
                temp = arr[j-1];
                arr[j-1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}
// 改进:对于后面已经排好序的数进行记录,下一次不再进行比较
function bubbleSort2(arr) {
    if (!isArray(arr) || !arr.length) {
        return;
    }
    let temp = null, pos = arr.length, end = arr.length;
    for (let i = 0; i < arr.length; i++) {
        end = pos;
        for (let j = 1; j < end; j++) {
            if (arr[j-1] > arr [j]) {
                temp = arr[j-1];
                arr[j-1] = arr[j];
                arr[j] = temp;
                pos = j;
            }
        }
    }
    return arr;
}
// 改进:从左右两边,正反向分别冒泡
function bubbleSort3(arr) {
    var low = 0;
    var high = arr.length - 1; //设置变量的初始值
    var tmp, j;
    console.time('2.改进后冒泡排序耗时');
    while (low < high) {
        for (j = low; j < high; ++j) //正向冒泡,找到最大者
            if (arr[j] > arr[j + 1]) {
                tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
            --high; //修改high值, 前移一位
        for (j = high; j > low; --j) //反向冒泡,找到最小者
            if (arr[j] < arr[j - 1]) {
                tmp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = tmp;
            }
            ++low; //修改low值,后移一位
    }
    return arr;
}

选择排序

原理

简单选择排序的基本**非常简单,即:第一趟,从 n 个元素中找出关键字最小的元素与第一个元素交换;第二趟,在从第二个元素开始的 n-1 个元素中再选出关键字最小的元素与第二个元素交换;如此,第 k 趟,则从第 k 个元素开始的 n-k+1 个元素中选出关键字最小的元素与第 k 个元素交换,直到整个序列按关键字有序。

步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

  3. 重复第二步,直到所有元素均排序完毕。

代码实现

function selectionSort(arr) {
    if (!isArray(arr) || !arr.length) {
        return;
    }
    let minItemIndex = 0, temp = null, len = arr.length;
    for (let i = 0; i < len; i++) {
        minItemIndex = i;
        for (let j = i + 1; j < len; j++) {
            if (arr[minItemIndex] > arr[j]) {
                minItemIndex = j;
            }
        }
        if (i !== minItemIndex) {
            temp = arr[minItemIndex];
            arr[minItemIndex] = arr[i];
            arr[i] = temp;
        }
    }
    return arr;
}

插入排序

原理

原理是通过构建youxuxulie,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入

步骤

  1. 将第一个待排序序列看作是一个有序序列,把第二个到最后一个看作是一个无序序列
  2. 从头到尾依次扫描未排序序列,将扫描到的元素插入到适当的位置

代码实现

      // 原理是通过构建youxuxulie,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入
      function insertionSort(arr) {
        if (!isArray(arr) || !arr.length) {
          return;
        }
        let current = null, preItemIndex = null, len = arr.length;
        for (let i = 1; i< len; i++) {
          preItemIndex = i - 1;
          current = arr[i];
          while (preItemIndex >= 0 && current < arr[preItemIndex]) {
            arr[preItemIndex + 1] = arr[preItemIndex];
            preItemIndex--;
          }
          // 满足条件,交换
          if (current !== arr[preItemIndex+1]) {
            arr[preItemIndex + 1] = current;
          }
        }
        return arr;
      }

希尔排序

原理

希尔排序是基于插入排序的以下两点性质而提出的改进方法:

  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
  2. 但是插入排序一般是低效的,因为插入排序每次只能将数据移动一位

希尔排序基本**是:先将整个待排序序列分割成若干个子序列分别进行直接插入排序,待整个序列中"基本有序"时,再对全体记录进行直接插入排序。

步骤

  1. 选择一个增量序列,t1、t2...tk
  2. 按增量序列个数,进行k趟排序

代码实现

function shellSort(arr) {
    var len = arr.length,
        temp,
        gap = 1;
    while(gap < len/3) {          //动态定义间隔序列
        gap =gap*3+1;
    }
    for (gap; gap > 0; gap = Math.floor(gap/3)) {
        for (var i = gap; i < len; i++) {
            temp = arr[i];
            for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
                arr[j+gap] = arr[j];
            }
            arr[j+gap] = temp;
        }
    }
    return arr;
}

归并排序

原理

归并排序是另一类不同的排序方法,这种方法是运用分治法解决问题的典型范例。归并排序的基本**是基于合并操作,即合并两个已经有序的序列是容易的,不论这两个序列是顺序存储还是链式存储,合并操作都可以Ο(m+n)时间内完成(假设两个有序表的长度分别为 m 和 n)。

步骤

为此,由分治法的一般设计步骤得到归并排序的过程为:

  1. 划分:将待排序的序列划分为大小相等(或大致相等)的两个子序列;
  2. 治理:当子序列的规模大于 1 时,递归排序子序列,如果子序列规模为 1 则成为有
    序序列;
  3. 组合:将两个有序的子序列合并为一个有序序列。

代码实现

function mergeSort(arr) {  // 采用自上而下的递归方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}

快速排序

原理

快速排序是将分治法运用到排序问题中的一个典型例子,快速排序的基本**是:通过一个枢轴(pivot)元素将 n 个元素的序列分为左、右两个子序列 Ll 和 Lr,其中子序列 Ll中的元素均比枢轴元素小,而子序列 Lr 中的元素均比枢轴元素大,然后对左、右子序列分别进行快速排序,在将左、右子序列排好序后,则整个序列有序,而对左右子序列的排序过程直到子序列中只包含一个元素时结束,此时左、右子序列由于只包含一个元素则自然有序。

步骤

用分治法的三个步骤来描述快速排序的过程如下:

  1. 划分步骤:通过枢轴元素 x 将序列一分为二, 且左子序列的元素均小于 x,右子
    序列的元素均大于 x;

  2. 治理步骤:递归的对左、右子序列排序;

  3. 组合步骤:无

代码实现

// 输入:数据元素数组 arr,划分序列区间[low..high]
// 输出:将序列划分为两个子序列并返回枢轴元素的位置
function partition(arr, low, high) {
  let pivot = arr[low];
  while (low < high) {
    while (low < high && arr[high] >= pivot) {
      high--;
    }
    arr[low] = arr[high];
    while (low < high && arr[low] <= pivot) {
      low++;
    }
    arr[high] = arr[low];
  }
  arr[low] = pivot;
  return low;
}
function quickSort(arr, low, high) {
  if (low < high) {
    let pivot = partition(arr, low, high);
    quickSort(arr, low, pivot - 1);
    quickSort(arr, pivot + 1, high);
  }
  return arr;
}

堆排序

原理
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
堆排序的平均时间复杂度为 Ο(nlogn)。

步骤
图示

  1. 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

github如何向开源项目提交pr

  • fork 到自己的仓库
  • git clone 到本地
  • 上游建立连接
    git remote add upstream 开源项目地址
  • 创建开发分支 (非必须)
    git checkout -b dev
  • 修改提交代码
    git status git add . git commit -m git push origin branch
  • 同步代码三部曲
    git fetch upstream git rebase upstream/master git push origin master
  • 提交pr
    去自己github仓库对应fork的项目下new pull request

js基础知识讲解

题目->考点->知识再到题目

window.onload与DOMContentLoaded

  1. onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。
  2. DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash

变量类型和计算

类型

  • 值类型。栈内存(栈是为执行线程留出的内存空间,栈附属于线程,因此当线程结束时栈被回收)

  • 引用类型(对象、数组、函数)。堆内存(堆(heap)是为动态分配预留的内存空间,堆通常通过运行时在应用程序启动时被分配,当应用程序(进程)退出时被回收)。

  • typeof。只能识别值类型和函数,不能识别引用类型

    typeof undefined // undefined
    typeof 123 // number
    typeof 'rty' // string
    typeof true // boolean
    typeof null // object
    typeof {} // object
    typeof [] // object
    typeof console.log // function

计算-强制类型转换

  • 字符串拼接。
  • ==。
  • if。
  • 逻辑运算符。(tips:!!undefined)
  • 何时使用==和===?除了(obj.a == null ,jquery源码写法)其它均用===

内置对象

10个函数类型( String,Number,Boolean,Array,Function,Date,RegExp,Error,Object,Event )函数类型 有 __proto__prototype 属性。

2个对象类型(Math,JSON) 对象类型只有__proto__属性。

  • Object
  • Array
  • Boolean
  • Numner
  • String
  • Function
  • Date
  • RegExp
  • Error
  • Math内置对象

原型和原型链

构造函数

  • 名称首字母大写。
  • 默认return this
function Food(){
    this.cai = cai;
    // return this // 默认返回
}

构造函数-扩展

var a = {} 其实是var a = new Object()的语法糖
var a = [] 其实是var a = new Object()的语法糖
function Foo() {} 其实是var Foo = new Function()

基本概念

  • 原型:所有的函数才有的prototype属性,prototype属性值是一个普通的对象。

  • 原型链:JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

  • 对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

  • JavaScript是动态的没有class实现。在ES6中引入了class关键字,但是是语法糖。

  • 谈到继承时,JavaScript只有一种结构:对象。每个object都有一个私有属性(__proto__)指向它的原型对象(prototype)。该原型对象也有一个自己的(__proto__),层层往上直到一个对象的原型对象为null。根据定义,null没有原型。

原型规则(5条规则)和实例

  1. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由拓展属性(除了null)。
  2. 所有的引用类型(数组、对象、函数),都具有一个__proto__(隐式原型)属性,属性值是一个普通的对象。
  3. 所有的函数,都有一个prototype(原型)属性,属性值也是一个普通的对象。里边有一个constructor,指向函数自身。
  4. 所有的的引用类型(数组、函数、对象),__proto__(隐式原型)属性值指向它构造函数的prototype(原型)属性值。obj.__proto__ === Object.prototype // true
  5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即构造函数的prototype)中寻找。

循环对象自身的属性

for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        // 高级浏览器已经在for in中屏蔽了来自原型链的属性
    }
}

原型链

例子

// 构造函数
function Foo(name, age) {
    this.name = name;
    this.age = age;
}

Foo.prototype.alertName = function () {
    alert(this.name)
}
// 创建示例
var f = new Foo('kk', 90);
f.printName = function () {
	console.log(this.name)
}
// 测试
f.printName()
f.alertName()
f.toString() // 要去 f.__proto.__proto__找

图示

prototype

使用instanceof判断一个函数是否是一个变量的构造函数

  • f instanceof Foo的判断逻辑:
  1. f 的__proto__一层层往上,能否对应到Foo.prototype
  2. 再试着判断f instanceof Object

__proto__不是标准属性,并且该属性不是标准属性,不可以用在编程中,用于浏览器内部

例题

如何准确判断一个变量是数组

// arr instanceof Array

// 判断数组
function isArray(arr) {
    if (typeof Array.isArray === 'function') {
        return Array.isArray(arr);
    } else {
        return Object.prototype.toString.call(arr) === '[object Array]';
    }
}

写一个原型链继承的例子

// 形象例子
function Animal() {
    this.eat = function() {console.log('animal eat');}
}

function Cat(){
    this.mill = function() {console.log('cat mill');}
}
// 原型链继承
Cat.prototype = new Animal();

var bosimao = new Cat();
// 接近实战的例子
function Ele(id) {
    this.elem = document.getElementById(id)
}

Ele.prototype.html = function(val) {
    var elem = this.elem
    if (val) {
        elem.innerHTML = val
        return this //链式调用
        } else {
        return elem.innerHTML
    }
}

Ele.ptototype.on = function(type, handle) {
    var elem = this.elem
    elem.addEventListner(type, handle)
    return this // 链式调用
}

var divc = new Ele('id');
divc.on('click', function(e){
    console.log('i am click')
})

描述new一个对象的例子
1、创建一个空对象,并且this变量引用该对象,同时继承了该函数的原型(实例对象通过__proto__属性指向原型对象;obj.proto = Base.prototype;) 2、属性和方法被加入到 this 引用的对象中。

1. 创建一个新对象
2. this指向这个新对象
3. 执行代码对this赋值
4. 返回this
function Food(){
    this.cai = cai;
    // return this // 默认有这一行
}

详解

function myNew(_constructor, arg) {
  var obj = {};
  obj._proto_ = _constructor.prototype;
  //把该对象的原型指向构造函数的原型对象,就建立起原型了:obj->Animal.prototype->Object.prototype->null
  return _constructor.call(obj, arg);
}

zepto(或其它框架)源码中如何使用原型链

作用域和闭包

基础知识

  • 执行上下文。

  • this。要在执行时才能确认,定义时无法确认。

  • call,bind,apply

    // 指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象)
    fun.call(thisArg, arg1, arg2, ...)
    fun.apply(thisArg, [argsArray])
    // 创建一个新函数,在调用时设置this关键字为提供的值。
    function.bind(thisArg[, arg1[, arg2[, ...]]])

作用域

  • 没有块级作用域
  • 只有函数和全局作用域
  • 作用域链。由函数和全局作用域组成
  • 自由变量,到父作用域(定义的时候决定)寻找

闭包

不要尝试用几句话去说明闭包

使用场景

  • 函数作为返回值
  • 函数作为参数传递

例题

对变量提升的理解

  • 变量定义
  • 函数声明(注意函数表达式的区别)
  • 作用域链

说明this的几种不同使用场景

  • 作为构造函数执行
  • 作为对象属性执行
  • 作为普通函数执行
  • call apply bind

创建10个a标签,点击弹出相应的序号

  var i;
  for (i = 0; i < 10; i++) {
    (function (i) {
      var a = document.createElement('a');
      a.innerHTML = i + '<br>';
      a.addEventListener('', function (e) {
        e.preventDefault();
        alert(i); // 自由变量,要去父作用域获取值
      });
      document.body.appendChild(a);
    })(i)
  }

如何理解作用域

  • 自由变量
  • 作用域链,即自由变量的查找
  • 闭包的两个应用场景

实际开发中闭包的应用
实际开发中主要用于封装变量,收敛权限

异步和单线程

异步

异步与同步区别

  • 是否会阻塞代码执行
  • alert是同步,setTimeout是异步

前端使用异步的场景

  • 定时任务:setTimeout、setInterval
  • 网络请求:ajax请求,动态img加载
  • 事件绑定

异步和单线程

单线程

  • 一次只能执行一个任务
  • 不能同时干两件事情

Date Math Array Object

Date

Date.now() // 获取当前时间的毫秒数
var dt = new Date()
dt.getTime() // 获取毫秒数
dt.getFullYear() // 年
dt.getMonth()
dt.getDay()
dt.getHours()
dt.getMinutes()
dt.getSeconds()

Math

Math.random() // 获取随机数

Array

forEach()
every()
some()
sort()
map()
filter()

Object

for in

写一个能遍历对象和数组的函数

function forEach(obj, callback) {
  var key;
  if (obj instanceof Array) {
    obj.forEach(function (item, index) {
      callback(index, item);
    })
  } else {
    for (key in obj) {
      callback(key, obj[key]);
    }
  }
}

Web API

DOM本质

HTML(HyperText Markup Language)
结构:dom树

DOM节点操作

获取DOM节点

  getElementById() //元素
  getElementsByTagName() //集合
  getElementsByClassName() // 集合
  querySelectorAll()
  querySelector() 

proterty
js属性

  style,className,nodeName,nodeType

Attribute

  html属性

DOM结构操作

  createElement(),appendChild(),.parentElement,childNodes,removeChild()

题目

  • DOM是哪种基本的数据结构
  • DOM常用的操作API
  • DOM节点的attr和property有何区别

事件绑定

  function bindEvent(elem, type, selector, fn) {
    if(fn == null) {
      fn = selector;
      selector = null;
    }
    elem.addEventListener(type, function(e) {
      var target;
      if(selector) {
        // 代理
        target = e.target;
        if(target.matches(selector)) {
          fn.call(target, e)
        }
      } else {
        fn(e)
      }
    })
  }

事件�冒泡

  • DOM树形结构
  • 事件冒泡
  • 阻止冒泡
  • 冒泡的应用

代理:

  1. 代码简洁
  2. 浏览器压力小

XHR

XMLHttpRequest

var xhr = new XMLHttpRequest()
xhr.open('GET', 'url', false)
xhr.onreadystatechange = function () {
  if(xhr.readyState === 4) {
    if(xhr.status === 2) {
      xhr.response
    }
  }
}
xhr.send(null)
readyState 作用
0 未初始化,还未调用send()
1 已调用send(),还在发送请求
2 请求发送成功,已经接收到响应内容
3 解析响应内容
4 相应内容解析完成,可以在客户端用了

跨域

  • 浏览器有同源策略,不允许ajax访问其他域的接口
  • 跨域条件:协议,域名,端口

可以跨域的标签

  • img:用于打点统计,统计网站可能是其他域
  • link,script:可以使用cdn,cdn也能是其域
  • script可以用于jsonp
  1. JSONP实现原理。
  2. 服务端设置http header

存储
cookie

  • 本身是用于客户端与服务端通信。
  • 但是它本有本地存储的功能,于是就被借用

缺点:

  1. 存储量太小,只有4kb
  2. 所有的http请求都带着,会影响获取资源的效率
  3. api简单,document.cookie,需要封装

localStorage,sessionStorage

  • HTML5为存储而设计的。最大容量5M
  • api简单易用

cookie,sessionStorage,localStorage区别

  • 容量
  • api易用性
  • ajax是否携带

关于开发环境

git

代码管理

  • 将当前服务器的代码全部打包并记录版本号,备份
  • 将测试完成的代码提交到git版本库的master分支
    上线回滚
  • 将当前服务器的代码打包并记录版本号,备份
  • 将备份的上一个版本号解压,覆盖到线上服务器,并生成新的版本号

基本命令:


模块化

AMD

  • 对于依赖的模块,amd是提前执行(提前读取并加载),
  • cmd是延迟执行,按需加载(提前读取不加载)。CMD 推崇 as lazy as possible
    webpack
  var path = require('path')
  var webpack = require('path')
  module.exports = {
    context: path.resolve(__dirname, './src'),
    entry: {
      app: './app.js'
    },
    output: {
      path: path.resolve(__dirname, './dist')
      filename: 'bundle.js'
    }
  }

运行环境

页面加载过程

性能优化

  • 多使用内存,缓存或者其它方法
  • 减少CPU计算,减少网络

从哪里入手:
加载资源优化

  1. 静态资源的压缩合并

  2. 静态资源的缓存

  3. 使用cdn让资源加载更快

  • cdn原理:CDN做了两件事,一是让用户访问最近的节点,二是从缓存或者源站获取资源CDN的工作原理:通过dns服务器来实现优质节点的选择,通过缓存来减少源站的压力
  1. 使用SRR后端渲染,数据直接输出到HTMl

渲染优化

  • css放前面,JS放后面
  • 懒加载(图片懒加载)
  • 减少DOM查询,对DOM查询做缓存
  • 减少DOM操作,多个操作尽量合并在一起执行

安全性

XSS跨站请求攻击
插入一段script
XSRF跨站请求伪造

浅拷贝和深拷贝

  1. 最大的区别就是基本类型和对象类型:基本类型是传值,对象类型是传引用
  2. 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

深拷贝的实现

lodash

ES6,针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用

  var obj = { a: 0 , b: { c: 0}}; 
  // ES6
  Object.assign({}, obj)

JSON

  var obj = { a: 0 , b: { c: 0}}; 
  JSON.parse(JSON.stringify(obj))

递归

(function ($) {
    'use strict';

    var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');

	function type () {
	   return Object.prototype.toString.call(this).slice(8, -1);
	}

	for (var i = types.length; i--;) {
	    $['is' + types[i]] = (function (self) {
	        return function (elem) {
	           return type.call(elem) === self;
	        };
	    })(types[i]);
	}

    return $;
})(window.$ || (window.$ = {}));//类型判断

function copy (obj,deep) { 
    if ($.isFunction(obj)) {
    	return new Function("return " + obj.toString())();
    } else if (obj === null || (typeof obj !== "object")) { 
        return obj; 
    } else {
        var name, target = $.isArray(obj) ? [] : {}, value; 

        for (name in obj) { 
            value = obj[name]; 

            if (value === obj) {
            	continue;
            }

            if (deep) {
                if ($.isArray(value) || $.isObject(value)) {
                    target[name] = copy(value,deep);
                } else if ($.isFunction(value)) {
                    target[name] = new Function("return " + value.toString())();
                } else {
            	    target[name] = value;
                } 
            } else {
            	target[name] = value;
            } 
        } 
        return target;
    }         
}

跨域

跨域是发生在浏览器端

jsonp的原理和简单实现

  1. 动态创建script(script的src实现跨域)标签
  2. 通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信。
  3. 只能支持get方法
  function jsonp (req) {
    var script = document.createElement('script')
    var url = req.url + '?callback=' + req.callback.name
    script.src = url
    document.getElementsByTagName('head')[0].appendChild(script)
  }

for of,for in, Object.keys()

for..of和for...in的区别, 使用时注意用Object.hasOwnProperty判断是否真的包含元素

var a = [4,1,6]
for(var i of a) {console.log(i)} // 4,1,6
for(var i in a) {console.log(i)} // 0,1,2

Object.keys与for...of的区别:

  1. Object.keys不会遍历到__proto__

html相关

doctype(文档类型的)的作用

  1. 对文档进行有效性验证。它告诉用户代理和验证器这个文档是按照什么DTD(文档类型定义Document Type Definition)写的。这是个被动的过程,每当页面加载时浏览器并不会主动下载DTD并校验合法性,只有当手动校验时才启动。
  2. 决定浏览器的呈现模式。通知浏览器用哪种算法解析文档。如果没有写,则浏览器根据自身的规则进行渲染,可能会严重影响布局。浏览器有三种方式解析html文档
    1. 非怪异(标准)模式
    2. 怪异模式
    3. 部分怪异模式

浏览器标准模式与怪异模式的区别

在“标准模式”(Standards Mode) 页面按照 HTML 与 CSS 的定义渲染,而在“怪异模式”(Quirks Mode)就是浏览器为了兼容很早之前针对旧版本浏览器设计、并未严格遵循 W3C 标准的网页而产生的一种页面渲染模式。
浏览器基于页面中文件类型描述的存在以决定采用哪种渲染模式;如果存在一个完整的DOCTYPE则浏览器将会采用标准模式,而如果它缺失则浏览器将会采用怪异模式。

浏览器内核

IE:trident内核

Firefox:gecko(壁虎)内核

safari:webkit内核,WebKit的优势在于高效稳定,兼容性好,且源码结构清晰,易于维护。

Chrome:Blink(基于webkit,google与opera software共同开发)

opera:以前是presto内核,现在改用google chrome的Blink内核

标签语义化

描述内容的含义

  1. 去掉或者丢失样式的时候能够让页面呈现出清晰的结构
  2. 有利于SEO;
  3. 便于团队开发和维护,语义化更具可读性。遵循 W3C 标准的团队都遵循这个标准,可以减少差异化

可替换元素(replaced element)和 非替换元素(non-replaced element)

可替换元素(replaced element)
img, object, iframe, video 等,展现效果不是由 CSS 来控制的。这些元素是一种外部对象,它们外观的渲染,是独立于 CSS 的。(浏览器根据元素的标签和属性,来决定元素的具体显示内容。)
非替换元素
大多数标签属于非替换元素,这部分标签把标签里的内容直接告诉浏览器显示出来

meta标签

  1. 如何在不使用JS的情况下刷新页面()
  2. 设置页面缓存()
  3. 移动端设置

行内元素

  1. 只有设置左右的margin和padding。
  2. 不能设置高宽,它的高度取决于内部文字的行高。宽度取决于内部文字的多少
    html 里的元素分为替换元素(replaced element)和非替换元素(non-replaced element)。

讨论margin-top和margin-bottom对行内元素是否起作用,则要对行内替换元素和行内非替换元素分别讨论。

行内替换元素:(img)有影响
行内非替换元素:无影响

数据结构

特点
栈是一种后进先出(LIFO)的数据结构
应用

  1. 网络浏览器将用户访问的地址组织为一个栈。
  2. 当今主流的编辑器大都支持编辑操作历史功能 。
    方法
    pop(),push()

队列

特点
栈是一种先进先出(FIFO)的数据结构
应用

  1. 银行排队服务。
    方法
    shift(),unshift()

链表

解决数组长度固定的不足

单链表

链表:按线性次序排列的一组数据节点。单链表的末节点特征是next为null

双向链表

除了有next引用,还有prev引用

一种非线性数据结构。作为一种抽象数据类型,树可以用来对一组元素进行层次化的组织。
二叉树
每个节点均不超过2度的有序树称为二叉树。
满二叉树
若二叉树 T 中所有叶子的深度完全相同,则称之为满二叉树
完全二叉树
若在一棵满二叉树中,从最右侧起将相邻的若干匹叶子节点摘除掉,则得到的二叉树称作完全二叉树

遍历

  • 前序遍历:根结点 ---> 左子树 ---> 右子树

  • 中序遍历:左子树---> 根结点 ---> 右子树

  • 后序遍历:左子树 ---> 右子树 ---> 根结点

  • 层次遍历:按树的深度进行遍历

  • 深度优先遍历:前序遍历、中序遍历、后序遍历

  • 广度优先遍历:广度优先遍历会先访问离根节点最近的节点

堆Heap

小顶堆和大顶堆
满足一下两条性质的二叉树:

  1. H中各元素的连接关系应该符合二叉树的基本结构
  2. 并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点

JavaScript红宝书笔记

JavaScript高级程序设计

第一二章简介

<script>元素定义了6个属性

async: 可选。表示应该立即下载的脚本,但不应妨碍页面中的其他操作。只对外部脚本文件有效
charset: 可选。表示通过src属性指定
defer: 可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效(更常用的做法是把脚本放到页面底部)
src: 可选。表示包含要执行代码的外部文件
type:可选。表示编写代码使用的脚本语言的内同类型

第三章基本概念

数据类型

  1. 基本数据类型:Undefined、Null、Boolean、String、Number、Symbol(ES6引入了一种新的原始数据类型Symbol, 表示独一无二的值。用于解决对象属性名都是字符串容易造成属性名冲突)
  2. 引用类型:Object
  3. 0.1+0.2 =0.3000000004(原因:浮点数的最高精度是17位,会产生舍入误差)
  4. NaN(not a number)。(NaN == NaN,false)
  5. 数值转换:Number()、parseInt()、parseFloat()

Object

Object的每个实例都有下列属性和方法:

  1. Constructor:构造函数
  2. hasOwnProperty(PropertyName):用于检查给定属性是否在当前对象实例中
  3. isPropertyOf(Object): 用于检查传入的对象是否是另一个对象的原型
  4. propertyIsEnumerable(propertyName):用于检查给定的属性是否能用for-in进行枚举
  5. toLocalString()
  6. toString()
  7. valueOf()

标识符规则

  1. 第一个字符必须是一个字母、下划线(_)、或一个美元符号(💲)
  2. 其它字符可以是字母、下划线、美元符号或者是数字

操作符

  1. 前置递增/递减,后置递增/递减
    后置型递增/递减操作与前置递增/递减,最大的区别:后置是在它们的语句被求值之后才执行
  2. ==与===,==会先进行数据类型转换

理解参数arguments

  1. arguments参数是一个类数组对象(有length,argument[0],但是没有数组push()等相应的等方法)
  2. 参数传递都是按值传递

第四章 变量、作用域和内存问题

基本类型和引用类型

  1. 最关键的点:基本类型保存在栈内存中,引用类型是存储在堆内存中
  2. 检测类型:typeof/instanceof

执行环境和作用域

执行环境:定义了变量或函数有权访问的其它数据

理解:

  1. 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而在函数执行后,栈(后进先出,LIFO)将其弹出
  2. 代码在环境栈中执行时,会创建变量的一个作用域链。作用域链的用途:保证对执行环境有权访问的所有变量和函数的有序访问
  3. 标识符解析:是沿着作用域链一级一级地搜索标识符的过程。从内往外搜索,从局部到全局

没有块级作用域

if (true) {
    var color = 'blue';
}
console.log(color); // blue

垃圾收集

JavaScript具有自动垃圾收集机制,原理:找到那些不再使用的变量,然后释放内存

策略

  1. 标记清除(最常用)。
  2. 引用计数

第五章 引用类型

Array

检测数组

function isArray (arr) {
    if (typeof Array.isArray === 'function') {
        return Array.isArray(arr);
    } else {
        return Object.prototype.toString.call(arr) === '[Object Array]';
    }
}

转换方法

方法 用法
toLocalString() 返回字符串形式,每次都调用toLocalString
toString() 返回字符串形式
valueOf() 返回字符串形式

栈和队列方法

方法类型 方法
栈方法(LIFO) push()、pop()
队列方法(FIFO) unshift()、shift()

重排序

  1. reverse()反序

  2. sort()升序,调用每个数组项的toString()方法,比较得到的字符串

    // 解决 10,5这样的大小问题
    arr.sort(
    	function compoare(value1, value2) {
            return value2 - value1;
    	}
    )

操作方法

方法 语法 是否改变原数组
concat() arrayObject.concat(arrayX,arrayX,......,arrayX) 否,返回新数组
slice() arrayObject.slice(start,end) 否,返回新数组
splice() array.splice(index,howmany,item1,.....,itemX)

位置方法

方法 语法
array.indexOf(item, start) 从数组的开头开始查找,不支持ie8
lastIndexOf(item, start) 从数组的末尾开始查找

迭代方法(ie9)

方法 语法 返回值
every() arr.every(callback[, thisArg]) true,false
filter() arr.filter(callback(element[, index[, array]])[, thisArg]) 返回true的项
forEach() array.forEach(callback(currentValue, index, array){ //do something }, this)
map() var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])。方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。 新数组
some() arr.some(callback[, thisArg]) true,false

缩小方法

方法 语法
reduce() arr.reduce(function(prev, cur, index, array){})
reduceRight() arr.reduceRight(function(prev, cur, index, array){})

Date

Date.parse()Date.UTC()
不同宿主在如何解析日期字符串上仍存在许多差异

RegExp

Function

没有重载
后面的会覆盖前面的函数

  1. 函数表达式。
  2. 函数声明。代码开始执行之前,解析器通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部
    函数内部属性
    arguments,this
    函数属性和方法
    属性
  3. length,表示函数希望接受的命名参数的个数
  4. prototype
    方法
  5. apply()。在特定的作用域中调用函数
  6. call()。在特定的作用域中调用函数。类数组转化:Array.prototype.slice.call(arguments)

基本包装类型

Boolean,Number,String

单体内置对象

不依赖宿主环境的对象,这些对象在ECMAScript执行之前就已经存在(不必显示地实例化)
GlobalMath

面向对象程序设计

属性

数据属性:

  • configurable: 能否通过delete删除属性从而重新定义属性
  • enumerable: 能否通过for-in循环返回属性
  • writable: 表示能否修改属性的值
  • value:包含这个属性的数据值
    访问器属性
  • configurable
  • enumerable
  • get
  • set

创建对象

模式 解释 优点 缺点
工厂模式 这种模式抽象了创建具体对象的过程 解决了创建多个相似对象的问题 没有解决对象识别的问题
构造函数模式 使用new操作符创建对象 可以将它的实例标记为特定的类型 每个方法都要在每个实例上创建一遍
原型模式 用prototype属性,创建实例的共享方法和属性 共享方法和属性 每个实例一般要有属于自己的全部属性
组合构造函数模式和原型模式 构造函数和原型模式一起使用 两种模式的优点
动态原型模式 根据检验方法是否有效来决定是否需要初始化原型 避免大量相同的添加操作
寄生构造函数模式 创建一个函数,返回新创建的对象 解决特殊需求,比如说不改prototype,但是要新增额外方法
稳妥构造函数模式 适合在安全的环境中,防止数据被其他应用程序改变时使用 安全性

继承

实现继承,主要用原型链(prototype)实现
确定原型和实例的关系
instanceof,isPrototypeOf

BOM

window对象

描述 属性
窗口位置 window.screenLeft/window.screenX,window.screenTop/window.screenY
窗口大小 window.innerWidth/innerHeight/outerHeight/outerWidth
导航和打开窗口 window.open()

其它对象

对象 属性
location hash,host,host,name,href,pathname,port,protocol,search
navigator
screen
history (方法)go(),back().forward(),length

客户端检测

DOM

Node类型

每个节点都有一个nodeType属性用于表明节点的类型
nodeName和nodeValue属性
操作节点
appendChild(),insertBefore(),removeChild()

Document类型

文档信息
document.title,document.URL,document.domain,document.referrer
查找元素
getElementById(),getElementsByTagName(),getElementsByName(),getElementByClassName()

Dom扩展

选择符API

  1. querySelector()。返回第一个元素
  2. querySelectorAll()。返回所有
  3. scrollIntoView()

事件流

事件冒泡

IE的事件流叫做事件冒泡。即事件开始时由最具体的元素(文档中嵌套深的节点)接受,后逐级向上传播到不具体的节点。

let eventUtil = {
    addHandler: (element, type, handler) => {
        // dom2级事件处理程序(ie9)
        if (element.addEventListener) {
            // false表示在冒泡阶段调用事件处理程序
            element.addEventListener(type, handler, false);
        // dom0级事件处理程序(ie)
        } else if (element.attachEvent) {
            element.attachEvent('on' + type, handler);
        // 其它事件
        } else {
            element['on' + type] = handler; 
        }
    },
    removeHandler: (element, type, handler) => {
        // dom2级事件处理程序(ie9)
        if (element.addEventListener) {
            // false表示在冒泡阶段调用事件处理程序
            element.removeEventListener(type, handler, false);
        // dom0级事件处理程序(ie)
        } else if (element.attachEvent) {
            // ie事件处理程序
            element.detachEvent('on' + type, handler);
        // 其它事件
        } else {
            element['on' + type] = null; 
        }
    },
    stopPropagation: e => {
        e.stopPropagation ? e.stopPropagation() : e.cancelBubble = false;
    },
    preventDefault: e => {
        e.preventDefault ? e.preventDefault() : e.returnValue = false;
    },
    getEvent: e => {
        return e || window.event;
    },
    getTarget: e => {
        return e.target || e.srcElement;
    }
};

事件捕获

与事件冒泡相反

手写冒泡

<ul class="parent">
    <li class="son1">son1</li>
    <li class="son2">son2</li>
</ul>
            
let parent = document.querySelector('.parent');
eventUtil.addHandler(parent, 'click', e => {
    console.log(e.target.innerText)
})

css相关

盒模型

w3c标准盒模型
属性width,height只包含内容content,不包含border和padding。

ie盒模型
width = content + padding * 2 + border * 2

box-sizing
控制元素盒模型的解析方式

  1. content-box: 默认值
  2. border-box:告诉浏览器去理解你设置的边框和内边距的值是包含在width内的

visibility: hidden 与 display:none

1、display:none 元素不再占用空间。
2、visibility: hidden 使元素在网页上不可见,但仍占用空间。 

BFC(Block Formatting Contexts)块级格式化上下文

块格式化上下文(block formatting context) 是页面上的一个独立的渲染区域,容器里面的子元素不会在布局上影响到外面的元素。它是决定块盒子的布局及浮动元素相互影响的一个因素。

触发条件

  1. position 属性不为 static 或者 relative
  2. float 属性不为 none
  3. overflow 不为 visible
  4. display(display为inline-block、table-cell)

特性

  1. 内部的 Box 会在垂直方向,从顶部开始一个接一个地放置
  2. Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生叠加
  3. BFC 的区域不会与 float box 叠加
  4. BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然
  5. 计算 BFC 的高度时,浮动元素也参与计算

用处

  1. 清除内部浮动:对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把把父元素变成一个BFC就行了。常用的办法是给父元素设置overflow:hidden。
  2. 上下margin重合问题,可以通过触发BFC来解决

清除浮动

  1. 使用空标签设置 clear: both;(clear 有哪些值可以设置?应用在什么元素上?缺点:增加无意义标签)
  2. 为父级元素设置 overflow: hidden; (利用 BFC 的原理,除了设置 hidden,还能设置其他的值吗?)
  3. 使用伪元素,为要清除浮动的元素添加 .clearfix 类(推荐,其原理可查看 http://nicolasgallagher.com/micro-clearfix-hack/)

清除浮动元素的方法和各自的优缺点

原理:清除浮动,实际上是清除父元素的高度塌陷。因为子元素脱离了父元素的文档流,所以,父元素失去了高度,导致了塌陷。要解决这个问题,就是让父元素具有高度。

浮动元素的特性: 在正常布局中位于该浮动元素之下的内容,此时会围绕着浮动元素,填满其右侧的空间。浮动到右侧的元素,其他内容将从左侧环绕它,浮动元素影响的不仅是自己,它会影响周围的元素对其进行环绕。
float仍会占据其位置,position:absolute不占用页面空间 会有重叠问题

position

  1. absolute :生成绝对定位的元素, 相对于最近一级的 定位不是 static 的父元素来进行定位。
  2. fixed (老IE不支持)生成绝对定位的元素,通常相对于浏览器窗口或 frame 进行定位。
  3. relative 生成相对定位的元素,相对于其在普通流中的位置进行定位。
  4. static 默认值。没有定位,元素出现在正常的流中
  5. sticky 生成粘性定位的元素,容器的位置根据正常文档流计算得出(饿了么的h5搜索框就是用了这个特性)

动画

用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,以及新的requestAnimationFrame

<div id="demo" style="position:absolute; width:100px; height:100px; background:#ccc; left:0; top:0;"></div>

<script>
  var demo = document.getElementById('demo');
  function rander(){
    demo.style.left = parseInt(demo.style.left) + 1 + 'px'; //每一帧向右移动1px
  }
  requestAnimationFrame(function(){
    rander();
    //当超过300px后才停止
    if(parseInt(demo.style.left)<=300) requestAnimationFrame(arguments.callee);
  });
</script>

css3使用
@Keyframes 结合animation
transition:property duration timing-function delay

布局

两列布局

  1. position absolute;margin-left;
  2. float+margin
  3. 负margin值

三列布局

  1. 与两列布局类似,左右两边设置position:absolute;和top:0;left:0;right:0; 中间设置margin-left和margin-right即可。
  2. 左右采用float,中间设置margin:0 300px
  3. 负边距

弹性布局flex
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)

CSS Grid

各个单位的区别(px, em, rem, 百分比, vw, vh, vmax, vmin)

px:绝对单位,页面按精确像素展示

em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算(浏览器默认字体是16px),整个页面内1em不是一个固定的值。

rem:相对单位,可理解为”root em”, 相对根节点html的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持。

vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%。

vh:viewpoint height,视窗高度,1vh等于视窗高度的1%。

vmin:vw和vh中较小的那个。

vmax:vw和vh中较大的那个。

水平垂直居中的方法

行内布局

line-height + text-align
vertical-align + text-align

块布局

position absolute + margin auto
position absolute + negative margin
position absolute + translate(-50%, -50%)

父容器子容器不确定宽高的块级元素,做上下居中

1.flex

#wrap{
	display:flex;
	justify-content:center;
	align-items:center;
}

2.tabel

.parent {
   text-align: center;//水平居中
   display: table-cell;
   vertical-align: middle;//垂直居中
}
.child {
    display: inline-block;//防止块级元素宽度独占一行,内联元素可不设置
}

3.absolute+transform 水平垂直居中

<div class="parent">
  <div class="child">Demo</div>
</div>

<style>
  .parent {
    position: relative;
  }
  .child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
</style>

4.webkit-box

//对父级元素设置
position: relative;
display: -webkit-box;
-webkit-box-align: center;
-webkit-box-pack: center;

Jquery源码

链式调用

return this

关于===与==

if (Object.a == null) {} // 除了这个其它均用===

HTTP

HTTP-MDN

HTTP 概述

超文本传输协议(Hypertext Transfer Protocol),是应用层协议。

UDP(User Data Protocol)

面向报文
UDP 是一个面向报文(报文可以理解为一段段的数据)的协议。意思就是 UDP 只是报文的搬运工,不会对报文进行任何拆分和拼接操作。

具体来说:

在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了

在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作

不可靠性

  1. UDP 是无连接的,也就是说通信不需要建立和断开连接。
  2. UDP 也是不可靠的。协议收到什么数据就传递什么数据,并且也不会备份数据,对方能不能收到是不关心的
  3. UDP 没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比4如电话会议)就需要使用 UDP 而不是 TCP

高效性
因为 UDP 没有 TCP 那么复杂,需要保证数据不丢失且有序到达。所以 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的。

传输方式
UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能

TCP

TCP的头部比UDP复杂很多,对于 TCP(Transmission Control Protocol) 头部来说,以下几个字段是很重要的

头部 详情
Sequence number 这个序号保证了 TCP 传输的报文都是有序的,对端可以通过序号顺序的拼接报文
Acknowledgement Number 这个序号表示数据接收端期望接收的下一个字节的编号是多少,同时也表示上一个序号的数据已经收到
Window Size 窗口大小,表示还能接收多少字节的数据,用于流量控制标识符
URG=1 该字段为一表示本数据报的数据部分包含紧急信息,是一个高优先级数据报文,此时紧急指针有效。紧急数据一定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。
ACK=1 该字段为一表示确认号字段有效。此外,TCP 还规定在连接建立后传送的所有报文段都必须把 ACK 置为一。
PSH=1 该字段为一表示接收端应该立即将数据 push 给应用层,而不是等到缓冲区满后再提交。
RST=1 该字段为一表示当前 TCP 连接出现严重问题,可能需要重新建立 TCP 连接,也可以用于拒绝非法的报文段和拒绝连接请求。
SYN=1 当SYN=1,ACK=0时,表示当前报文段是一个连接请求报文。当SYN=1,ACK=1时,表示当前报文段是一个同意建立连接的应答报文。
FIN=1 该字段为一表示此报文段是一个释放连接的请求报文。

TCP/IP协议族各层的作用

  • 应用层:决定了向用户提供应用服务时通信的活动(发送HTTP报文)。FTP/DNS/HTTP
  • 传输层:传输层对上层应用,网络连接中的计算机之间的数据传输。有TCP(transmission Control Protocol,传输控制协议)和 UDP(User Data Protocol,用户数据报协议)
  • 网络层(网络互联层):网络层用来处理在网络上流动的数据包。IP
  • 链路层(数据链路层,网络接口层):用来处理连接网络的硬件部分。操作系统、设备驱动、NIC(网络设配器,网卡),光纤

HTTP的基本性质

  • http是简单的。HTTP报文能够被人读懂,还允许简单测试,降低了门槛,对新人很友好。
  • http是可扩展的。在HTTP/1.0出现的HTTP Headers让协议扩展变的很容易。
  • HTTP是无状态的,有会话的。在同一个连接中,两次成功的请求之间是没有关联的,这导致用户无法进行连续的交互(如购物车问题)。
    使用HTTP的头部扩展HTTP Cookies就可以解决这个问题。注意,HTTP本质是无状态的,使用Cookies可以创建有状态的会话。

HTTP和连接

连接(一般指TCP连接)是由传输层控制的

HTTP/1.0

HTTP/1.0为每一个响应/请求都打开一个TCP连接,缺点: 打开一个TCP连接需要多次往返消息传递(三次握手),速度慢。

HTTP/1.1

HTTP/1.1引入了持久连接的概念,底层的TCP连接可以通过Connection(keep-alive)头部来被部分控制

为了更好的适合HTTP,设计一种更好传输协议的进程一直在进行。

Google就研发了一种以UDP为基础,能提供更可靠更高效的传输协议QUIC。

HTTP长连接和短连接这个说法是有有问题的。应该是TCP(传输层协议)连接。

  1. http是应用层协议。应该是http请求/http响应
  2. http1.0默认的是短连接,http1.1后默认的是长连接

TCP长连接
tcp建立一次连接后不关闭(具体的时间长短,是可以在header当中进行设置的,也就是所谓的超时时间)。web中的js,css,html都是可以缓存的

TCP短连接
tcp每次建立连接后,请求完成,连接关闭。下次传输连接需要再次建立。

优点与缺点

  1. 长连接可以减少tcp建立和关闭的操作,节约时间和带宽。
  2. 短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段

HTTP能控制什么

  • 缓存
  • 开放同源策略
  • 认证
  • 代理和隧道
  • 会话

HTTP报文

请求报文

GET/test/xx.txt  HTTP/ 1.1  // 起始行
Accept: text/* // 首部
Host: d.w.org // 首部

响应报文

    HTTP/1.1 200 OK // 起始行
    Content-type: text/plain // 首部
    Content-length: 15 // 首部
    hi ni hao // 主体

HTTP首部

通用字段 作用
Cache-Control 控制缓存的行为
Connection 浏览器想要优先使用的连接类型,比如 keep-alive
Date 创建报文时间
Pragma 报文指令
Via
Transfer-Encoding 传输编码方式
Upgrade 要求客户端升级协议
Warning 在内容中可能存在错误
请求字段 作用
Accept 能正确接收的媒体类型
Accept-Charset 能正确接收的字符集
Accept-Encoding 能正确接收的编码格式列表
Accept-Language 能正确接收的语言列表
Expect 期待服务端的指定行为
From 请求方邮箱地址
Host 服务器的域名
If-Match 两端资源标记比较
If-Modified-Since 本地资源未修改返回 304(比较时间)
If-None-Match 本地资源未修改返回 304(比较标记)
User-Agent 客户端信息
Max-Forwards 限制可被代理及网关转发的次数
Proxy-Authorization 向代理服务器发送验证信息
Range 请求某个内容的一部分
Referer 表示浏览器所访问的前一个页面
TE 传输编码方式
响应字段 作用
Accept-Ranges 是否支持某些种类的范围
Age 资源在代理缓存中存在的时间
ETag 资源标识
Location 客户端重定向到某个 URL
Proxy-Authenticate 向代理服务器发送验证信息
Server 服务器名字
WWW-Authenticate 获取资源需要的验证信息
实体字段 作用
Allow 资源的正确请求方式
Content-Encoding 内容的编码格式
Content-Language 内容使用的语言
Content-Length request body 长度
Content-Location 返回数据的备用地址
Content-MD5 Base64加密格式的内容 MD5检验值
Content-Range 内容的位置范围
Content-Type 内容的媒体类型
Expires 内容的过期时间
Last_modified 内容的最后修改时间

HTTP状态码

类别

状态码 类别 原因
1XX Informational(信息性状态码) 接收的请求正在处理
2XX Success 请求正常处理完毕
3XX Redirectin 需要进行附加操以完成请求
4XX Client Error(客户端错误) 服务器无法处理请求
5XX Server Error 服务器请求出错

状态码详情

  • 100

表示目前为止一切正常, 客户端应该继续请求, 如果已完成请求则忽略

  • 200 表明请求已经成功. 默认情况下成功的请求将会被缓存。不同请求方式对于请求成功的意义如下:

    • GET: 已经取得资源,并将资源添加到响应中的消息体.
    • HEAD: 作为消息体的头部信息.
    • POST: 消息体中描述此次请求的结果.
    • TRACE: 响应的消息体包含服务器接收到的请求信息.
  • 201 是一个代表成功的应答状态码,表示请求已经被成功处理,并且创建了新的资源。新的资源在应答返回之前已经被创建。同时新增的资源会在应答消息体中返回,其地址或者是原始请求的路径,或者是 Location 首部的值。

  • 300 是一个用来表示重定向的响应状态码,表示该请求拥有多种可能的响应。用户代理或者用户自身应该从中选择一个。

  • 301 永久重定向 说明请求的资源已经被移动到了由 Location 头部指定的url上,是固定的不会再改变。搜索引擎会根据该响应修正。

  • 302 重定向状态码表明请求的资源被暂时的移动到了由Location 头部指定的 URL 上。浏览器会重定向到这个URL, 但是搜索引擎不会对该资源的链接进行更新

  • 303 重定向状态码,通常作为 PUT 或 POST 操作的返回结果,它表示重定向链接指向的不是新上传的资源,而是另外一个页面,比如消息确认页面或上传进度页面。而请求重定向页面的方法要总是使用 GET。

  • 304 浏览器已缓存,不用再请求服务器资源

  • 307 临时重定向)是表示重定向的响应状态码,说明请求的资源暂时地被移动到 Location 首部所指向的 URL 上。

状态码 307 与 302 之间的唯一区别在于,当发送重定向请求的时候,307 状态码可以确保请求方法和消息主体不会发生变化。当响应状态码为 302 的时候,一些旧有的用户代理会错误地将请求方法转换为 GET:使用非 GET 请求方法而返回 302 状态码,Web 应用的运行状况是不可预测的;而返回 307 状态码时则是可预测的。对于 GET 请求来说,两种情况没有区别。

  • 308 永久重定向)是表示重定向的响应状态码,说明请求的资源已经被永久的移动到了由 Location 首部指定的 URL 上。

在重定向过程中,请求方法和消息主体不会发生改变,然而在返回 301 状态码的情况下,请求方法有时候会被客户端错误地修改为 GET 方法。

  • 401 代表客户端错误,指的是由于缺乏目标资源要求的身份验证凭证,发送的请求未得到满足。
  • 403 代表客户端错误,指的是服务器端有能力处理该请求,但是拒绝授权访问。
  • 404 代表客户端错误,指的是服务器端无法找到所请求的资源。由于它的频繁出现,这个状态码估计是最著名的一个了。返回该响应的链接通常称为坏链(broken link)或死链(dead link),它们会导向链接出错处理(link rot)页面。
  • 405 表明服务器禁止了使用当前 HTTP 方法的请求。需要注意的是,GET 与 HEAD 两个方法不得被禁止,当然也不得返回状态码 405。
  • 500 是表示服务器端错误的响应状态码,意味着所请求的服务器遇到意外的情况并阻止其执行请求。
  • 503 是一种HTTP协议的服务器端错误状态代码,它表示服务器尚未处于可以接受请求的状态。常造成这种情况的原因是由于服务器停机维护或者已超载。

Https

HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS (传输层安全协议,Transport Layer Security)协议进行了加密。
TLS
TLS协议位于传输层之上,应用层之下。首次进行TLS协议传输时需要两个RTT(同一个封包来回时间(Round-Trip Time))。接下来可以通过Session Resumption(会话复用) 减少到一个 RTT。

在 TLS 中使用了两种加密技术,分别为:对称加密和非对称加密

  • 对称加密。对称加密就是两边拥有相同的秘钥,两边都知道如何将密文加密解密。

  • 非对称加密。有公钥私钥之分,公钥所有人都可以知道,可以将数据用公钥加密,但是将数据解密必须使用私钥解密,私钥只有分发公钥的一方才知道。

TLS 握手过程如下图
TCP连接建立(3次握手)

在 TLS 握手阶段,两端使用非对称加密的方式来通信,但是因为非对称加密损耗的性能比对称加密大,所以在正式传输数据时,两端使用对称加密的方式通信。

  • B服务端先创建TCB(传输控制模块: 存储了连接中的重要信息,如:tcp连接表,接收和发送的信号),准备接受客户端的请求,然后进程属于LISTEN状态,等待客户的连接。
  • A客户端先创建TCB,然后向B发送请求报文。1
  • B服务端接受到请求报文,如同意建立连接则确认。2
  • A客户端接收到B的确认后,还要再次向B服务端发送确认。3

tcp连接建立

TCP连接释放(4次挥手)
A、B端都处于连接已建立的状态。

  • A客户端先向TCP发出连接释放报文段,并停止发送数据,主动关闭TCP连接。1
  • B服务端接收到释放信号,发出确认。然后进入关闭等待状态。2
  • A客户端接收到B的确认信号后,进入终止等待。等待B发出的连接释放报文段。3
  • B发出连接释放报文段,A需要再次确认。4

tcp连接释放

HTTP2.0

HTTP 2.0 相比于 HTTP 1.X,可以说是大幅度提高了 web 的性能。

在 HTTP 1.X 中,为了性能考虑,我们会引入雪碧图、将小图内联、使用多个域名等等的方式。这一切都是因为浏览器限制了同一个域名下的请求数量,当页面中需要请求很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。

二进制传输

HTTP/2是二进制协议而不是文本协议。
HTTP 2.0 中所有加强性能的核心点在于此。在之前的 HTTP 版本中,我们是通过文本的方式传输数据。在 HTTP 2.0 中引入了新的编码机制,所有传输的数据都会被分割,并采用二进制格式编码。

多路复用

在HTTP2.0中有两个非常重要的概念:帧(frame)和流(stream),帧代表着最小的数据单位,流由帧组成。
多路复用
就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

Header压缩

在 HTTP 1.X 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。

在 HTTP 2.0 中,使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值

服务端Push

在 HTTP 2.0 中,服务端可以在客户端某个请求后,主动推送其他资源。

可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用 prefetch

QUIC(快速UDP网络连接)

这是一个谷歌出品的基于 UDP 实现的同为传输层的协议,目标很远大,希望替代 TCP 协议。

  • 该协议支持多路复用,虽然 HTTP 2.0 也支持多路复用,但是下层仍是 TCP,因为 TCP 的重传机制,只要一个包丢失就得判断丢失包并且重传,导致发生队头阻塞的问题,但是 UDP 没有这个机制
  • 实现了自己的加密协议,通过类似 TCP 的 TFO 机制可以实现 0-RTT,当然 TLS 1.3 已经实现了 0-RTT 了
  • 支持重传和纠错机制(向前恢复),在只丢失一个包的情况下不需要重传,使用纠错机制恢复丢失的包
    • 纠错机制:通过异或的方式,算出发出去的数据的异或值并单独发出一个包,服务端在发现有一个包丢失的情况下,通过其他数据包和异或值包算出丢失包
    • 在丢失两个包或以上的情况就使用重传机制,因为算不出来

计算机通识

DNS

DNS 的作用就是通过域名查询到具体的 IP。

因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。

在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问 www.google.com 时,会进行一下操作:

  1. 操作系统会首先在本地缓存中查询
  2. 没有的话会去系统配置的 DNS 服务器中查询
  3. 如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 com 这个一级域名的服务器
  4. 然后去该服务器查询 google 这个二级域名
  5. 接下来三级域名的查询其实是我们配置的,你可以给 www 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP

以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。

PS:DNS 是基于 UDP 做的查询。

从输入URL到页面加载完成发生了什么

这是一个很经典的面试题,在这题中可以将本文讲得内容都串联起来。

  1. 首先做 DNS 查询,如果这一步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来
  2. 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了
  3. TCP 握手结束后会进行 TLS 握手,然后就开始正式的传输数据
  4. 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件
  5. 首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错
  6. 浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件
  7. 文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行,如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP 2.0 协议的话会极大的提高多图的下载效率。
  8. 初始的 HTML 被完全加载和解析后会触发 DOMContentLoaded 事件
  9. CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西
  10. 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了

  1. 域名解析,查找缓存
    • 查找浏览器缓存(DNS缓存)
    • 查找操作系统缓存(如果浏览器缓存没有,浏览器会从hosts文件查找是否有DNS信息)
    • 查找路由器缓存
    • 查找ISP缓存
  2. 浏览器获得对应的ip地址后,浏览器与远程Web服务器通过TCP三次握手协商来建立一个TCP/IP连接。
  3. TCP/IP连接建立起来后,浏览器就可以向服务器发送HTTP请求
  4. 服务器处理请求,返回资源(MVC设计模式)
  5. 浏览器处理(加载,解析,渲染)
    • HTML页面加载顺序从上而下
    • 解析文档为有意义的结构,DOM树;解析css文件为样式表对象
    • 渲染。将DOM树进行可视化表示
  6. 绘制网页
    • 浏览器根据HTML和CSS计算得到渲染数,最终绘制到屏幕上
    • 一个完整HTTP请求的过程为: DNS Resolving -> TCP handshake -> HTTP Request -> Server -> HTTP Response -> TCP shutdow

浏览器的缓存机制

ETag(验证令牌) 验证缓存的响应

  • 服务器使用 ETag HTTP 标头传递验证令牌。
  • 验证令牌可实现高效的资源更新检查:资源未发生变化时不会传送任何数据

cache-control

  • 每个资源都可通过 Cache-Control HTTP 标头定义其缓存策略
  • Cache-Control 指令控制谁在什么条件下可以缓存响应以及可以缓存多久

缓存层次结构和技巧

  • 使用一致的网址:如果您在不同的网址上提供相同的内容,将会多次提取和存储这些内容。 提示:请注意,网址区分大小写。
  • 确保服务器提供验证令牌 (ETag):有了验证令牌,当服务器上的资源未发生变化时,就不需要传送相同的字节。
  • 确定中间缓存可以缓存哪些资源:对所有用户的响应完全相同的资源非常适合由 CDN 以及其他中间缓存进行缓存。
  • 为每个资源确定最佳缓存周期:不同的资源可能有不同的更新要求。 为每个资源审核并确定合适的 max-age。
  • 确定最适合您的网站的缓存层次结构:您可以通过为 HTML 文档组合使用包含内容指纹(版本号)的资源网址和短时间或 no-cache 周期,来控制客户端获取更新的速度。
  • 最大限度减少搅动:某些资源的更新比其他资源频繁。 如果资源的特定部分(例如 JavaScript 函数或 CSS 样式集)会经常更新,可以考虑将其代码作为单独的文件提供。 这样一来,每次提取更新时,其余内容(例如变化不是很频繁的内容库代码)可以从缓存提取,从而最大限度减少下载的内容大小。

XHR

ajax

基础
发送异步请求

创建过程

  1. 创建XMLHttpRequest对象,也就是创建一个异步调用对象.

  2. 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息(xhr.open(method, url, true/false)).

  3. 设置响应HTTP请求状态变化的函数.(onreadystatechange,readyState==4,status==200)

  4. 发送HTTP请求.(xhr.send(method, url, true/false))

  5. 获取异步调用返回的数据.

  6. 使用JavaScript和DOM实现局部刷新.

method
var xhr;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xhr=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xhr=new ActiveXObject("Microsoft.XMLHTTP");
  }
xhr.onreadystatechange=function()
  {
  if (xhr.readyState==4 && xhr.status==200)
    {
    document.getElementById("txtHint").innerHTML=xhr.responseText;
    }
  }
xhr.open("GET","gethint.asp?q="+str,true);
if(method === 'post') {
    xhr.send(data);
} else {
    xhr.send();
}
}

AJAX的工作原理

Ajax的工作原理相当于在用户和服务器之间加了—个中间层(AJAX引擎),使用户操作与服务器响应异步化。 Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。

ajax优缺点

优点:无刷新更新数据,异步与服务器通信,前后端负载均衡

缺点:

  • ajax干掉了Back和history功能,对浏览器机制的破坏
  • 对搜索引擎支持较弱
  • 违背了URI和资源定位的初衷

Promise

Promise是ES6新增的语法,解决了callback hell的问题。

可以把Promise看成是一个状态机。初始状态是pending,通过resolve or reject 可以将状态转化为fulfilled or rejected.状态一旦改变就不能再次改变。

then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then调用就失去意义了。

Promise实现类似Axios

// 用promise实现类似axios的功能
function p(method, url, data) {
    return new Promise((resolve, reject) => {
        let xhrObj;
        if (window.XMLHttpRequest) {
            xhrObj = new XMLHttpRequest();
        } else {
            xhrObj = new ActiveObject("Microsoft.XMLHTTP");
        }
        xhrObj.open(method, url, true);
        if (method === 'post') {
            xhrObj.send(data);
        }
        xhrObj.send();
        xhrObj.onreadystatechange = () => {
            if (xhrObj.readyState === 400) {
                let res = xhrObj.response;
                if (xhrObj.status === 200) {
                    return resolve(JSON.parse(res));
                } else {
                    return reject(xhrObj);
                }
            }
        }
    })
}

promise

// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  // 用于保存 then 中的回调,只有当 promise
  // 状态为 pending 时才会缓存,并且每个实例至多缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = function (value) {
    if (value instanceof MyPromise) {
      // 如果 value 是个 Promise,递归执行
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = function (reason) {
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  // 用于解决以下问题
  // new Promise(() => throw Error('error))
  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  // 规范 2.2.7,then 必须返回一个新的 promise
  var promise2;
  // 规范 2.2.onResolved 和 onRejected 都为可选参数
  // 如果类型不是函数需要忽略,同时也实现了透传
  // Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
      // 所以用了 setTimeout 包裹下
      setTimeout(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        // 异步执行onRejected
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function () {
        // 考虑到可能会有报错,所以使用 try/catch 包裹
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
  // 规范 2.3.2
  // 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        // 再次调用该函数是为了确认 x resolve 的
        // 参数是什么类型,如果是基本类型就再次 resolve
        // 把值传给下个 then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  // 规范 2.3.3.3.3
  // reject 或者 resolve 其中一个执行过得话,忽略其他的
  let called = false;
  // 规范 2.3.3,判断 x 是否为对象或者函数
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 规范 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 规范 2.3.3.1
      let then = x.then;
      // 如果 then 是函数,调用 x.then
      if (typeof then === "function") {
        // 规范 2.3.3.3
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            // 规范 2.3.3.3.1
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        // 规范 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 规范 2.3.4,x 为基本类型
    resolve(x);
  }
}

generator/yield,async/await

// generator/yield
var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}
// 无限迭代器
function* idMaker(){
    let index = 0;
    while(true)
        yield index++;
}

let gen = idMaker(); // "Generator { }"

console.log(gen.next().value); 
// 0
console.log(gen.next().value); 
// 1
console.log(gen.next().value); 
// 2
// ...

// async/await(它就是 Generator 函数的语法糖)
async function Name() {
  await fetch(ulr)
}

fetch api

优点

  • XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好
  • 语法简洁,更加语义化
  • 基于标准 Promise 实现,支持 async/await
  • 同构方便,使用 isomorphic-fetch

特点

  • Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

实例

	if (window.fetch && method == 'fetch') {
		let requestConfig = {
			credentials: 'include', // Fetch 请求默认是不带 cookie 的,需要设置 `fetch(url, {credentials: 'include'})`
			method: type,
			headers: {
				'Accept': 'application/json',
				'Content-Type': 'application/json'
			},
			mode: "cors",
			cache: "force-cache"
		}

		if (type == 'POST') {
      // 发送post请求需要设置body属性传数据
			Object.defineProperty(requestConfig, 'body', {
				value: JSON.stringify(data)
			})
		}
		
		try {
			const response = await fetch(url, requestConfig); // async function, return Promise
			const responseJson = await response.json(); //  parses response to JSON
			return responseJson
		} catch (error) {
			throw new Error(error)
		}
	} 

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.