JS高级

this指向分析

指向

  • 直接调用,指向window

  • 通过对象调用,指向对象

  • call/apply

    总结:跟位置无关,跟调用方式有关。只有在执行的时候this指向才会被确定

绑定规则:

  • 默认绑定

    // 独立函数调用,this指向window
    function foo(){
        console.log(this)
    }
    foo() // window
    
    // 跟位置无关,跟调用方式有关
    var  obj = {
        foo:function(){
            console.log(this)
        }
    }
    var baz = obj.foo
    baz()  // window
    
    // 高阶函数
    function foo1(fn){
        fn()
    }
    foo1(obj.foo) // window
    
    // 严格模式下,独立调用的函数中的this指向的是undefined
    "use strict"
    
    var  obj = {
        foo:function(){
            console.log(this)
        }
    }
    var baz = obj.foo
    baz()  // undefined
    
  • 隐式绑定

    var  obj = {
        foo:function(){
            console.log(this)
        }
    }
    obj.foo() // obj
    
  • 显示绑定

    • call(obj,[item1],[item2])

    • apply(obj,[item1,item2])

      var obj={
          name:'hyf'
      }
      funciton foo(){
          console.log(this)
      }
      
      foo.call(obj) // obj
      
    • bind:创建一个绑定函数BF,怪异函数对象。永久绑定

      var obj={
          name:'hyf'
      }
      funciton foo(){
          console.log(this)
      }
      
      var f = foo.bind(obj,[item1],[item2]...)
      
  • new

    /*
    1. 创建一个新的空对象
    2. 将this指向这个空对象
    3. 执行函数体重的代码
    4. 如果函数没有返回其他对象时,默认返回这个对象
    */
    function Foo(){
        console.log(this)
    }
    var foo = new Foo() // Foo {}
    

内置函数的调用绑定

  • forEach(fn,this): 默认绑定window, 可以通过第二个参数绑定this
  • setTimeout(): this指向window
  • el.coclick: this指向 el

优先级

默认绑定 < 隐式绑定 < 显示绑定 < new绑定。new不可以和apply/call一起使用。bind优先级大于call和apply

null/undefined

function Foo(){
    console.log(this)
}
Foo.call(null) // window, 忽略显示绑定,使用默认规则
Foo.apply(undefined)  // window,忽略显示绑定,使用默认规则

间接函数引用

var obj1 = {
    foo:function(){
        console.log(this)
    }
}
var obj2 = {
    name:'hyf'
}
obj2.foo = obj1.foo
obj2.foo()  // obj2
(obj2.foo = obj1.foo)()  // window

箭头函数

  • 不会绑定this、arguments
  • 不能作为构造函数
var foo = (arg) => {
    // do some thing
}
var obj = (arg) => ({name:33})  // obj = {name:33}

浏览器原理

网页解析过程

  • DNS域名解析
  • 通过IP地址,与服务器三次握手
  • 获取URI资源
  • html下载到浏览器中

浏览器渲染过程

简单渲染过程

详细流程

  • 下载并解析index.html,生成DOM树

  • 下载并解析CSS,生成样式规则,CSSOM,CSS对象模型

  • DOM Tree+ CSSOS 生成渲染树,render Tree

  • 在Reader Tree上计算节点尺寸和位置等信息,进行布局Layout

  • 绘制Paint,将每个frame转为屏幕上的实际的像素点

    link元素不会阻塞DOM Tree 但会阻塞Reader Tree构建

    Reader Tree 与 DOM Tree不是一一对应的,

回流和重绘

  • 回流reflow: 对节点的大小、位置修改重新计算称为回流
    • DOM结构发生改变,添加或删除
    • 改变布局(width,height,padding,font-size)
    • resize(修改了窗口的尺寸)
    • 调用getComputedStyle方法获取尺寸、位置信息
  • 重绘 repaint: 重新渲染
    • 修改背景色,文字颜色,边框颜色,样式

回流一定会引起重绘

  • 如何避免回流

    • 修改样式时尽量一次性修改

    • 尽量避免频繁的操作DOM, DocumentFragment

      const list = document.querySelector('#list')
      const fruits = ['Apple', 'Orange', 'Banana', 'Melon']
      
      const fragment = new DocumentFragment()
      
      fruits.forEach((fruit) => {
        const li = document.createElement('li')
        li.textContent = fruit
        fragment.appendChild(li)
      })
      
      list.appendChild(fragment)
      
    • 尽量避免通过getComputedStyle获取尺寸,位置等信息

    • 对某些元素使用position的absolute或者fixed, 开销相对较少

合成层

  • 默认情况下,标准流中的内容都是被绘制在同一个图层中的
  • 创建新的合成层
    • 3D transforms
    • video、canvas、iframe
    • opacity动画转换时
    • position: fixed
    • will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
    • animation或transition设置了opcity、transform

script元素与页面解析

遇到script,会停止DOM解析,先加载执行script脚本

  • defer属性:
    • 不会阻塞DOM Tree构建过程。
    • 在DOMContentLoaded之前执行
    • 多个脚本顺序执行
    • 建议放到head中,让浏览器先加载
  • async属性:
  • 不阻塞页面
  • 脚本完全独立
  • 不能保证多个脚本顺序
  • 不能保证在DOMContentLoaded之前或之后执行

JS原理

Webkit = WebCore + JavaScriptCore

V8引擎原理

V8引擎是由C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等,V8可以独立运行,也可以嵌入到任何C++应用程序中

V8

  • Parse模块,会将JS代码转为AST(抽象语法树),解释器并不认识JS代码。如果函数没有被调用是不会被转为AST树的
  • lgnition是一个解释器,会将AST转为ByteCode(字节码),同时会搜集TurboFan优化所需的信息(如函数参数类型信息),如果函数只调用一次,lgnition会解释执行ByteCode
  • TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码
    • 如果一个函数被调用多次,那么会被标记为热点函数,就会经过TurboFan转换为优化的机器码,提高代码的执行性能
    • 但是,机器码实际上也会被还原为ByteCode,这是因为函数在执行中传入的类型改变,之前优化的机器码并不能准确的处理,就会逆转为字节码

官方

JS执行上下文

整体执行流程

  1. js代码执行之前,初始化全局对象Global Object(GO)

    • 该对象在堆内存中创建,所有作用域都可访问

    • 包含Date、Array、String、Number、setTimeout、setInterval

    • 还有window指向自己

  2. 每一个执行上下文都会关联一个VO(Variable Object,变量对象),变量和函数声明都会被添加到这个VO对象中

  3. 全局代码被执行的时候,VO就是GO对象

  4. AO对象:函数执行上下文,会创建一个AO(Activation Object)

    • 这个AO会使用arguments作为初始化,并且初始值是传入的参数
    • 这个AO对象会作为执行上下文的VO来存放变量的初始化
var message = 'aaa'
function bar(){
    var message = 'bb'
}
var num1= 0
var num2= 1
var result = num1+num2

函数会被先创建

函数代码的多次执行

函数在第一次执行完成以后,会被栈移除,AO对象的移除需要看情况(垃圾回收)

作用域和作用域链

函数在创建的时候,作用域链就被确定了,跟调用位置无关

作用域链

作用域链综述:

  • 首先,在代码执行之前,创建GO(Global Object)--VO(Variable Object)对象

    在GO对象中
    message = undefined
    foo = 0x001(指向一个函数对象 Function Object),这个函数对象中包含一些属性,arguments/name/length/[[scopes]]...
    [[scopes]] 中包含0:Global Object
    bar = undefined
    test = undefined
    
  • 代码执行,

    运行 var message = 'global message' 时,GO对象的message被赋值为'global message'
    调用 foo() 时,运行 foo函数代码,此时创建 foo VO(Variable Object)即AO(Activation Object)
    foo AO中 name = undefined , bar = 0x002(指向一个函数对象 Function Object) 这个函数对象中包含一些属性,arguments/name/length/[[scopes]]...
    [[scopes]] 包含 0:Global Object
    返回 bar函数的内存地址 0x002
    将0x002赋值给bar变量
    此时GO中的bar = 0x002
    
  • 代码执行bar()

    调用函数bar(),运行0x002中的代码,创建test=0x003 函数(Function Object)arguments/name/length/[[scopes]]...
    [[scopes]] 中包含0:foo AO, 1:Global Object
    打印name, 寻找name顺序 0--->1
    返回0x003
    将0x003赋值给GO中test变量
    

面试题

// 1.
var n = 100
function foo(){
    n = 200
}
foo()
console.log(n) // 200
// 2
var n = 100
function foo(){
    console.log(n) // undefined
    var n = 200
    console.log(n) // 200
}
foo()
// 3
var n = 100
function foo1(){
    console.log(n)
}

function foo2(){
    var n = 200
    console.log(n) // 200
    foo1()
}
foo2()
/*
分析:
1. 创建GO,n=undefined,foo1=0x001(arguments/name/[[scopes]]:{"0":GO}),foo2=0x002(arguments/name/[[scopes]]:{"0":AO,"1":GO}) AO 包含 n = undefined
2. 调用foo2() 执行foo2代码体中的var n = 200, foo2 AO n = 200,打印200,调用foo1()
3. foo1执行代码,打印n,n的作用域只用GO,所以打印n为100
*/
// 4
var a = 100
funciton foo(){
    console.log(a) // undefined
    return
    var a = 100
}
foo()
// 5
function foo(){
    var a = b = 100
}
foo()
console.log(a) // c is not defined
console.log(b) // 100 , 相当于 var a = 100;  b = 100

内存管理

内存的生命周期

  • 分配申请你所需的内存
  • 使用分配的内存(存放变量,对象等)
  • 不需要使用时,对其进行释放

JS内存分配

  • JS对原始数据类型的内存分配直接在栈空间进行分配
  • JS对复杂数据类型内存的分配会在堆内存中开辟空间,将空间指针地址返回给变量引用

垃圾回收机制 GC(Garbage Collection)

对于那些不再使用的对象,称之为垃圾,需要被回收。Lisp最先提出

引用计数 (Reference counting)

  • 概念

    • 一个对象有一个引用指向它时,对象的引用就+1
    • 当一个对象的引用为0时,这个对象就可以被销毁
  • 缺陷

    • 循环引用

      obj1 = {}             
      obj2 = {}
      obj1.info = obj2
      obj2.info = obj1
      

标记清除 (mark-Sweep)

  • 概念
    • 核心思路:可达性
    • 设置一个根对象,定期从根对象开始,找所有从根开始有引用到的对象,对于那些没有引用到的对象,就认为是不可用的对象
    • 可以很好的解决循环引用的问题

标记整理

  • 与标记清除类似
  • 不同的是,回收期间将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化

分代回收

  • 将内存中对象分为新生代、老生代
  • 新创建的对象,都放到新生代,使用结束以后,GC清除垃圾,经过多次回收,还剩下的对象放到老生代中
  • 老生代的检查频率是很低的。

增量收集

  • 如果有很多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
  • 所以引擎试图将垃圾收集工作分为几部分来做,然后将这几部分逐一处理,这样会有许多微小的延迟而不是一个很大的延迟

闲时收集

  • GC只会在CPU空闲时尝试运行,以减少可能对代码执行的影响

内存管理

闭包

  • 最早出现在Scheme
  • 如果一个函数,能够访问外层作用域中的变量,那么这个函数和周围环境就是一个闭包
  • 所以,JS中的函数都可以称为闭包函数,因为每当创建一个函数时,这个函数都可以访问外层,如GO中的变量
function createAdder(con){
    function adder(num){
        return con+num
    }
    return adder
}

var adder5 = createAdder(5)  // con固定为5 addr函数中num+5
adder5(10)  // 15 5+10 
var adder10 = createAdder(10)   // con固定为10 addr函数中num+5
adder10(10)  // 20  10+10

内存泄漏与释放

  • 对于某些内存不再使用,需要手动进行释放add10 = null

内存优化

  • AO不使用的属性会被浏览器优化

函数增强

对象属性


// 对象属性
function foo(){}
foo.mes = 'xxx'
console.log(foo.mes)  // xxx

// 默认属性,name/length(参数的个数)/arguments

// 剩余参数,length 不会将剩余参数算在内,默认值也不会算在内
function foo(...arg){console.log(arg)} // [1,2,3]
foo(1,2,3)

arguments

  • 类数组对象

    /*
    可以使用length,索引
    不可以使用map,filter等
    转换成数组
    1. for of  push
    2. [...arguments]
    3. Arrary.from(arguments)
    4. slice(start,end)   [].slice.apply(arguments)
    5. Array.prototype.slice.apply(arguments)
    */
    
  • 在箭头函数中是不会绑定arguments的,会一层一层沿着作用域寻找arguments

纯函数

满足以下条件,被称为纯函数:

  • 此函数在相同输入的情况下,必须是相同的输出
  • 函数的输出与输出值以外的其他隐藏信息和状态无关
  • 不能在语义上可观察的函数副作用,如“触发事件”
    • 除了返回函数值外,还进行了一些其他操作,如修改全局变量等

splice和slice

splice 不是一个纯函数  修改原来数组
slice  是一个纯函数  不会修改原来数组,返回一个新的数组

优势

  • 安心编写、安心使用
  • 编写的时候不需要关注外部变量状态,只关注自己的业务逻辑即可
  • 使用的时,不会修改外部资源

柯里化 Curring

概念:将一个函数转换成一个接收参数的,并且返回一个函数去处理剩余参数的过程称为柯里化
// 普通函数
function foo(a,b,c) {
    console.log(a,b,c)
}
foo(1,2,3)


// 柯里化
function bar(a){
    return function(b){
        return function(c){
            console.log(a,b,c)
        }
    }
}
bar(1)(2)(3)

// 柯里化另一种写法:箭头函数
var foo2 = a => b => c => console.log(a,b,c)
foo2(1)(2)(3)

自动柯里化封装

function foo(a,b,c){
    console.log(a,b,c)
}

function hyCurrying(fn){
    function currying(...arg){
        if (arg.length>=fn.length) {
            // 传入参数如果等于原函数的形参个数
            return fn(...arg)  // 可以绑定this fn.apply(this,arge)
        } else {
            // 如果传入函数小于形参个数,返回一个函数去处理剩余参数
            return function(...newArr){
                return currying(...[...arg,...newArr])  // 可以绑定currying.apply(this,[...arg,...newArr])
            }
        }
    }
    return currying
}
var curryingFn = hyCurrying(foo)  // 柯里化
curryingFn(1,2,3)    // 1 2 3
curryingFn(1)(2,3)    // 1 2 3
curryingFn(1)(2)(3)    // 1 2 3

应用场景

// 应用一:参数复用
function uri_curring(protocol) {
  return function(hostname, pathname) {
    return `${protocol}${hostname}${pathname}`; 
  }
}

// 测试一下
const uri_https = uri_curring('https://');

const uri1 = uri_https('www.fedbook.cn', '/frontend-languages/javascript/function-currying/');
const uri2 = uri_https('www.fedbook.cn', '/handwritten/javascript/10-实现bind方法/');
const uri3 = uri_https('www.wenyuanblog.com', '/');

console.log(uri1);
console.log(uri2);
console.log(uri3);


// 应用二:兼容性检测 每次写监听事件的时候调用 addEvent 函数,都会进行 if...else... 的兼容性判断
// 使用立即执行函数,当我们把这个函数放在文件的头部,就可以先进行执行判断
const addEvent  = (function() {
  if(window.addEventListener) {
    console.log('判断为其它浏览器')
    return function(element, type, listener, useCapture) {
      element.addEventListener(type, function(e) {
        listener.call(element, e);
      }, useCapture);
    }
  } else if(window.attachEvent) {
    console.log('判断为 IE9 以下浏览器')
    return function(element, type, handler) {
      element.attachEvent('on'+type, function(e) {
        handler.call(element, e);
      });
    }
  }
}) ();
// 提前返回

// 测试一下
let div = document.querySelector('div');
let p = document.querySelector('p');
let span = document.querySelector('span');

addEvent(div, 'click', (e) => {console.log('点击了 div');}, true); // 和延迟执行,返回的是一个函数
addEvent(p, 'click', (e) => {console.log('点击了 p');}, true);
addEvent(span, 'click', (e) => {console.log('点击了 span');}, true);

组合函数

// 传入多个函数,自动组合在一起,一起调用
function composeFn(...fns){
    var length = fns.length
    if (fns.length <=0 ) return
    // 边界判断
    for (var fn of fns) {
        if (typeof fn !== 'function'){
            throw new Error('fn must be function')
        }
    }
    return function(...args){
        var result = fns[0].apply(this,args)
        for (var i = 1; i< length; i++) {
            var fn = fns[i]
            result = fn.apply(this,[result])
        }
    }
}

var newFN = composeFn(fn1,fn2,...)

with:不推荐使用

var obj ={
    msg : 'hello'
}

msg = 'aa'

with (obj) {
    console.log(msg)   // hello
}

eval

var evalStr = 'var name="hyf";console.log(name)'
eval(evalStr)
  • eval代码的可读性非常的差
  • eval是一个字符串,那么有可能在执行的过程中被篡改,造成被攻击的风险
  • eval的执行必须经过JavaScript解释器,不能被JavaScript引擎优化

严格模式

"use strict"
/*
1. 不会意外创建全局变量
2. 发现静默错误  Object.defineProperty(obj,'name',{writable:false})
3. 不允许0的八进制语法
4. 不允许使用with
5. eval不再为上层创建变量
6. this不会转化成对象类型   undefined
*/

对象增强

对象属性的控制

/*
属性描述符
Object.defineProperty(obj,prop,descriptor)
*/

属性描述符分类

  • 数据属性描述符

    • [[configurable]]: 表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符

      • 当我们直接在一个对象上定义某个属性时,这个属性的的[[configurable]]为true

      • 当我们通过属性描述符定义一个属性时,这个属性的[[configurable]]默认为false

        var obj = {
          name:'why', [[configurable]]:true
          age:18
        }
        
        Object.defineProperty(obj,'addr',{})  // addr的[[configurable]]:false
        
    • [[Enumberable]]: 表示属性是否可以通过for-in或者Object.keys()返回属性值

      • 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true
      • 当我们通过属性描述符定义一个属性时,这个属性的[[Enumberable]]默认为false
    • [[Writable]]: 是否可以修改属性的值(只读属性)

      • 当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true
      • 当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false
    • [[value]]: 告诉js引擎,返回这个value

  • 存取属性描述符

    • [[configurable]]

    • [[Enumberable]]

    • get: 取值时

    • set:修改值时

      var obj = {
        name:'why',
        age:18
      }
      
      var _name = ''
      Object.defineProperty(obj,'name',{
        configurable:true,
        set:function(value){
          _name = value
          console.log('set:',_name)
        },
        get:function(){
          console.log('get:',_name)
          return _name
        }
      })
      
      obj.name = 33
      console.log(obj.name)
      
      

多个属性描述符

var obj = {
  name:'why',
  age:18
}

Object.defineProperty(obj,{
    name:{
        configurable:false
    },
    age:{
        configurable:false
    }
})

对象方法

  • getOwnPropertyDescriptor:获取属性描述符
  • getOwnPropertyDescriptors:获取多个属性描述符
  • preventExtensions: 阻止对象扩展新属性
  • seal:密封对象,不能配置和删除属性
  • freeze:冻结对象,不能写入

对象方法

  • hasOwnProperty:属性是否属于对象本身
  • in/for in操作符:遍历的是自己对象和原型上的属性
  • instanceof:判断对象与构造函数之间关系的,用于判断构造函数的prototype,是否出现在某个实例对象的原型链上
  • isPrototypeOf:判断对象与对象之间的关系的

原型

ES5-普通对象的原型

每一个对象都有一个[[prototype]]:object,称为对象的原型对象,

原型的作用:当通过[[get]]获取属性值时,先在自己的对象中查找,如果不存在,则会在原型对象中查找,原型对象中没有找到,还会在原型对象的原型对象中查找...直到null

所有函数都有一个prototype属性
  • 对象:非标准obj.__proto__,标准Object.getPrototypeOf(obj)

  • 函数

    • 作为对象:非标准foo.__proto__,标准Object.getPrototypeOf(foo)
    • 作为函数:foo.prototype,显式原型,用来构建对象时,给对象设置隐式原型
  • 函数的原型作用:在通过new操作符创建对象时,将这个显式原型赋值给创建出来的对象的隐式原型,避免实例化时创建多个函数

    // 将方法设置到原型上,实例化对象时,方法函数将不会大量创建,方法函数只有一份
    function Foo(name,age){
        this.name = name
        this.age = age
        /*eating:function(){
            console.log(this.name+'eating')
        }*/
    }
    
    Foo.prototype.eating = function(){
    	console.log(this.name+'eating')
    }
    
    var name1 = new Foo('hyf',18)
    var name2 = new Foo('hyf01',18)
    name1.eating()  // hyfeating
    /*
     , 
     eating函数中的this指向name1,因为,name1.__proto__ === Foo.prototype,
     [eating]先在 name1中找,没有找到,在原型对象name1.__proto__即Foo.prototype中寻找
     name1.eating()中的this,指向调用者本身,即name1
    */
    
    name2.eating()  // hyf01eating
    

显示原型的属性

function Person(){
    
}
Person.prototype.running = function(){}
Person.prototype.msg = 'hello'

// constructor:指向函数
console.log(Person.prototype)  // {msg: 'hello', running: ƒ, constructor: ƒ}
Person.prototype.constructor === Person  // true
Person.name = "Person"    // true
Person.prototype.constructor.name = "Person"  // true

原型重写

// 如果原型对象属性很多,可以进行原型对象重写
Person.prototype = {
    ...
    // 默认没有constructor,最好在defineProperty中定义
    // constructor:Person  
}

// 更加精准的控制
Object.defineProperty(Person.prototype,"constructor",{
    enumerable:false,
    configurable:true,
    writable:false,
    value:Person
})

原型链

var obj ={}
属性的查找顺序是: obj--->obj.__proto__----> obj.__proto__._proto__ ----->null

Object是所有类的父类

面向对象

三大特征

  • 封装:属性和方法封装到一个类中,称为封装的过程

  • 继承:将类中共同部分提取出来,作为一个单独的类,称为父类,子类类继承父类的公用属性或方法

    // 继承方式一:父类的原型直接赋值给子类的原型,缺点:父类与子类共享一个原型对象,修改任何一个,另一个也被修改
    // 继承方式二:创建一个父类的实例,用这个实例对象作为子类的原型对象,缺点:子类在独立属性和代码复用上有分歧
    // 继承方式三(最终方案):借用构造函数,组合继承(借用构造函数+原型链)
    function Person(props){
        this.props = props
    }
    
    var p = new Person('dd')  // 第一次调用
    
    function Student(props,prop1,prop2){
        Person.call(this,props)  // 第二次调用
        // this.props = props
        this.prop1 = prop1
        this.prop2 = prop2
    }
    
  • 多态:不同的数据类型进行同一个操作时,表现出不同的形态,称为多态的表现

    • 必须有继承

    • 必须有父类引用指向子类对象

    • 对于JS来说,处处都是多态

组合继承的缺陷

  • 调用两次父类构造函数:new 、 call

  • 所有子类实例实际上会有两份父类的属性,Stul1中有一份,p对象中也有一份

    组合继承的缺陷

寄生组合继承:

原型链+借用+原型式继承+寄生式函数

/*
满足什么条件:
1. 必须创建一个对象
2. 这个对象的隐式原型必须指向父类的显式原型
3. 将这个对象赋值给子类的显式原型
*/

// 原型式继承
function createObject(o){  // 出现兼容问题时将Object.create替换成createObject
    function F(){}
    F.prototype = o
    return new F()
}

// 将Subtype依赖Supertype,称为寄生式函数
function inherit(Subtype,Supertype){
    Subtype.prototype = Object.create(Supertype.prototype)  // 原型链
    Object.defineProperty(Subtype,'constructor',{
        configurable:true,
        writable:true,
        enumerable:false,
        value:Subtype
    })
    // 类方法的继承
    Object.setPrototypeOf(Subtype,Supertype)
}

function Person(){}

function Student(){}

inherit(Student,Person)

最终方案

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

Person.prototype.running = function(){
    console.log('running')
}

Person.prototype.eating = function(){
    console.log('eating')
}


function Student(name,age,height,sno,score){
    // this.name = name
    // this.age = age
    // this.height = height
    Person.call(this,name,age,height) // 继承Person中的属性  借用
    this.sno = sno
    this.score = score
}

// 使用寄生式函数
inherit(Student,Person)
Student.prototype.studying = function(){
    console.log('studying')
}

Object类是所有类的父类

Object类是所有类的父类

原型

ES5-构造函数的类方法

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

// 实例方法
Person.prototype.running = function(){
    console.log('running')
}

// 函数对象
Person.eating = function(){
    console.log('eating')
}

Person.eating()

ES6

class Person {
	// 当通过new关键字调用一个类时,默认调用class中的constructor方法
    constructor(name,age){
        this.name = name
        this.age = age
    }
    running(){ // 内聚  Person.prototype.running = function(){ console.log('running')}
        console.log('running')
    }
    eating(){
         console.log('eating')
    }
}
var p1 = new Person('hyf01',17)
var p2 = new Person('hyf02',17)

console.log(Person.prototype === p1.__proto__)

// 另一种写法 var Student = class {}, 不常用

class 定义访问器

// 监听访问器方式一:
Object.defineProperty(obj,'name',{
    configurable:true,
    enumberable:true,
    set:function(val){
        
    },
    get:function(){
        
    }
})

// 方式二:
var obj = {
    _name:'',
    set name(){
        this._name = value
    },
    get name(){
        return this._name
    }
    
}

// 类中 下划线属性一般不访问,程序员之间的约定,不是规范约定
class Person(){
    constructor(name,age){
        this._name = name
        this._age = age
    }
    set name(val){
        this._name = value
    }
    get name(){
        return this._name
    }
}
var p1 = new Person()
console.log(p1.name)

应用场景

class Rectangle {
    constructor(x,y,width,height){
        this.x = x
        this.y = y
        this.width = width
        this.height = height
    }
    get position(){
        return {x:this.x,y:this.y}
    }
    get size(){
        return {width:this.width,height:this.height}
    }
}

var r1 = new Rectangle(1,2,3,4)
console.log(r1.position) // {x:1,y:2}

定义静态方法

class Person {
	constructor(){
        
    }
    eating(){ // 实例方法
        
    }
	static random(){  // 类方法,静态方法
        
    }
}

继承 extends

// js只支持单继承
class Animal {
    constructor(name,age){
        this.name = name
        this.age = age
    }
 	eating(){
        console.log(this.name+'is eating')
    }
}

class Dog extends Animal{
    constructor(name,age,home){
        super(name,age)  // super.method() 调用父类方法,super(xx) 调用constructor(),
        
        // 使用this之前必须先调用super
        this.home = home
    }
    running(){
        console.log(this.name+'is running')
    }
}

super用法

  • 在子类的constructor中调用时必须在this.xx之前
  • super(args),调用的是constructor()
  • super.method(), 调用父类的方法

继承内置类

class MyArray extends Array {}

类的混入mixin

function mixinRunner(BaseClass){
    // 返回一个类
    return class extends BaseClass {
        running(){
            console.log('running')
        }
    }
}

class Dog{
    eating(){
        console.log('eating')
    }
}

var NewDog = mixinRunner(Dog)
// 或者 class NewDog extends mixinRuuner(Dog) {}
var dog1 = new NewDog()
dog1.running()
dog1.eating()

对象字面量增强

var name = 'hyf'
var age = 18
var obj = {
    name,
    age,
    running:function(){
        
    },
    clear(){ // 简写
        
    },
    eating:()=>{ // 箭头函数
        
    },
    
    // 计算属性名
    [name+age]:3
    
}

数组和对象的解构赋值

var nums = [1,2,3,4,5,6,7]
var obj = {name:'hyf',age:18}

var [a,b,c,d,e,,g,f=8] = nums
var {name,age} = obj
var {name:newName,age:newAge} = obj
console.log(newName,newAge,name,age) 

// 对象默认值
var {name:newName,age:newAge,bb='dd',...newObj} = obj

防抖和节流

underscore库

  • 防抖:不断延迟执行
  • 节流:一定时间内执行一次

网络请求

SSR和前后端分离

  • SSR(Server side render): 服务端渲染
    • 服务端返回整个页面给浏览器渲染
    • 带宽增加
    • ajax:动态替换页面中展示的数据,无需页面刷新
  • 前后端分离
    +

http

超文本传输协议:HyperText Transfer Protocol,应用层协议

组成

  • 请求
    • 请求行:方法 URI 协议版本
    • 请求头:
      • content-type:请求携带的数据类型
        • application/x-www-form-urlencoded: 表示数据被编码成以'&'分隔的键-值对,同时以’=‘分隔键和值
        • application/json: 表示一个json类型
        • text/plain: 表示文本类型
        • application/xml: 表示xml类型
        • mutipart/form-data: 表示上传文件
      • content-length:文件的大小长度
      • keep-alive: 保持连接
        • http1.0中,服务端和客户端都需要设置connection:keep-alive
        • http1.1中默认connection:keep-alive,Node中默认是5s
      • accept-encoding: 告知服务器,客户端支持的文件压缩格式,如gzip编码,对应.gz文件
      • accept: 告知服务器,客户端可接收文件的格式类型
      • user-agent: 客户端相关的信息
    • 请求体
  • 响应
    • 响应行:协议版本 状态码 状态码短语
      • 常见状态码:
        • 200: 客户端请求成功
        • 201: POST请求,创建新的资源
        • 301: 重定向,请求资源的URL已经修改,响应中会给出新的URL
        • 400: 客户端错误,服务器无法或者不进行处理
        • 401: 未授权错误,必须携带请求的身份信息
        • 403: 客户端没有权限访问,被拒接
        • 404: 服务器找不到请求的资源
        • 500:服务器遇到了不知道如何处理的情况
        • 503:服务器不可用,可能处于维护或者重载状态,暂时无法访问
    • 响应头:
    • 响应体:

版本

  • http/0.9:1991,只支持get请求
  • http/1.0: 1996 ,支持POST、HEAD等请求,不再局限文本数据,但每次都会建立一个TCP连接
  • http/1.1: 1997, 增加PUT、DELETE等请求方法,采用持久连接(Connetion: keep-alive),多个请求可以共用一个TCP连接
  • http/2.0: 2015
  • http/3.0: 2018

请求方式

  • get:用于获取数据
  • head:与get响应相同,但是没有响应体,一般在下载文件前,回去文件的大小
  • post:将实体提交给指定资源
  • put:用请求有效载荷替换目标资源的所有当前表示
  • delete:删除指定资源
  • patch:用于对资源修改
  • connect:通常用在代理服务器
  • trace:沿着到目标资源的路径执行一个消息环回测试

AJAX发送请求

// 1. 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()

// 2. 监听状态的改变
xhr.onreadystatechange = function(){
    if (xhr.readyState !== XMLHttpRequest.DONE) return 
    const resJSON= JSON.parse(xhr.response)
    console.log(resJSON)
}

// 告知xhr返回数据的类型
xhr.responseType = 'json'

// 3. 配置请求open/ 默认异步true
xht.open('POST',"http://xxxx",true)

// 4. 发送请求
xht.send()

事件监听on

  • readystatechange
  • loadstart: 请求开始
  • progress: 一个响应数据包到达,此时整个response body都在response中
  • abort: 调用xhr.abort()取消请求
  • error: 发生连接错误,例如: 域错误
  • load: 请求成功完成
  • timeout: 由于请求超时而取消了该请求(仅发生在设置了timeout的情况下)
  • loadend: 在load,error,timeout或者abort之后触发

state状态

state状态码

响应状态status

  • status
  • statusText

GET/POST请求传递参数

  • get: query ?name=ss&age=11
  • post: xhr.setRequestHeader('Content-Type',xxx)
    • x-www-form-urlendcoded: 放到请求体中;xhr.send('name=ss&age=11')`
    • formdata:new FormData(formEl)
    • json:xhr.send(JSON.stringify(object))

Fetch

// fetch-get请求
fetch('xxx').then(res=>{
    return res.json() // 返回一个Promise
}).then(res=>{
    console.log(res)
}).catch(err=>{
    console.log(err)
})

// post json
async function getInfo(){
    const response = await fetch('xxxx',{
        method:'post',
        headers:{
            'Content-Type':'application/json'
        },
        body:JSON.stringify(data)
    })
    const res = response.json()
    console.log(res)
}


// post x-www-form-urlencoded
async function getInfo(){
    const response = await fetch('xxxx',{
        method:'post',
        headers:{
            'Content-Type':'application/x-www-form-urlencoded'
        },
        body:'name=hh&age=11'
    })
    const res = response.json()
    console.log(res)
}

// formdata
async function getInfo(){
    const formData = new FormData(el)
    formData.append('xx',3)
    const response = await fetch('xxxx',{
        method:'post',
        body:formData
    })
    const res = response.json()
    console.log(res)
}
posted @ 2023-03-25 23:52  转角90  阅读(6)  评论(0编辑  收藏  举报