《JS万字总结》笔记

189 阅读25分钟

原文链接:juejin.cn/post/684490…

一、变量类型

==和===

  • ==

    非原始,调ToPrimitive,为对象调valueOf,还非原始调toString,最后还非原始则报错

  • ===

    • 先判断类型,不同返回false
    • 再比较大小,不会进行任何隐式转换
    • 对于引用类型来说,只有两者存储的内存地址相同,才会返回true

七大原始类型和Object类型

  • Number
  • BigInt
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol
  • Object

类型判断

原始类型判断

1isNaN可判断是否为数值
2、除了Null,都可以用typeof(返回字符串形式)判断类型
3、判断nullnull不可以通过typeof判断,因为typeof(null)  返回object):
    - 通过null===null来判断是否为null
    - (!a && typeof (a) != 'undefined' && a != 0 && a==a)判断a是否为null
    - Object.prototype.__proto__===a判断a是否为原始对象原型的原型即null
    - typeof (a) == 'object' && !a通过typeof判断null为对象,且对象类型只有null转换为Booleanfalse

非原始类型判断

  • 数组

      1、使用Array.isArray()判断数组
      2、使用[] instanceof Array判断是否在Array的原型链上,即可判断是否为数组
      3、[].constructor === Array通过其构造函数判断是否为数组
      4Object.prototype.toString.call([])判断值是否为'[object Array]'来判断数组
    
  • 对象

      1Object.prototype.toString.call({})结果为'[object Object]'则为对象
      2、{} instanceof Object判断是否在Object的原型链上,即可判断是否为对象
      3、{}.constructor === Object通过其构造函数判断是否为对象
    
  • 函数

      1、使用func typeof function判断func是否为函数
      2、使用func instanceof Function判断func是否为函数
      3、通过func.constructor === Function判断是否为函数
      4、也可使用Object.prototype.toString.call(func)判断值是否为'[object Function]'来判断func
    

其他判断

  • Object.is(a,b)判断a与b是否完全相等,与===基本相同,不同点在于Object.is判断+0不等于-0,NaN等于自身
  • prototypeObj.isPrototypeOf(object)判断object的原型是否为prototypeObj,不同于instanceof,此方法直接判断原型,而非instanceof 判断的是右边的原型链

一个简单的类型验证函数

function isWho(x) {
  // null
  if (x === null) return 'null'
  const primitive = ['number', 'string', 'undefined',
    'symbol', 'bigint', 'boolean', 'function'
  ]
  let type = typeof x
  //原始类型以及函数
  if (primitive.includes(type)) return type
  //对象类型
  if (Array.isArray(x)) return 'array'
  if (Object.prototype.toString.call(x) === '[object Object]') return 'object'
  if (x.hasOwnProperty('constructor')) return x.constructor.name
  const proto = Object.getPrototypeOf(x)
  if (proto) return proto.constructor.name
  // 无法判断
  return "can't get this type"
}

二、深拷贝和浅拷贝

浅拷贝

1Object.assign({},obj)浅拷贝object
2、obj1={...obj2}通过spread展开运算符浅拷贝obj2
3Object.fromEntries(Object.entries(obj))通过生成迭代器再通过迭代器生成对象
4Object.create({},Object.getOwnPropertyDescriptors(obj))浅拷贝obj
5Object.defineProperties({},Object.getOwnPropertyDescriptors(obj))浅拷贝obj

简单实现浅拷贝
// a原拷贝对象,b新对象
for (const key in a) {
    b[key] = a[key]
}
------------------------------------------
for (const key of Object.keys(a)) {
    b[key] = a[key]
}

深拷贝

1JSON.parse(JSON.stringify(obj))通过JSON2次转换深拷贝obj,不过无法拷贝undefinedsymbol属性,无法拷贝循环引用对象
2、函数:
    1.简单版深拷贝,只能拷贝基本原始类型和普通对象与数组,无法拷贝循环引用
        function simpleDeepClone(a) {
          const b=Array.isArray(a) ? [] : {}
          for (const key of Object.keys(a)) {
            const type = typeof a[key]
            if (type !== 'object' || a[key] === null) {
              b[key] = a[key]
            } else {
              b[key] = simpleDeepClone(a[key])
            }
          }
          return b
        }
    2.精简版深拷贝只能拷贝基本原始类型和普通对象与数组,可以拷贝循环引用
        function deepClone(a, weakMap = new WeakMap()) {
          if (typeof a !== 'object' || a === null) return a
          if (s = weakMap.get(a)) return s
          const b = Array.isArray(a) ? [] : {}
          weakMap.set(a, b)
          for (const key of Object.keys(a)) b[key] = clone(a[key], weakMap)
          return b
        }
    3.js原生深拷贝,无法拷贝Symbolnull、循环引用
        function JSdeepClone(data) {
          if (!data || !(data instanceof Object) || (typeof data == "function")) {
            return data || undefined;
          }
          const constructor = data.constructor;
          const result = new constructor();
          for (const key in data) {
            if (data.hasOwnProperty(key)) {
              result[key] = deepClone(data[key]);
            }
          }
          return result;
        }
    4.深拷贝具体版,非完全,但大部分都可以
    function deepClonePlus(a, weakMap = new WeakMap()) {
      const type = typeof a
      if (a === null || type !== 'object') return a
      if (s = weakMap.get(a)) return s
      const allKeys = Reflect.ownKeys(a)
      const newObj = Array.isArray(a) ? [] : {}
      weakMap.set(a, newObj)
      for (const key of allKeys) {
        const value = a[key]
        const T = typeof value
        if (value === null || T !== 'object') {
          newObj[key] = value
          continue
        }
        const objT = Object.prototype.toString.call(value)
        if (objT === '[object Object]' || objT === '[object Array]') {
          newObj[key] = deepClonePlus(value, weakMap)
          continue
        }
        if (objT === '[object Set]' || objT === '[object Map]') {
          if (objT === '[object Set]') {
            newObj[key] = new Set()
            value.forEach(v => newObj[key].add(deepClonePlus(v, weakMap)))
          } else {
            newObj[key] = new Map()
            value.forEach((v, i) => newObj[key].set(i, deepClonePlus(v, weakMap)))
          }
          continue
        }
        if (objT === '[object Symbol]') {
          newObj[key] = Object(Symbol.prototype.valueOf.call(value))
          continue
        }
        newObj[key] = new a[key].constructor(value)
      }
      return newObj
    }

三、原型和原型链

原型

  • 每个实例对象都有一个__proto__属性,该属性指向的是原型对象(隐式原型对象),此属性其实是个访问器属性,并不是真实存在的属性,或者可以使用es6的Reflect.getPrototypeOf(obj)和Object.getPrototypeOf(obj)方法获取对象的原型,其关系Reflect.getPrototypeOf({}) === Object.getPrototypeOf({}) === {}.proto
  • 每个函数有2个属性,一个是是__proto__(与普通对象类似),还有一个是函数专有的prototype属性,该属性指向的也是原型对象(显式原型对象)
  • 构造函数的显示原型 === 当前构造函数实例对象的隐式原型对象
  • 原型对象的本质:普通的object实例
  • 不是所有的对象都会有原型,比如对象原型Object.prototype的原型Object.prototype.__proto__就指向null,字典对象的原型也为null(把对象的__proto__设置为null,或者使用Object.create(null)创建一个没有原型的字典对象,但是这个对象还是属于对象类型),所以原始对象原型(Object.prototype)就是最原始的原型,其他对象类型都要继承自它。
  • 箭头函数虽然属于函数,由Function产生,但是没有prototype属性没有构造器特性,所以也就没有所谓的constructor,就不能作为构造器使用

原型链

  • 查找对象的属性的时候先在自身找,如果没有沿着__proto__找原型对象
  • 如果原型对象上没有,继续沿着__proto__,知道找到Object的原型对象
  • 如果还没有,返回undefined
  • 原型链:沿着__proto__查找的这条链就是原型链
  • 如图

构造函数

静态成员: 在构造函数本身上添加的成员,只能通过构造函数来访问
实例成员: 实例成员就是在构造函数内部,通过this添加的成员。实例成员只能通过实例化的对象来访问。
定义构造函数的规则:公共属性定义到构造函数里面,公共方法我们放到原型对象身上。

new 创建实例的过程

  • 创建一个空对象 {}
  • 该空对象的原型指向构造函数(链接原型) son.__proto__ = Father.prototype
  • 绑定 this:将对象作为构造函数的 this 传进去,并执行该构造函数;Father.call(son)
  • 为新对象属性赋值
  • 返回新对象 return this,此时的新对象就拥有了构造函数的方法和属性了

每个实例上的方法共享吗

  • 在构造函数上直接定义方法(不共享)
    • 在构造函数上通过this来添加方法的方式来生成实例,每次生成实例,都是新开辟一个内存空间存方法。这样会导致内存的极大浪费,从而影响性能。
  • 通过原型添加方法(共享)

继承ES5

instanceof

实例对象a instanceof 构造函数B
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

isPrototypeOf()

isPrototypeOf()的用法和instanceof相反。
它是用来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false

原型链继承

  • 代码

    Child.prototype = new Parent()
    
  • 优点

      继承了父类的模板,又继承了父类的原型对象
    
  • 缺点

    • 如果要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面
    • 无法实现多继承(因为已经指定了原型对象了)
    • 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响(这点从修改child1.colors可以看出来)
    • 创建子类时,无法向父类构造函数传参数(这点从child1.name可以看出来)

构造函数继承

  • 代码

        function Child () {
          Parent.call(this, 'child')
        }
    
  • 优点

      解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数
    
  • 缺点

    • 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
    • 实例并不是父类的实例,只是子类的实例
    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

组合继承

  • 代码
    // 构造继承
    function Child () {
      Parent.call(this, ...arguments)
    }
    // 原型链继承
    Child.prototype = new Parent()
    // 修正constructor
    Child.prototype.constructor = Child
    
  • 优点
    • 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
    • 弥补了原型链继承中引用属性共享的问题
    • 可传参,可复用
  • 缺点
    • 使用组合继承时,父类构造函数会被调用两次
    • 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。

寄生组合继承

  • 代码
    // 构造继承
    function Child () {
      Parent.call(this, ...arguments)
    }
    // 原型式继承
    Child.prototype = Object.create(Parent.prototype)
    // 修正constructor
    Child.prototype.constructor = Child
    

原型式继承

  • 代码
    • ES5之前
    function objcet (obj) {
        function F () {};
        F.prototype = obj;
        F.prototype.constructor = F;
        return new F();
    }
    var cat = {
      heart: '❤️',
      colors: ['white', 'black']
    }
    
    var guaiguai = objcet(cat)
    var huaihuai = objcet(cat)
    
    • ES5之后
    var cat = {
      heart: '❤️',
      colors: ['white', 'black']
    }
    
    var guaiguai = Object.create(cat)
    var huaihuai = Object.create(cat)
        ```
    

寄生式继承

  • 代码
    var cat = {
      heart: '❤️',
      colors: ['white', 'black']
    }
    function createAnother (original) {
        var clone = Object.create(original);
        clone.actingCute = function () {
          console.log('我是一只会卖萌的猫咪')
        }
        return clone;
    }
    var guaiguai = createAnother(cat)
    
  • 优点
    • 继承某个对象上的属性,同时又想在新创建的对象中新增上一些其它的属性。
    • 再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
  • 缺点
    • 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
    • 谨慎定义方法,以免定义方法也继承对象原型的方法重名
    • 无法直接给父级构造函数使用参数

混入方式继承多个对象

  • 代码
    function Child () {
        Parent.call(this)
        OtherParent.call(this)
    }
    Child.prototype = Object.create(Parent.prototype)
    Object.assign(Child.prototype, OtherParent.prototype)
    Child.prototype.constructor = Child
    

  • 本质还是一个函数,类就是构造函数的另一种写法。
  • 类没有变量提升
  • 类的所有方法都定义在类的prototype属性上面
  • 通过Object.assign,在原型上追加方法。
    Object.assign(Father.prototype,{
        dance(){
            return '我爱跳舞';
        }
    });
    
  • 继承ES6
    class Father {
        constructor(name){
            this.name = name;
        }
        dance(){
            return '我在跳舞';
        }
    }
    class Son extends Father{
        constructor(name,score){
            super(name);
            this.score = score;
        }
        sing(){
            return this.name +','+this.dance();
        }
    }
    let obj = new Son('小红',100);
    

ES5继承和ES6继承的区别:

  • 在ES5中的继承(例如构造继承、寄生组合继承) ,实质上是先创造子类的实例对象this,然后再将父类的属性和方法添加到this上(使用的是Parent.call(this))。
  • 而在ES6中却不是这样的,它实质是先创造父类的实例对象this(也就是使用super()),然后再用子类的构造函数去修改this。

总结

  • 构造函数特点:
    • 1.构造函数有原型对象prototype。
    • 2.构造函数原型对象prototype里面有constructor,指向构造函数本身。
    • 3.构造函数可以通过原型对象添加方法。
    • 4.构造函数创建的实例对象有__proto__原型,指向构造函数的原型对象。
  • 类:
    • 1.class本质还是function
    • 2.类的所有方法都定义在类的prototype属性上
    • 3.类创建的实例,里面也有__proto__指向类的prototype原型对象
    • 4.新的class写法,只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
    • 5.ES6的类其实就是语法糖。

四、作用域、执行上下文与闭包

作用域与作用域链

作用域

  • 抽象的概念
  • 用来决定代码执行的范围,变量所属的范围
  • 作用域是代码定义的时候决定的
  • 作用:
    • 隔离变量
    • 规定其之后的作用域链是什么:[[scopes]]:上一级作用域链

作用域链

  • 作用域链也就是所谓的变量查找的范围
  • 在当前作用域引用变量时,如果没有此变量,则会一路往父级作用域查找此变量,直到全局作用域,如果都没有,在非严格情况下会自动声明,所以是undefined,在严格条件下则会报错
  • 作用域链是一个数组结构
  • 该结构内保存的事一个个变量对象
  • 在js代码正式执行之前创建的

执行上下文

  • js引擎在js代码执行之前先创建一个执行环境
  • 进入该环境创建一个变量对象,该对象用于收集当前环境下的:变量、函数、函数参数、this
  • 确认this指向
  • 创建作用域链

执行上下文栈

  • 用于保存执行上下文
  • 先进后出
  • 执行上下文是动态创建的:
    • 函数每调用一次就创建一次执行上下文,执行完就销魂

闭包

定义

  • 闭包是一个存在内部函数的引用关系
  • 该引用指向的是外部函数的局部变量对象(前提是内部函数使用了外部函数的局部变量)

形成条件

  • 函数嵌套
  • 内部函数引用外部函数的局部变量
  • 内部函数被使用,注意: 函数变量提升的时候,如果内部函数没有被使用,在预解析的过程中不会定义内部函数

优缺点

  • 延长外部函数变量对象的生命周期
  • 使用闭包能够间接的从函数外部访问函数内部的私有变量

五、this

this指向

如何改变函数的this指向

  • apply方法中第一个参数为被调用的函数中的this指向,传入你想要绑定的this值即可,第二个参数为被调用函数的参数集合,通常是个数组
  • call与apply方法基本一致,区别在于传入参数形式不同,call传入的参数为可变参数列表,参数按逐个传入
  • bind方法与以上不同的是不会直接调用函数,只是先绑定函数的this,到要使用的时候调用即可,此方法返回一个绑定this与参数之后的新函数,其传入参数形式同call
  • 通过变量保留指定this来达到固定this

apply、call、bind实现

  • 手动实现apply
Function.prototype.Apply = function (thisArg, args = Symbol.for('args')) {
  console.dir(this)            //this为这个方法的调用者=>foo函数
  const fn = Symbol('fn')      //生成一个不重复的键
  thisArg[fn] = this || window //把foo函数作为传入this的一个方法
  args === Symbol.for('args') 
  ? thisArg[fn]()
  : thisArg[fn](...args)       //调用这方法,传参
  delete thisArg[fn]           //使用完删除
}
  • 手动实现call
Function.prototype.Call = function (thisArg) {
  console.dir(this)            //this为这个方法的调用者=>foo函数
  const fn = Symbol('fn')      //生成一个不重复的键
  thisArg[fn] = this || window //把foo函数作为传入this的一个方法
  const args = Array.from(arguments).slice(1)
  args.length ? thisArg[fn](...args) : thisArg[fn]()  //调用这方法,传参
  delete thisArg[fn]           //使用完删除
}
  • 手动实现bind
Function.prototype.Bind = function (thisArg) {
  const fn = Symbol('fn')       //生成一个不重复的键
  thisArg[fn] = this || window  //把foo函数作为传入this的一个方法
  const f = thisArg[fn]         // 负责一份函数
  delete thisArg[fn]            //删除原来对象上的函数,但是保留了this指向
  const args = Array.from(arguments).slice(1)
  return function () {
    const arg = args.concat(...arguments)
    f(...arg)
  }
}

六、eventloop

浏览器是如何执行js代码的:

  • 通常浏览器在最开始运行js代码的入口就是html中的script标签所涵盖的代码
  • 当GUI渲染线程解析到script标签,则会把标签所涵盖的js代码加入到宏任务队列中
  • 首先js引擎(如V8引擎)先取第一个宏任务,即script的代码块,然后主线程在调用栈中解析js代码
  • 等所有代码解析完成之后开始运行js代码
  • 如果遇到同步代码直接执行
  • 遇到异步代码,如果是宏任务类型即异步WebApis处理的异步代码,那么将会通知WebApis在对应的线程中处理异步任务,此时js主线程继续执行下面的代码,在其他线程处理完毕之后如果有回调函数,则异步线程会将回调函数加入到宏任务队列尾部,
  • 如果是微任务类型的异步代码,也同宏任务处理,只不过是把回调函数加入到微任务队列中,其执行的优先级高于宏任务队列
  • 当同步代码全部执行完成,主线程将会一直检测任务队列,如果有异步微任务则执行完全部的微任务
  • 进一步执行浏览器渲染进程绘制页面,之后就是开始下一轮的事件循环,就又回到取宏任务执行
  • 这里注意,所有的微任务都是由宏任务中执行的代码产生,一开始只有宏任务队列有任务

微任务

  • 微任务可以由这些方法关键字调用产生Promise、async、await、MutaionObserver、process.nextTick(Node.js环境),优先级比宏任务高

宏任务

  • 一般的宏任务队列存放的是定时器任务setTimeout、setInterval、ajax请求、图片动态加载、DOM事件

七、WebWorker多线程

// test.html(主线程)
<button>开启计时器</button>
  <span class="time"></span>
  <script>
    document.querySelector('button')
        .addEventListener('click', function handleClick() {
          console.log('开启定时器......');
          if (window.Worker) {
            const worker = new Worker('StartTimeLock.js');
            worker.postMessage({ msg: 'User:你好,我准备开启定时器了!', state: true });
            worker.onmessage = function (e) {
              console.log(e.data.msg||e.data.time);
              document.querySelector('.time')
                .innerHTML = e.data.time==undefined?'开始':e.data.time;
              if (e.data.time === 10) {
                worker.terminate();
              }
            };

          }
        }, false);
  </script>
----------------------------------------------
// StartTimeLock.js(worker线程)
onmessage = function (e) {
  console.log(e.data.msg);
  postMessage({ msg: 'Worker:Worker正在处理您的消息!!' });
  if (e.data.state) {
    let time = 0;
    setInterval(function handleInterval() {
      postMessage({ time: time++ });
    }, 1000);
  }
};

webworker线程与js主线程最大的区别就在于webworker线程无法操作window与document对象

八、script标签之async与defer

async

- 引入的js需要异步加载和执行,此属性只适用于外部引入的js

defer

- 也将会使js异步加载执行,且会在文档被解析完成后执行

使用

  • 如果只有async,那么脚本在下载完成后异步执行。
  • 如果只有defer,那么脚本会在页面解析完毕之后执行。
  • 如果都没有,那么脚本会在页面中马上解执行,停止文档解析阻塞页面加载
  • 如果都有那么同async,当然此情况一般用于html的版本兼容下,如果没有async则defer生效
  • 不过还是推荐直接把script标签放在body底部

九、window之location、navigator

location

  • location对象

补充一个origin属性,返回URL协议+服务器名称+端口号 (location.origin == location.protocol + '//' + location.host)

  • location方法

- assign(url),通过调用window.location.assign方法来打开指定url的新页面window.location.assign('http://www.baidu.com')在当前页面打开百度,可回退
- replace(url),在当前页面打开指定url,不可回退
- reload([Boolean]),调用此方法将会重新加载当前页面,如果参数为false或者不填,则会以最优的方式重新加载页面,可能从缓存中取资源,如果参数为true则会从服务器重新请求加载资源

navigator

  • navigator.appCodeName 只读,任何浏览器中,总是返回 'Gecko'。该属性仅仅是为了保持兼容性。
  • navigator.appName 只读,返回浏览器的官方名称。不要指望该属性返回正确的值。
  • navigator.appVersion 只读,返回一个字符串,表示浏览器的版本。不要指望该属性返回正确的值。
  • navigator.platform 只读,返回一个字符串,表示浏览器的所在系统平台。
  • navigator.product 只读,返回当前浏览器的产品名称(如,"Gecko")。
  • navigator.userAgent 只读,返回当前浏览器的用户代理字符串(user agent string)

十、WebSocket

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议,即连接双方可以同时实时收发数据,它可以在用户的浏览器和服务器之间打开双工、双向通讯会话。
  • WebSocket API提供全局方法WebSocket(url[, protocols])创建实例,参数1 对方绝对url其url以ws://或者wss://(加密)开头,参数2 protocols是单协议或者包含协议的字符串数组
// 必须传入绝对URL,可以是任何网站
const s = new WebSocket('ws://www.baidu.com') 
s.readyState    // 0 建立连接 1 已经建立 2 正在关闭 3 连接已关闭或者没有链接成功
s.send('hello') // 发送的数据必须是纯文本
s.onopen = function () {}
s.onerror = function () {}
s.onmessage = function (event) {
  // 当接收到消息时
  console.log(event.data) // 数据是纯字符
}
s.close()   // 关闭连接
s.onclose = function (event) {
  /*
    * event.wasClean 是否明确的关闭 
    * event.code 服务器返回的数值状态码
    * event.reason 字符串,服务器返回的消息
    */
}
  • 10个属性

    • binaryType 返回websocket连接所传输二进制数据的类型(blob, arraybuffer)

    • bufferedAmount 只读 返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。一旦队列中的所有数据被发送至网络,则该属性值将被重置为0。但是,若在发送过程中连接被关闭,则属性值不会重置为0。

    • extensions 只读 返回服务器选择的扩展名。这当前只是空字符串或连接协商的扩展列表

    • onclose 用于指定连接失败后的回调函数

    • onmessage 用于指定当从服务器接受到信息时的回调函数

    • onopen 用于指定连接成功后的回调函数

    • protocol 只读 服务器选择的下属协议

    • readyState 只读 当前的链接状态,共4个

      • 0 建立连接

      • 1 已经连接

      • 2 正在关闭

      • 3 连接已经关闭或者没有连接成功

    • url 只读 WebSocket 的绝对路径

  • 2个方法

    • close(code, reason) 数字状态码 可选 默认 1005和一个可选的类可读的字符串,它解释了连接关闭的原因。
    • send(data) 向服务器发送数据(ArrayBuffer,Blob等)

十一、存储

cookie

  • cookie是由服务器发送给客户端用于存储少量信息,以键值对形式存储{key:value}

  • 客户端请求服务器时,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。而客户端浏览器会把Cookie保存起来。当浏览器再请求 服务器时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器通过检查该Cookie来获取用户状态

  • cookie是不可跨域,不过只在域名不同的情况下不支持跨域,忽略协议与端口,https://localhost:80/http://localhost:8080/的Cookie是共享的,可以通过domain设置域,path设置域下的共享路径

  • cookie属性

    • name 表示设置的cookie名也就是key,不能重复,不可更改
    • value 表示设置cookie的值
    • domain 表示cookie绑定的域名,默认绑定当前域,多级域名不可交换cookie,如果设置以点开头的域名,则所有子域名可以访问,如设置.baidu.com,则a.baidu.com可访问其上级域名的cookie
    • path 表示cookie所能使用的路径,默认'/'路径,只要满足当前匹配路径以及子路径都可以共享cookie
    • maxAge 表示cookie失效时间,单位秒,正数为失效时间,负数表示当前cookie在浏览器关闭时失效,0表示删除cookie
    • secure 表示cookie是否使用安全协议传输如HTTPS、SSL,默认不使用,只在HTTPS等安全协议下有效,这个属性并不能对客户端的cookie进行加密,不能保证绝对的安全性
    • version 当前cookie使用的版本号,0 表示遵循Netscape的Cookie规范(多数),1表示遵循W3C的RFC2109规范(较严格),默认为0
    • same-site 规定浏览器不能在跨域请求中携带 Cookie,减少CSRF攻击
    • HttpOnly 如果这个属性设置为true,就不能通过js脚本来获取cookie的值,用来限制非HTTP协议程序接口对客户端Cookie进行访问,可以有效防止XSS攻击(跨站脚本攻击,代码注入攻击)
  • 前端通过document.cookie对cookie进行读写操作

  • 创建cookie就是后端的事情了

session

  • session 表示服务器与客户端的一次会话过程,session对象存储特定用户的属性及配置信息
  • 当用户在应用程序的 Web 页之间跳转时,存储在session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 session 超时失效时会话结束

  • 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建创建对应的 session ,请求返回时将此 session 的唯一标识信息 sessionID 返回给浏览器,浏览器接收到服务器返回的 sessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 sessionID 属于哪个域名
  • 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 sessionID,再根据 sessionID 查找对应的 session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 session 证明用户已经登录可执行后面操作
  • session 的运行依赖 session id,而 session id 是存在 Cookie中的

cookie与session的区别

  • cookie数据存放在客户的浏览器上,session数据放在服务器上
  • cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。用户验证这种场合一般会用 session
  • session保存在服务器,客户端不知道其中的信息;反之,cookie保存在客户端,服务器能够知道其中的信息
  • session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
  • session中保存的是对象,cookie中保存的是字符串
  • session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的
  • session: 是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中
  • cookie: 是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现session的一种方式

本地存储localStorage与sessionStorage

localStorage

  • localStorage浏览器api,用于存储本地数据,可持久化,永不过期,除非主动删除

  • 基本使用:

      localStorage.setItem("b", "isaac");  //设置b为"isaac"
      localStorage.getItem("b");           //获取b的值,为"isaac"
      localStorage.key(0);                 //获取第0个数据项的键名,此处即为“b”
      localStorage.removeItem("b");        //清除c的值
      localStorage.clear();                //清除当前域名下的所有localStorage数据
    
  • localStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据,一般用于跨页面共享数据

  • 可通过window.addEventListener("storage", function(e){}设置localStorage事件监听,当存储区域的内容发生改变时,将会调用回调

sessionStorage

  • sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储

  • 基本使用:

      sessionStorage.setItem(name, num);    //存储数据
      sessionStorage.setItem('value2', 119);
      sessionStorage.valueOf();             //获取全部数据
      sessionStorage.getItem(name);         //获取指定键名数据
      sessionStorage.sessionData;           //sessionStorage是js对象,也可以使用key的方式来获取值
      sessionStorage.removeItem(name);      //删除指定键名数据
      sessionStorage.clear();
    
  • 使用方式与localStorage类似

  • 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除

  • 主要用于存储当前页面独有的数据,不与浏览器其他页面共享

cookie、sessionStorage、localStorage区别

  • 数据存储方面

    • cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
    • sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。
  • 存储数据大小

    • 存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。
    • sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  • 数据存储有效期

    • sessionStorage:仅在当前浏览器窗口关闭之前有效;
    • localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;
    • cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
  • 作用域不同

    • sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
    • localStorage在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
    • cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在