天天看点

javascript高级进阶

 0,基本数据类型

数组

数组构造器有哪几种?

const arr = Array(6), Array.from,Array.of

Array.of和Array的唯一区别就是单个参数,一个作为长度一个作为元素

Array.from:只要该对象有迭代器,它就能把该对象变成数组(...,arguments,set,string,map) 

哪些是改变自身的方法?  push  pop shift unshift  splice sort  reverse   copyWithin fill

哪些是不改变自身的方法? join  indexOf  lastIndexOf  concat toString includes slice     

遍历的方法有哪些?

map forEach

every  some 

keys  values entries 

find findIndex  

filter  

reduce reduceRight

数据类型判断的几种方式?

typeof(arr)  == "object"

str instanceof String == false

a.constructor === Array

Object.prototype.toString.apply(a) ==='[object Array]'

Array.prototype.isPrototypeOf(a);

Object.getPrototypeOf(a) === Array.prototype

如果浏览器不支持es6中的isArray方法,可以这么写:

const  arr = [];

if(!Array.isArray){

Array.prototype.isArray = function(arg){

 return Object.prototype.toString.call(arg) === '[object Array]'

}

1,闭包有啥用?

闭包就是函数里面套函数,作用就是阻止js的变量被垃圾回收机制自动回收(该作用域执行完毕,作用域内的变量会全部销毁),以下代码要想输出2,3,4,得使用闭包:

function  aaa() {
       var a = 1;
       a++;
       alert(a)
 }
aaa(); // 2
aaa(); // 2
           

闭包方式:

function aaa() {
        var a = 1;
        return function(){
            a++;
            alert(a)
        }
    }
    var bbb = aaa();
    bbb(); // 2
    bbb(); // 3
    bbb(); // 4
           

常见闭包出问题的两种情况:函数作为参数传递,函数作为返回值 

//函数作为参数被传递
function aa(fn){
  const a = 100;
  fn()
}

const a = 200;
function fn(){
console.log(a)
}
           
//函数作为返回值
function aa(){
 const a= 200
return function(){
  console.log(a)
}
}
 const bb = aa();
const a = 100;
bb()
           

两段代码执行结果都是200,究其原因核心还是自由变量寻找方式:

自由变量:在本作用域未定义但是使用的变量,比如以下代码,a在块级作用域未定义但是使用了

let a = 5;
if(true){
  console.log(a)
}
           

 自由变量寻路方式:自由变量首先在自己作用域寻找看是否定义,若未定义则向上一级寻找,直到找到为止。

上面的两个闭包,函数内的自由变量在函数定义的地方找是否定义,若未定义则向函数定义的上一级寻找。注意自由变量是在函数定义的地方查找,而不是函数执行的地方。 

2,this的指向问题?

默认谁直接调用的就指向谁(window调用,对象调用,鼠标事件,没有人调用),刚好和自由变量相反

1,window调用

var name= "edward"
function fn(){
    var name = "zhangsan";
    console.log(this)             //  Window
    console.log(this.name);       //  edward
}
var b = fn(); 
           

2,对象(链式)调用

var person1 = {
        name:'Tom',
        getName(){
            console.log(this.name); // Tom
        }
    };
 var person2 = {
        name:'sam',
        friend:person1
    };
 var person3 = {
        name: 'jam',
        friend:person2
    };
 person3.friend.friend.getName()  //本质上还是 person1 调用的
           

3,鼠标事件(非vue,react)

<script>
function hello(){
console.log(this.innerHTML)
}
</script>
<body>
  <button onClick="hello()">点击</button>
</body>
           

4,没有直接调用者(默认就是window)

<script type="text/javascript">
    var message = "Hello!";
    var app = new Vue({
        el:"#app",
        data:{
            message: "你好!"
        },
        created: function() {
          this.showMessage1(); //this指向vue组件
         },
        methods:{
            showMessage1:function(){
                setTimeout(function() {
                   document.getElementById("id1").innerText = this.message;  //不知道谁调用,this指向window,setTimeout()调用的代码运行在与所在函数完全分离的执行环境上
                }, 10)
            } 
        }
    });
</script>
           

5,在class方法中调用 

6,bind,call, apply三种是传入什么就绑定什么

function fn(){
 console.log(this)
}

fn.call({x:100})   //直接执行,打印{x:100},即this指向{x:100}

const fn2=fn.bind({x:200})  //bind返回一个函数
fn2()   // 打印{x:200} ,即this指向{x:200}
           

fn.bind(thisobj, 参数1,参数2...)   //不执行fn函数,后面的参数在执行fn时作为前几个参数被加t入

fn.call( thisobj,参数1,参数2...)   //执行fn函数

fn.call(thisobj,[参数1,参数2...] )   //执行fn函数

7,箭头函数:永远找它的上一级作用域的this(在箭头函数定义的地方)

在react中改变this的指向3中方式(bind,constructor内bind,箭头函数)

1,使用bind将this指向外部组件内

onClick={this.handleClick1.bind( this )}
           

2,constructor内bind将this指向外部组件

this.handleClick2 = this.handleClick2.bind(this)
           

3,箭头函数内的this都指向定义的地方的上一级

handleClick3 =()=>{
console.log( this )
}
           

手写bind函数:

//bind()函数里有无数个参数,第一个参数表示this要指向的对象,后面所有的参数都是被绑定的函数的参数

Function.prototype.bind1= function(){
  //将参数列表arguments转化为数组
     const  arr = Array.prototype.slice.call(arguments);
//第一个参数表示this指向的对象
     const  t = arr.shift();
//这个this表示 fn1.bind(...)中fn1函数内的this
const self = this;
   //bind会返回一个函数
return function(){
  return  self.apply(t, arr);
}
}
           

3,作用域

全局作用域: 只有html的script标签内编辑的代码或者通过script引入的js文件属于,像vue中import的js文件不属于全局作用域;

全局变量:一定在全局作用域内才可以互相调用,定义的三种方式:

//1,使用var定义的变量,(使用const和let不行 )
var  a= 10; 
 
//2,定义在函数内,未使用关键字,在调用该属性前执行过getnum函数
function getnum(){
a = 10
} 

//3,使用window定义的变量,(假如定义在函数内,在调用该属性前也要提前执行该函数)
window.a = 10

 
           

全局对象(window):全局变量和全局函数成为全局对象的属性

局部作用域:函数内定义的就是局部作用域,局部和全局作用域定义的变量不冲突

var a =1;
function b(){
var a = 2 ; //不报错
}
           

块级作用域实现事件监听:

//当i是全局作用域时, 会出现点击所有的p弹出的都是10
let i,a;
for(i=0;i<10;i++){
    a = document.createElement('p');
    p.innerHTML = i;
    p.addEventListener('click',function(){
           alert(i)
   })
    document.body.appendChild(p)
}


//当i的块级作用域时,点击对应的p会弹出对应的数字
let a;
for(let i=0;i<10;i++){
 ...
}
           

4,vue作用域、模块作用域

全局作用域(应用程序作用域):整个应用

Vue.prototype.$globalValue = 'Global Scope!'; //添加到Vue原型中
export default {
mounted() {
console.log(this.$globalValue);  // 每个组件使用全局变量的方式,$表示变量是全局而不是该组件唯一的
},
};
           

子树作用域 不会

组件作用域 不会

实例作用域 不会

模块作用域:

//方式一
//a.js
window.name = "bob"

//b.js
import 'a.js'
alert(name)   //bob


//方式二
//c.js
window.name="bob"

//b.js
import 'c.js'

//a.js
import  'b.js'
alert(name)   //bob
           

5,对象属性调用

obj.属性名和obj["属性名"]两种调用方式

使用obj["属性名"]的情景:

不知道对象中是否有该属性  

属性名是个数字 :   obj["23"] 

为该obj对象添加新属性:obj["name"] = "bob"

属性名是个变量:obj[ age ]

6,变量函数声明提前

变量声明提前:只有var关键字定义的可以声明提前,无关键字不行

console.log(a)  // 输出undefined
var a = 10;
 
cosole.log(b)  //报错 b is not defined
b = 12;   
           

函数声明提前:只有function定义的函数可以,函数表达式定义的不可以

getname();   //打印bobo
function getname(){
cosole.log('bobo');
}

getage();   //报错 getage is not defined
var getage= function(){
console.log(11)
}
           

7.1,面向对象编程(构造函数、类 、继承、原型,原型链)

面向对象编程(封装,继承,多态)

js是一个多范式编程语言(面向对象编程,面向过程编程)

js可以使用构造函数或class进行面向对象编程

构造函数

//函数中包含this,且被new,就是构造函数
function person(){
var name = "bob";  //var申明的变量仅在当前作用域下可以使用
this.name = 123;   //this声明的变量,指向上下文
}
var  kids = new person();
console.log(kids.name)   // name的上下文是kids,输出123
           

构造函数基本语法

function  Person(){
this.name = 'bob';
this.getname = function(){ }
}
Person.prototype.age = 8;  //原型链增加属性
Person.prototype.getage=function(){ }
 
var kid = new Person();
kid.age = 4; //修改自身的属性,不影响Person

var adult = new Person();
adult._proto_.age = 24;  //修改原型链上的属性,影响Person

console.log(kid.age)  //24

//相当于  adult.__proto__ === Person.prototype
           

class基本语法(本质是: 构造函数加原型链继承的语法糖)

class Person{
    constructor{
      this.name = 'bob'
      this.age = 11
     }

getname(){ }

}
           

构造函数和class对比:

一个:构造函数内部可以用this定义变量,也可以用var let const;而class内部只允许this定义变量

二个:构造函数存在性能问题,他定义的方法在每一个实例中都会创建一遍,存在一定的内存浪费。解决办法:将对象的属性定义在构造函数中,把方法和一些公共属性定义在原型链中

三个:构造函数继承和class继承

//构造函数继承
function Animal() {}
function Dog() {}
Dog.prototype = new Animal() // Dog继承Animal

//class继承
class Dog extends Animal{}
           

new 一个构造函数过程发生了啥?

1,把构造函数的原型(prototype给了新对象)

2,把构造函数的this指向了新对象

3,返回新对象

实现一个new

function new(){
 var obj = {};
 var [constructor,...args] = [...arguments];
  obj.__proto__ = constructor.prototype;
 constructor.apply(obj,...args)
 return obj;
}
           

 现在来使用一下它

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

const result = _new(Person,"嗷大喵");
console.log(result.name) //嗷大喵
           

补充:如果不能理解constructor.apply(obj,...args),可以替换成以下:

Person(...args){

obj.name = name  //相当于把this替换为obj,此时obj就拥有了name属性

}  

 7.2 extends

class People{
 constructor(name){
    this.name=name
}
getName(){  }
}

class Adult extends People{
   constructor(name, age){
        super(name)
        this.age = age
   }
getAge(){}
}

const  someOne = new Adult("鲍勃", 24);
           

7.3 原型

原型分为显式原型(prototype),隐式原型(__proto__)。 

每个class的显式原型 = 每个实例的隐式原型

每个实例在调用属性、函数的时候,先在自身查找,找不到再去__proto__找

javascript高级进阶

7.4 原型链和instanceof 

 在7.2我们知道,someOne是Adult的实例化对象,Adult继承于People,People继承于Function

这就构成了一个条链子:原型链。即

1,someOne.__proto__ = Adult.prototype

2,Adult.prototype.__proto__ = People.prototype

3,People.prototype.__proto__  = Object.prototype

以上的关系可以把Adult.prototype看作People new的一个实例化对象。

下面上图,结构更加清晰 

javascript高级进阶

实例化对象xialuo.eat()首先在自身找eat函数,找不到再去__proto__上找,然后还找不到,继续向上。可以使用hasOwnProperty()判断一个属性是否在自身上: xialuo.hasOwnProperty(eat) === false

其中hasOwnProperty()方法在Object处定义的:

javascript高级进阶

而instanceof,只要被测试的类在实例化对象的原型链上,就能被找到。比如上图,xialuo  instanceof  ( Student,People,Object)都是true,但是xialuo instancof  Array  返回就是false

7.5 Object.create()和new Object的区别?

function Person(){
   this.name = "bob"
}
Person.prototype.getName = function(){
   return 1
}

//new一个Person可以获得name属性和getName方法
//Object.create只能获得getName方法

const a = new Person();
a.name //bob
a.getName  //1

const b = Object.create(Person.prototype)
b.name // undefined
b.getName //1
           

7.5  实现简易的jQuery

class jQuery{
constructor(selector){
    const result = document.queryAllSelector(selector);
    this.length = result.length;
    for(let i=0;i<this.length;i++){
     this[i] = result[i]
    }
}

 //dom操作API
//获取某个元素
get(index){
return this[index]
}

//每个元素都执行某个操作
each(fn0){
for(let i=0;i<this.length;i++){
   fn0(this[i])
}
}

//点击事件
on(action,fn1){
  this.each(elem=>{
    elem.addEventListener(action,fn1)
}
}

const $p = new jQuery('p')
$p.get(2)
$p.each(ele=>{console.log(ele)});
$p.on('click', ()=>{alert('hi')} );
           

对jquery的扩展,有两种方式:插件扩展和自己造轮子

//插件形式(大家还是用的jquery)
jQuery.prototype.dialog = function(){} 

//造轮子(大家用的是我的myJquery)
class myJquery extends jQuery{
 constructor(selector){
    super(selector)
}
addClass(){ }

}
           

8,单线程,多线程

一个程序是一个进程,一个进程里有很多线程;多线程允许同一时间执行多个任务,比如编辑文字的同时检查是否有错别字;单线程同一时间只能执行一个任务;

浏览器和node.js已经支持js启动进程,如web worker,但是不影响js是一个单线程语言。

因为js要操作DOM,所以js引擎和UI渲染(DOM)共用一个线程,所以DOM渲染的时候js必须停止,js执行的时候DOM就不能渲染。

需要用到多线程的情况:

  1. 该任务阻塞用户界面操作。 
  2. 各个任务必须等待外部资源(网络请求)

虽然H5的web worker允许js创建多个线程,但是受两个限制,前端本质上仍然是单线程:

  1. 不能操作DOM
  2. 子线程完全受主线程控制

执行主线程的时候遇到子线程,将子线程放到队列里挂起,等到主线程执行完毕的时候,子线程也返回了结果,这个时候再执行子线程(子线程中微任务优先于宏任务执行) 

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

console.log("hello");  //主线程(同步任务)

setTimeout(function(){}, 100)  //子线程1(异步任务,宏任务)

new Promise(()=>{       //子线程2(异步任务,微任务)
resolve()
}).then()

alert('hi')   //主线程(同步任务)
           

9.1 同步和异步

 因为js和dom共用一个线程,两者不能同时执行任务,假如一个定时器没有执行完毕,DOM就一直卡着,很不合理,所以有了异步。异步并不是说,先执行所有的同步任务,等全部执行完毕,再回过头来调用异步代码,如果是这样的话,只是改变了一下代码的执行顺序,花费的时间仍然是相同的。所以异步其实是在同步任务执行的时候,异步任务另外开一个线程去执行,那么问题来了,这个操作难度不是多线程吗?对的,其实多线程就是实现异步的一种方式,实现异步还有其他种方式,比如异步I/O操作,所以异步包含多线程。

但是又说异步是以callback形式调用的,意思当所有同步任务执行完毕,再回过头来调用异步代码。这其实不矛盾,他的意思是说,执行同步代码的时候另开一个线程执行异步代码,等同步代码全部执行完毕了,来异步这儿看看有没有计算出结果,然后按顺序执行异步里的代码。

可以看见setTimeout, ajax请求的代码形式都是回调(函数作为参数传递)

//定时器
setTimeout(function(){
   console.log('hi')
},1000)

//ajax
$.get(url, function(data){
   console.log(data)
})
           

异步的应用场景除了上面两种还有图片加载:

const img = document.createElement('img')
img.onload = function(){  }
           

两个定时器,第一个定时器1s后执行,第二个定时器0s后执行,答案是先执行第二个

setTimeout(function(){
console.log(1)
},1000)

setTimeout(function(){
console.log(2)
},0)

//结果:2,1
           

事件监听也用到回调:window.on("click",function(){})

9,web socket / web worker / ajax xmlhttprequest / fetch promise  / generator  yield/ async await

友情链接

使用web socket协议代替http协议

使用web worker在后台处理js文件

使用XMLHttpRequest对象向浏览器发送一个ajax请求

使用fetch发送请求接收promise对象结果

使用async 用于申明一个 function 是异步的, await 用于等待一个异步方法执行完成。

fetch解决了ajax的回调地狱,async解决fetch还有点回调的影子,让异步代码写起来像同步一样舒服

10.0 , web  socket 

web socket和http一样,是个通信协议,http只能客户端向服务端发送数据,服务端无法主动向客户端发送数据。假如服务端推动实时的消息,http只能轮询(不停连接,或者根本不断开),效率太低。socket做到服务端主动向客户端发消息。

const sock = new WebSocket("ws://localhost:8888");
sock.onopen=function(e){ }
sock.onmessege = function(e){   JSON.parse(e.data)  }  
//数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)
sock.onclose = function(e){ }
           

10.1,web worker

:html页面是先执行JS然后再渲染DOM页面的,如果js中包含很复杂的计算,会导致页面无法渲染;web worker 能够使得js在后台执行,不影响DOM加载;

下面这段代码在未执行完斐波那契数列值之前,div的点击事件无效,造成了DOM阻塞

<div></div> 
<script> 
  document.querySelector('div').onclick = function() { 
    console.log('hello world'); 
  }; 
 
  function fibonacci(n) { 
    return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2); 
  } 
 
  console.log(fibonacci(36)); 
</script>
           

使用web  worker将斐波那契函数放在后台执行

主线程main.html

<div ></div> 
<script> 
  document.querySelector('div').onclick = function() { 
    console.log('hello world'); 
  }; 
 
  var worker = new Worker('worker.js');  //加载后台js文件,创建一个web worker
  worker.postMessage(36);                 //发送给worker子线程一个消息,告诉它可以执行了
  worker.onmessage = function(event) {     //接收woker子线程运行返回值
    var data = event.data; 
    console.log(data) 
  }; 
 
  worker.onerror = function(event) { 
    console.log(event.filename, event.lineno, event.message); 
  }; 
</script> 
           

子线程worker.js文件

self.onmessage = function(event) { //接收来自主线程的消息
  var data = event.data; 
  var ans = fibonacci(data); 
  this.postMessage(ans);   //向主线程返回结果,推送消息
}; 
 
function fibonacci(n) { 
  return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2); 
} 
           

10.1,XMLHttpRequest

//get请求
var xhr = new XMLHttpRequest();
    //默认是异步请求async:true
xhr.open("GET", "./xhr1Servlet?name=zhangsan&age=23", true);
xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.response);
        }
    }
xhr.send();

//post请求,必须设置请求头
var data = "name=zhangsan&age=23";
var xhr = new XMLHttpRequest();
xhr.open("POST", "./xhr1Servlet", true); //true表示异步请求
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.response);
        }
    }
xhr.send(data);

//xhr1Servlet表示后端处理请求的代码路径
           

readystate

1, 0还未调用send

2,1已经调用send,正在发送请求

3,2 发送完成,已经接收到所有响应内容

4,3正在解析响应内容

5,4响应内容解析完成,可以在客户端调用

status状态码

1,2xxx成功

2,3xxx重定向 

3,4xxx资源不存在

4,5xxx服务端程序bug

XMLHttpRequest的回调地狱

因为上面的代码太烦了,所以封装成一个接口,大致这个意思

function  ajax1(){
  const result = XMLHttpRequest请求
  return result
}
           

 但实际上面的代码由于先return的结果,后执行XML请求,所以不对,应该用回调函数

function ajax2(path, callback){
  xhr.onreadystatechange = function() {
      callback(result);
}

callback0(result){
  return result
}
ajax2(path, callback0);
           

这时候有个问题,我想在得到第一个结果以后发起第二个请求,第二个请求因为计算过程有依赖第一个请求结果,所以必须第一个请求得出结果以后才能发起第二个,就进入了回调地狱

ajax2(path1, 
   ajax2(path2,
       ajax2(path3,
            ajax2(path4,callback)
            .
            .
            .
        )
    )
)
           

10.2 跨域和同源策略

同源策略:当前网页和服务端必须同源(http协议,域名,端口一样)。它仅限于ajax请求,浏览器端。像访问图片,css,js,都是可以跨域的。

//以下都是可以跨域的
//比如使用cdn引用bootstrap,引用jquery,或者网络图片
<img src="https://www.baidu.com/img/tiger.png" />
<link  href = "https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap-grid.css" />
<script src="http://code.jquery.com/jquery-1.4.4.min.js"></script>
           

同源策略是浏览器端设置的条件,服务端没有这个限制,不同源可以互相访问。比如在服务端写一个爬虫访问各个网页资源是允许访问的。或者服务端向网页发起攻击也是可以的。

不同源浏览器直接截获不让发。

实现跨域JSONP

1,实现跨域要经过服务端允许

2,没经过服务端允许就成功跨域的,说明这个浏览器有漏洞

刚才同源策略说过,使用<script>是可以跨域的,jsonp就是基于这个漏洞实现的,不过也是需要后端配合。

原理:使用script实现jsonp,当然script标签可以动态插入:

//前端
<script>
window.callback= function(data){
    console.log(data);
}
</script>
<script src="http://localhost:8080/hello.js?username=xx&date=xxx"></script>

//后端hello.js
callback(){
  {name:xxx}  //后端可以访问全局window.callback函数,然后通过计算传递的参数,去执行这个函数
}

//万一前端callback函数名不叫callback,而是abc,前端可以通过参数的方式告诉后端:
<script src="http://localhost:8080/hello.js?username=xx&date=xxx&callback=abc"></script>
           

平时使用的是,jquery封装以后实现的jsonp:

$.ajax({
url:'https://www.baidu.com",
dataType:'jsonp',  //使用SONP形式调用函数时,例如myurl?callback=?,JQuery将自动替换后一个“?”为正确的函数名,以执行回调函数。
jsonpCallback:'callback',//指定执行的回调名称,如果未定义则回调操作必须在success里
success(data){
console.log(data)
}

}
           

实现跨域cors

主要后端来操作,不关前端的事

后端设置允许去访问它的域名,前端就可以直接使用ajax访问了

javascript高级进阶

11,ajax

发送XMLHttpRequest对象的方式叫做ajax请求,而并不是接口名称叫ajax;jquery在此基础上倒是封装了ajax接口:$.get、$.post、$getJSON、$ajax。具体$.ajax()的参数详情

$.get(
    "submit.aspx",{
        id:     '123',
        name:   'bob',
    },function(data,state){
         console.log(data);
     }
)
           

ajax是基于XMLHttpRequest的,也是存在回调地狱(callback hell)的

$.get(url,function(data1){
  console.log(data1)

    $.get(url+data,function(data2){
           console.log(data2)
                .
                .
                .
            })
})
           

12,fetch

fetch后台处理后会返回一个promise对象,.then后面的操作都是为了处理promise对象的

fetch(url).then(response => response.json())
  .then(data => console.log(data))
           

fetch函数类似这样:

function fetch(url){
    return new Promise((resolve1,reject1)=>{
           const xhr = new XMLHttpRequest();
            xhr.open("GET",url,true);
            xhr.onreadystatechange = function(){
                if(xhr.readystate == 4 && xhr.status ==200){
                    resolve1(xhr.response)  //fetch.then中的data
                }else{
                    reject1("没数据")  //fetch.then中的err
                }
            }
           xhr.send();
        })
}

fetch(url).then((data)=>{}).catch((err)=>{} )
                    
           

 resolve和reject两个回调函数负责修改promise的状态(pending->)以及请求结果,对应的resolve函数return的结果触发.then()函数,并将结果放在.then()参数里,reject携带的结果放在.catch()参数里或者.then()的第二个参数里。

13,promise

分析promise的源码去理解resolve和reject:

// Promise接受一个函数A,函数A表示处理流程
// 函数A接受两个函数参数resolve,reject
// resolve,reject用来改变promise的状态
class Promise{
   constructor(executor) {
        this.state = 'pending'  //状态值
        this.value = undefined	//成功的返回值
        this.reason = undefined //失败的返回值
        // 成功
        let resolve = (value) => {
            if (this.state == 'pending') {
                this.state = 'fullFilled'
                this.value = value
            }
        }
        // 失败
        let reject = (reason) => {
            if (this.state == 'pending') {
                this.state = 'rejected'
                this.reason = reason
            }
        }
        try {
            // 执行函数
            executor(resolve, reject)
        } catch (err) {
            // 失败则直接执行reject函数
            reject(err)
        }
    }
    then(onFullFilled, onRejected) {
        // 状态为fulfuilled,执行onFullFilled,传入成功的值
        if (this.state == 'fullFilled') {
            onFullFilled(this.value)
        }
        // 状态为rejected,执行onRejected,传入失败的值
        if (this.state == 'rejected') {
            onRejected(this.reason)
        }
    } 
}
           

直接处理一个promise对象,promise提供的.then()函数其实是解决回调地狱的根本

new Promise(resolve=>{
  resolve("解决");
}).then( data=>{
  return data  //解决
}).then( data=>{
   return data
})
           

使用promise加载图片信息,.then()接受两种返回值,一个是数据对象,一个是promise实例

function loadImg(url){
return   new Promise((resolve,reject)=>{
        const img = document.createElement('img');
        img.onload=function(){
            resolve(img);
            }
        img.onerror = function(){
            const err= '图片加载错误';
            reject(err);
            }
        img.src = url;
})
}

const src="http://resu.com/tiger.png"

loadImg(src).then(img=>{
     console.log(img.width)
      return img           //返回对象,可以用.then接收
}).then(img=>{
     console.log(img.height)
     return loadImg(src2)   //返回promise实例,也可以用.then接收
}).then(img2=>{
    console.log(img.width)
})
           

其实promise.then()调用多了还是难以分辨,所以又出现了promise.all(read(A),read(B),,,,,)

all和race

promise.all([ P1,P2]).then().catch()  : 多个promise但凡有一个错误的将被catch

promise.race( [P1,P2]).then().catch() :  多个promise,谁先获得结果(错误或成功)就返回谁

14,async  await

友情链接

aysnc用来表明一个函数是异步函数,如果有return一定会返回一个promise对象;await表示等待一个返回值(可以是promise也可以不是),但是await必须放在一个async函数里

async function here(){
//return "hello"   即使return的是字符串,但是加了async前缀,return出去的仍然是个promise
return  Promise.resolve("hello")
}

function go(){
 return "world"
}

async function get(){
    var  a = await here();
    var  b = await go();
}
           

15,模块化

防止命名冲突

各js文件互相依赖,如果放html中需要注意引用顺序

避免将所有功能放在一个js文件中导致,功能高度耦合;方便功能复用

模块化的历史

//1,原始(容易产生命名冲突)
function  a(){}
function  b(){}

//2,使用namespace命名空间(对象管理不够安全)
var  allFun = {
    name:"bob",
    foo(){console.log(this.name)} // 对象内访问属性用this   //相当于foo:function... 也就是allFun.prototype.foo=function....  类似class内定义函数,使用的就是这种方式
}

//3,使用匿名闭包,IIFE模式(立即执行函数)
var  allFun =(function(){
  var name = "safe";
    var a = function(){ }
return {
a:a
}
})()
allFun.name; //undefined
allFun.a  

//4,增强功能,添加依赖
var  allFun =(function($){
  var name = $("body");
    var a = function(){ }
return {
a:a
}
})(Jquery)
           

如果将js放到各个文件,并且使用script标签引用,那么将会发出多个http请求,网页变卡;这个时候就出现了模块化(import,require等)

js出现了很多种模块化规范,包括commonJS,AMD,CMD,UMD,ES6模块

1,commonJS只能用在服务端(标致性语言:module.exports,require,比如webpack)

2,AMD是commonJS的分支,可以用在浏览器端(标志性语言:define,require)

3,CMD是commonJS的分支,可以用在浏览器端(标志性语言:define,require),只是比AMD优雅而已

4,UMD是commonJS+AMD结合,可以用在浏览器和服务器(标志性语言:module.exports,define,require)

16,commonJS

commonJS是为了提供一个类似Python,Ruby和Java语言的标准库,弥补js没有标准库的缺陷。commonJS规范可以开发除了客户端应用以外的其他应用,包括:

服务端的js应用程序(node.js)

命令行工具

桌面图形界面应用程序

commonJS是js最早的模块化规范 

一个文件就是一个模块,拥有单独的作用域;

普通方式定义的变量、函数、对象都属于该模块内;

通过require来加载模块;

通过exports和modul.exports来暴露模块中的内容,两者的关系相当于:var exports = module.exports

用起来是这样:

//使用moudle.exports导出
moudle.exports = {   //moudle-a.js  文件   
a: 1
};

var ma = require('./moudle-a');
var b = ma.a + 2;
module.exports ={
    b: b
};


//使用exports导出
exports.add = function() {   //math.js 文件
 var args = arguments
    return args[0];
};

var add = require('math').add;  
           

它源于服务端,无法直接用于浏览器端。 原因如下:

1. 外层没有function包裹,变量全暴露在全局。如上面例子中increment.js中的add。

2. 资源的加载方式与服务端完全不同。服务端require一个模块,直接就从硬盘或者内存中读取了,消耗的时间可以忽略。而浏览器需要从服务端来下载这个文件,所以require后面的一行代码,需要http资源请求完成才能执行。由于浏览器端是以插入

 17,AMD/require.js

为了使得commonJS能够服务于浏览器端,commonJS分化了三种方式:

制作Modules/Transport规范

制定AMD规范

制定Modules/Wrappings规范(综合以上两个规范)

其中AMD从commonJS中分离,作者亲自实现了符合AMD规范的requirejs,AMD规范包含以下几个内容:

1. 用全局函数define来定义模块,用法为:define(id?, dependencies?, factory);

2. id为模块标识,遵从CommonJS Module Identifiers规范

3. dependencies为依赖的模块数组,在factory中需传入形参与之一一对应

4. 如果dependencies的值中有"require"、"exports"或"module",则与commonjs中的实现保持一致

5. 如果dependencies省略不写,则默认为["require", "exports", "module"],factory中也会默认传入require,exports,module

6. 如果factory为函数,模块对外暴漏API的方法有三种:return任意类型的数据、exports.xxx=xxx、module.exports=xxx

7. 如果factory为对象,则该对象即为模块的返回值

 它写起来是这样的:

//a.js
define(function(){                   
     console.log('a.js执行');
     return {
          hello: function(){ }
     }
});

//b.js
define(function(){
     console.log('b.js执行');
     return {
          hello: function(){}
     }
});

//main.js
require(['a', 'b'], function(a, b){
     console.log('main.js执行');
     a.hello();
})
           

AMD的问题:

1,require模块的时候,把所有依赖的模块代码都执行了一遍

2,require模块的时候,function里形参还要再写一遍,很烦哎

解决方式:AMD保留了commonjs中的require、exprots、module这三个功能 。所以可以不把依赖罗列在dependencies数组中,而是在代码中用require来引入,这样就不会预先执行所有的依赖代码了:

define(function(){
     $('#b').click(function(){
          require(['b'], function(b){
               b.hello();
          });
     });
});
           

18,CMD/sea.js

AMD有很多不够优雅的地方,所以CMD问世了。还记得commonJS分化为三个规范流派吗?CMD全面拥抱第三个规范Modules/Wrappings,写法如下:

// moudle-b.js
define(function(require, exports, module) {
    var ma = require('./moudle-a');
    module.exports = { 
        b: 1 
    };
});
           

19,UMD

commonJS作用于服务端,AMD/CMD作用于浏览器端,UMD的作用就是解决跨平台。UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

代码看起来是这样的:

(function (window, factory) {
    if (typeof exports === 'object') {
     
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
     
        define(factory);
    } else {
     
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});
           

20,ES 模块(ES6 Module)

commonJS是在js运行的服务端的模块化,AMD是js运行在浏览器端,UMD是js运行在服务端或者浏览器端,它们都是在根据环境进行模块化规范定制;而es6终于从语言层面上定义模块化,这样只要是js无论运行在哪个端都无所谓了:

//module-a.js
export default {
    a: 1
}

//module-b.js
import { a } from './module-a'
export const b = a + 2
           

浏览器里使用该模块,在 

script

标签上加上

type="module"

,表明引入的是 ES 模块。在 Node.js 环境中使用时,把扩展名改成.mjs

一旦import一个模块,会立刻执行模块文件代码,例如以下代码,文件b.js在引入a.js后会立即alert一个hi

//文件a.js
alert('hi');
export const a = 1;

//文件b.js
import 'a.js';
           

21,ES 动态模块(ECMAScript 2020)

2020 年最新的 ESCMA 标准11版中引入了内置的 

import

函数,用于动态加载 ES 模块。

import

函数返回一个 

Promise

,在它的

then

回调里使用加载后的模块:

import("./esCounterModule.js").then(({ increase, reset }) => {
    increase();
    reset();
});
           

22,模块打包工具

为什么需要模块化打包工具?

1,并不是所有浏览器都支持ES模块的,打包可以将模块化语言解释成浏览器识别的语言

2,模块化产生的http请求过多,打包可减少请求次数

四款前端主流的打包工具:grunt , gulp,webpack, rollup。

23,js性能优化(高性能javascript)

一篇很棒的文章:链接

文章总结:

1,http方面

    减少http请求数量(合并js、css、png;雪碧图(紧凑点);使用get而不是post)

    缩短请求距离(cdn)

    减小带宽(cookie;Gzip压缩;压缩js和css的代码(空格,注释);工具压缩图片;减少dom元素数量table;)

    增大并发(多域名)

2,缓存方面

本地缓存(expires,cache-control响应头;Etags;使用外部js和css而不是内联)

3,其他

     文件加载顺序(css放页面顶部,js放在页面底部(defer);图片懒加载)

     高效的事件处理(DOMContentLoaded,事件委托)

     减少重排(img不要设置宽高,documentFragment,className确定样式,js少控制布局)

4,js方面

    数据存取方式

    算法(for in循环最慢;递归最好保存上次计算结果)

    web worker多线程

    将耗时长的JS运算放到setTImeout宏任务中,让出主线程,加快UI响应

script标签

假HTML是按代码顺序加载的,将script标签放在body里,可以先加载出来页面,再执行js动画交互

数据存取

    函数在自己作用域”找“某个变量时,总是先找最近的,没有的话再往远了(上一级作用域)”找“,js会随着”找“的深度增加,性能降低;所以将比较深的变量赋值给局部变量,在函数中直接调用局部变量能提高性能。

var go = 12;
function getGo(){
 let a = go;
 let b = a*2;
 let c = a+4;
 let d = a/3;
return b+c+d;
}
           

   2.1 对象方法的调用也是,首先从对象实例中查找该方法,如果没有,则会沿着原型链由近到远的查找 ;

   2.2 类似window.location.href的嵌套成员也会花销性能,如果多次用到该嵌套成员,可以将其赋值给一个变量,在执行完毕后利用

cacheObj = null

的方式释放缓存

// bad
document.querySelector('.xxx').style.margin = 10 + 'px'
document.querySelector('.xxx').style.padding = 10 + 'px'
document.querySelector('.xxx').style.color = 'pink'

// good
let xxxStyle = document.querySelector('.xxx').style
xxxStyle.margin = 10 + 'px'
xxxStyle.padding = 10 + 'px'
xxxStyle.color = 'pink'
xxxStyle = null
           

重绘和重排

重排(排列),重绘(颜色),重排非常消耗性能,因为布局改变是整体重新加载,而重绘只是局部颜色等的外观重新渲染,不会整体重新加载

引起重排的点

1,元素大小改变

2,元素位置改变

3,添加删除DOM元素

4,浏览器窗口大小改变

减少重排的操作

将多次改变样式属性的操作合并成一次操作,比如(批量添加DOM,多次改变样式),可以先让元素脱离文档流(可以将position属性设为absolute或fixed,或者display设置为none),操作完后再带入文档流

 事件绑定

假如有很多个按钮,都绑定了事件,那么可以利用事件冒泡,将事件委托给父节点或者祖先节点去做,减少DOM事件绑定

<div id="father">
    <button id="btn1"></button>
    <button id="btn1"></button>
    <button id="btn1"></button>
</div>

<script>
//本来是这样
btn1.onclick = function(){
			alert("btn1");
		}
		btn2.onclick = function(){
			alert("btn2");
		}
		btn3.onclick = function(){
			alert("btn3");
		}

//通过事件委托,减少事件绑定个数
father.onClick=function(event){
  switch (event.target.id) {
				case "btn1":
					alert("btn1");
					break;
				case "btn2":
					alert("btn2");
					break;
				case "btn3":
					alert("btn3");
					break;
			}
    }
</script>
           

24,设计模式(不太会,需要再学)

设计模式是一种开发代码的思想经验,与哪种语言无关,目的是为了构建更好的项目结构,使得系统代码可复用,可扩展,可解耦,可理解。就比如说vue的设计模式MVVM就是一种设计思想的体现

设计原则 

1,开闭原则(可以做拓展,但是不做修改)

2,里氏转换原则(子类继承父类,单独调用子类完全可以运行)

3,依赖倒转原则(依赖一个对象,如果这个对象有更底层的类型,直接引用底层)

4,接口隔离原则(每个接口都有自己的角色,不可能一个接口干很多事)

5,合成/聚合复用原则 (新的对象应该使用已有的对象,使之成为新对象的一部分)

6,迪米特原则(一个对象应该对其他对象有尽可能少的了解,

发布-订阅者模式

概念:当一个变量变化时,通知所有监听该变量的函数去执行某些操作。

使用场景:具体像vue的双向数据绑定原理就包含发布订阅者模式,当data中某个数据改变时,所有引用data中该值的地方都会发生响应。

实现的核心思想:

1.  应该实现一个对象,该对象包含两个函数,一个函数用来订阅消息,另一个函数用来发布消息。

2. 订阅函数包含两个参数,第一个参数表示订阅的变量名,第二个参数表示订阅的变量变化时引起的响应函数。发布函数包含两个参数,第一个参数表示发布的变量名,第二个参数表示发布的变量值。 

3.  订阅函数主要向一个数组中push第二个参数;发布函数主要遍历这个数组。

实现的代码:

const observer = (function(){
   const dingyue = [];
return{
 register:function(att, fn){
  if(!dingyue[att].length){
    dingyue[att] = [fn]
    }else{
    dingyue[att].push(fn)
   }
},
publisher:function(att,val){
    if(!dingyue[att].length) return;
    dingyue[att].map(reg=>{
      reg.call(this,val)
  }) 
 }
}    
})()


//现在订阅一个属性 test
observer.register('test', function(e){
 console.log(e.name)
})

//现在发布一个消息
observer.publisher('test',{name:"bob'})  //"bob"
           

25 ,typescript

js是一个弱类型语言,很多变量定义不会报错,不方便后期检查代码,不够严格,Typescript是js的超集(js是ts的子集),是它的增强,ts和es相比增加的内容:

1,数据类型拓展

2,面向对象能力增强

 数据类型拓展

除了js原始数据类型(boolean、number、string、null、undifined、void、以及 ES6 中的新类型 Symbol 和 BigInt),对象类型(js内置对象:Array、Function、Object、Promise、Boolean、String、Number、Math、Error、Date...... DOM、BOM对象:Document、HTMLElement、Event、NodeList),ts还增加了any类型,联合类型

//ts声明变量为联合类型(要么是string要么是number)
let  name:string | number ;
name = "bob"

// 变量可以是DOM元素类型
let body: HTMLElement = document.body
           

面向对象能力增强

js中面向对象只提供了class类,并不包括类的修饰以及接口概念,ts则增加了public,pravite,protected,readonly,abstract修饰符,以及接口定义:

interface  Person{
  readonly name:string,
  getAge: ()=> string
}

let child:Person = {
   name:'bob',
   getAge:():string=>{  return 'hi'  }
}
           

如何使用typescript?

1, vue-cli构建的项目,初始化时直接勾选typescript以及babel即可

2,如果不是以上情况,需要安装工具,npm install -g typescript,以及运行ts文件命令:tsc  hello.ts

ts中判断对象属性是否存在,存在即执行:hook?.prepatch?.name 

26,引用

数组和对象的赋值都是引用,指向的是同一内存空间

//案例一:
const  arr = [1 ,2,3,4]
const  brr = arr;
brr.push(5)
console.log(arr)  // 1,2,3,4,5

//案例二:
const  arr = [1,2,3]
const brr = [];
arr.push(5)
brr.push(arr)  //理论上此时 brr = [1,2,3,5]
arr.pop()   //结果此时 brr = [1,2,3]
           

 27,数据的存储方式

所有编程语言都是根据cpu和内存计算的耗时来去分配空间,值类型都存储于栈中

javascript高级进阶
javascript高级进阶

但是栈中的内存地址指向的内容放在堆中,因为对象和数组可能会很大,放在栈中去计算耗时。

javascript高级进阶

28,值类型和引用类型

值类型:undefined,string,number,boolean,Symbol

引用类型:object,array,null(null是特殊的引用类型,指针指向空地址),函数(特殊引用类型,不用于存储数据,没有拷贝,复制函数的说法)

29,typeof和深拷贝

typeof能判断的

1,全部的值类型

2,引用类型全部判断为(‘object')

3,函数判断为 'function'

typeof弊端:typeof  null;结果是object 

浅拷贝:直接拷贝对象的地址

深拷贝:遍历对象的属性,二者地址没有关系

深拷贝代码:

const  obj = {
 name:"bob",
 address:{
   city:'anhui'
}
}

function deepClone(obj = { }){
//判断是否为object
if(typeof obj !== 'object' || obj == null){
     return obj
}

//判断是数组还是对象
let result;
if(obj instanceof Array){
   result = []
}else{
   result = { }
}

for(let key in obj){
 //保证key不是原型上的属性
if(obj.hasOwnProperty(key)){
   result[key] = deepClone(obj[key])
}

return result
}

const a = deepClone(obj)
           

浅拷贝有哪些方法:Object.assign(),slice,解构赋值

instanceof

instanceof弊端:字符串  instanceof String  //居然是false

30,类型转换

发生类型转换的三种情况:

字符串拼接

==运算符

javascript高级进阶

31,==与===

===相比==多了一个类型判断

何时使用==:除了null以外全都用===

const obj = { name:"bob" }
if(obj.a == null){}
//相当于
if( obj.a === null || obj.a === undefined )
           

falsely和truly变量,falsely变量就是!!a === false,falsely变量以下这些类型:

0,'',NaN,null,undefined,false

if语句其实判断的是truly和falsely变量

32,js web API

js语法规范由ECMA规定

js web API、html、css、网络请求(ajax)、web存储 规范由W3C规定 

js web API包括以下内容:

1,DOM

2,BOM

3,事件绑定

4,AJAX

5,存储

32.1 DOM 

DOM本质

HTML是一个文件,而DOM是浏览器从HTML解析出来的一棵树,,DOM本质是一棵树。

Property:以对象调用属性的方式操作DOM

const divv = document.getElementById("pin")
console.log(divv.nodeName)  //div
console.log(divv.nodeType) // 1
divv.style.width
divv.style.color
           

attributes:向dom元素中添加新的属性(会对标签产生影像)

divv.setAttribute("age",12)
divv.getAttribute("age")    // 12
           

建议使用property,因为property不一定会重新渲染,但是使用atributes一定会重新渲染

DOM结构操作

插入节点,移动节点 

//插入略
//移动节点
const  p = document.getElementById("pis")   //本来在div2里
const div = document.getElementById("div1")
div.appendChild(p)
           

 获取父元素,子元素列表

//获取父元素
domp.parentNode

//获取子元素
const childs = domp.childNodes  //获取的是包含文本以及节点的混合

//过滤节点子元素(不要文本,只要dom节点)
const result = Array.prototype.slice.call(childs).filter(child=>{
    if(child.nodeType===1){  //dom节点的nodeType都是1
        return true
    }
    return false

})

//删除子元素
domp.removeChild( result[0]);
           

DOM性能优化

针对DOM多次查询,做缓存

for(let i = 0;i<document.getElementsByTagName('li').length;i++){}

//修改为
const li = document.getElementsByTagName('li')
const len = li.length;
for(let i = 0;i<len;i++){}
           

针对多次向DOM中插入元素,可以一次性插入

//先创建一个DOM片段,用于缓存所有的插入片段
const  frag = document.createDocumentFragment();

for(let i=0;i<100;i++){
   const li = document.createElement('li');
li.innerHTML = "假如再也不见,希望你早安晚安"+i
frag.appendChild( li)
}

document.appendChild(frag)
           

32.2 BOM

BOM分为几个重点:

1,navigator(浏览器信息)

2,screen(屏幕信息)

3,location(地址信息,url)

4,history(前进后退)

navigator

查看浏览器的类型

const isChrome = navigator.userAgent;
           
javascript高级进阶

 location

location.herf
//拆解url
location.host
location.search  (?后面参数)
location.hash  (#后面参数)
location.pathname (域名后面的文件路径,不包括参数)
//跳转
location.forward()
location.back()
           

32.3 事件绑定

如果页面上有无数个button绑定了点击事件,为了提升性能,需要用到事件委托(也叫事件代理)

<div id="content">
   <button>点我1</button>
   <button>点我2</button>
   <button>点我3</button>
   <button>点我4</button>
</div>
           

事件冒泡是,如果给子元素绑定了事件,父元素也能监听到。

事件委托是相反的,如果给父元素绑定了事件,那么它的子元素都能监听:

body.addEventListener('click', function(e)=>{
      const dom = e.target;
      if(dom.nodeName==="button"){ console.log(dom.innerHTML) }
})
           

不能滥用事件代理,一般用在瀑布流,或者有大量的标签、按钮需要绑定事件

写一个通用的事件监听函数(意思同时支持事件代理和普通绑定 )

function eventAgent(agent,type,aim,fn){
//首先根据参数个数判断是事件代理还是普通事件绑定
if(fn==null){
    fn = aim;
    aim = null
}

agent.addEventListener(type, function(event){
    const target = event.target;
    if(aim){
          if(target.matches(aim)){ //target是一个dom元素,aim是dom的名称,判断是否相符
            fn.call(target,event) //表示将this绑定到target上,同时传递一个event参数
        }
    }else{
          fn.call(tar,event)
    }
})
}

eventAgent('div','click',function(e){
 e.preventDefault();
 console.log( this.innerHTML )  //此this指向target
})
           

33,存储

浏览器存储一般有三个

1,cookie

2,session

3,localStorage

4,sessionStorage

严格上来讲,cookie属于http网络请求的一部分,localStorage和sessionStorage才是真正的 存储,但是由于早期没有这两个东西,是H5以后才出来的,所以早期是cookie被借用实现存储的。

cookie

cookie是由服务端生成,发送并存储在客户端。主要用于本地客户端和server端进行通讯,在http请求中常用的有get和post请求,get参数会放在url地址里,post形式的数据会放在请求体里,而cookie是放在header请求头里。在XHR发送send时会自动发送cookie,无法手动发送。而fetch不会自动发送。

javascript高级进阶

 请求头和请求体可以用这样的语言描述:请求头表示你是谁,请求体表示你要发什么数据。

所以cookie携带的是用户身份信息放在请求头,而post请求发送的数据则放在请求体。而get请求由于参数放在url中,所以数据是在请求行中携带的。

cookie可以在浏览器端添加键值对信息:

document.cookie="a=100"
document.cookie="b=102"
document.cookie //  "a=100;b=102"   ,它是一个追加的过程
           

cookie能够做本地存储是因为一旦赋值,不手动清除,浏览器将一直保存这个值(即使页面重新刷新)。但是cookie只允许存4kb。而且每次发送http都要带上cookie,所以慢。

服务端生成cookie的方式(java):

Cookie cookie = new Cookie("username", "Jovan");
           

cookie的用途:比如登录了某个网站,勾选下次自动登录。在勾选自动登录后,将用户名和密码使用MD5加密放在cookie中存在浏览器,再次登录的时候,浏览器端发起XHR请求,将cookie放在请求头中,服务端接收到请求头中的cookie,与数据库中的账户密码对比返回结果给浏览器。

session

session存储在服务端,

H5存储(WebStorage)

不会随着http发送,且能存5M,API也简单。localStorage在本地永久存储,sessionStorage页面关闭就没了(页面刷新还在)。

localStorage.setItem('bob',12)
localStorage.getItem('bob')
sessionStorage.setItem('amy',10)
sessionStorage.getItem('amy')
           

webStorage是H5新增的对象,可以直接在客户端设置值获取值,不需要服务端生成。

sessionStorage和localhostStorage的应用场景:

假如下次打开这个页面,首先查看webstorage中是否存储有用户名和密码,如果有,取出来作为用户名和密码利用http发送给后端,这个取的过程需要前端去处理。cookie则是后端来处理用户名和密码。

四个的比较

存储:cookie,localStorage,sessionStorage存储在客户端,容易被篡改;session存储在服务器;

发送:cookie会自动加载到http请求头中发送,其他不会。

容量:cookie 4kb

存在时长:cookie可以设置存在时长。sessionStorage网页关闭就没了;localStorage永不过期

跨域:cookie,localstorage同一个浏览器,同一个域名,不同页面都相同;session同一个域名,不同页面都相同;sessionStorage同一个浏览器,同一个域名,同一个页面相同。

同一个域名不同页面的意思:

http://www.baidu.com/index.html和

http://www.baidu.com/app.html就是同一个域名不同页面

javascript高级进阶

34, TCP\IP、HTTP、websocket

TCP/IP四层模型

1,网络接口层(物理层,数据链路层)

2,网络层

3,传输层

4,应用层(会话层,表示层,应用层)

http组成

发送一个http报文包括请求行、请求头、空行和请求体:

javascript高级进阶

相对应的 http响应报文包括状态行,响应头和响应正文

requset和response包括的参数:HTTP 常用 Header_荒岛码农-CSDN博客

一个header的例子:

GET /home.html HTTP/1.1   //请求行
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/testpage.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
Cache-Control: max-age=0
           

 可以打开控制台查看network,headers各项数据。

常见的request的header:

Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 Cookie: $Version=1; Skin=new;
Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close

常见的response的header:

Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8
Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD
Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1

 使用fetch发送的一个请求:

fetch(url:{
  header:{
Accept: text/plain, text/html
  }
}).then();
           

http请求包含8种:post,get,put ,delete, head,connect,options,trace

get: 只请求数据

post:插入新数据

put:更新数据

delete:删除数据

head:获取报头

connect: 管道

options:查看服务器性能

trace:回显

 get和post的区别:

javascript高级进阶

补充:

1,参数一个在url上,一个在请求体里

2,get一般用于 获取数据,post一般用于新增数据

http各个版本的发展历程

http/0.9:支持get请求、html文件

http/1.0:支持get,post,head、支持各类文件、支持cache、各种请求头

http/1.1:新增put,delete,options,patch、管道机制(并发)、、、

http/2.0:服务端并发处理、服务端数据推送

websocket

在http/1.0提供的请求头connection:keep-alive可以实现长连接,那么websocket与之对比呢?

区别①:keep-alive提供的长连接是一种伪长连接,因为虽然只进行一次TCP链接,但是每次发送http请求还是要携带请求header,而websocket是真正的长连接,不需要发送请求头,直接发数据体。

区别②:http协议只能客户端主动向服务端发消息,websoket最大的特点就是服务端能主动向客户端发消息了。 

35,开发环境

1,git

2,调试工具

3,抓包

4,webpack babel

5,linux开发命令

35.1 git

1, git  checkout  文件名;   //将该文件还原,加点还原所有文件

2,git branch  创建分支

3,git checkout -b 切换分支

4,git merge合并分支

5,git diff 文件名;//查看该文件修改的内容

没做任何git操作,恢复修改的文件:git checkout -- aaa.txt   
执行了git add,恢复文件:git reset HEAD,git checkout -- aa.txt  
执行了git add以及git commit ,恢复文件:git reset HEAD^,git checkout -- aa.txt
           

 36,页面渲染流程

总体分为以下步骤:参考

1,DNS解析

2,TCP连接

3,HTTP解析

4,浏览器render页面

 DNS解析

将url根据本地域名服务器查找对应IP

TCP连接

根据dns解析返回的IP,浏览器和该IP建立TCP连接。TCP三次握手:客户端和服务端建立连接需要发送3个包;TCP四次挥手:客户端和服务端断开连接需要发送四个包,两者都可以主动发起断开连接,比如socket,二者都可以执行close结束连接。

HTTP解析

浏览器和服务端建立连接后,使用http协议发送数据。

http的method:get,post,put,delete

http的header:分为请求头响应头,常见请求头(accept,accept-ecoding,accept-language,cookie),常见响应头(content-encoding,content-type)

浏览器渲染页面

浏览器组件构成

  1. User Interface: UI组件包括地址栏,前进/后退按钮,书签菜单等。
  2. Browser Engine: 在UI组件和渲染引擎间采取一些action.
  3. Rendering engine :  解析HTML,CSS等,并将解析的内容显示在屏幕上。
   不同的浏览器使用不同的渲染引擎:
  • IE使用Trident
  • Firefox使用Gecko
  • Safari使用WebKit
  • Chrome和Opera(版本15开始)使用Blink。它是基于Webkit开发的。

4.    Networking: 负责网络调用,例如HTTP请求。在不同的平台有不同的实

5.    UI backend: 主要用来绘画基本的UI元素,例如下拉框,Windows等。这个UI后台暴露一些通用的接口,并不依赖平台的。

6.    JavaScript interpreter. 用来解析和运行JavaScript code。

7.    Data storage. 数据持久化的那一层。浏览器可能需要存储各种各样的数据,例如Cookie。浏览器也得支持我们常用的LocalStorage, IndexedDB,WebSQL以及FileSystem。

渲染流程:首先渲染引擎解析HTML文件,得到一个DOM树,接着解析css文件,与DOM结合生成渲染树, 接着渲染引擎进入布局程序(进入重绘回流阶段),给每个DOM元素安排坐标放到屏幕上。

 由于浏览器是一边请求html一边解析,而不是一次性请求完毕再解析,所以在渲染引擎工作的同时,netWorking组件在下载文件。

渲染引擎和js引擎都是单线程,并且共用一个线程。

网络组件是多线程,一般有2-6个线程

如何加速页面渲染过程?可以开启GPU加速,在重绘回流阶段使用GPU渲染      

37,浏览器

   上面提到浏览器有很多组件,那么每个组件都要执行自己的任务,这些代码的分工又可以分为很多进程,浏览器包括以下进程:

1,浏览器进程

2,渲染进程

3,网络进程

4,GPU进程

.。。。。。

 每个tab标签页是一个独立的渲染进程,互相之间不影响。

渲染进程包括:

1,GUI渲染线程

2,js线程

3,http请求线程

4,定时器触发线程

5,事件触发线程

js引擎只有一个线程,worker和异步都是js请求浏览器新开的一个线程去实现多线程的。 

事件驱动

 浏览器中促使程序执行分为两种情况,分别是事件驱动和数据驱动。

事件循环

所有的同步代码放到栈里,当遇到异步任务,就放到另一个线程里执行,当栈里所有同步代码执行完毕,就去任务队列里取异步任务的回调,加入执行栈继续执行,遇到异步的任务再交给其他线程,以此循环直到执行完毕代码

javascript高级进阶

 任务队列分为宏任务和微任务,执行栈的东西清空了,首先检查(清空)微任务队列,然后才是宏任务。微任务只有一个任务队列,宏任务可能有多个任务队列。

宏任务:定时器,请求任务,点击事件,键盘事件(setTimeout,setInterval,setImmediate)

微任务:promise.then(),promise.catch(),process.nextTick()

setTimeout就是由浏览器的定时器线程来处理,ajax由于需要发送请求,所以是交给HTTP线程处理;而promise.then()不会被交给其他线程,甚至严格意义上讲不是异步也不是多线程,它其实相当于调换了代码的位置,将微任务代码放到同步代码的最后面执行而已。

javascript高级进阶

需要注意的是:setTimeout时间是0的时候,js默认给的是4毫秒,不是真正的0,所以遇到微任务还是先执行微任务。

定时器失效:因为定时器在交给另一个线程的时候开始计时,如果计时器已经被放到任务队列,执行栈还没清空,没法执行定时器的回调,就会产生延时。同步代码或者微任务堵塞都会造成影响

微任务之间也有执行的先后之分:

script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering 

38,迭代器,生成器,装饰器

迭代器iterator(es5)

迭代器的作用:用来优化for循环。for循环会使用一个变量i追踪数据的位置,嵌套多的话很容易乱

1,为不同的数据结构提供统一的访问接口

2,使数据结构的成员能够按照一定次序访问

3,主要为for-of提供服务

 一般数组使用下标获取元素,set使用循环/转为数组获取元素,map使用key获取元素;为了统一这三个数据结构访问的方式,出现了iterator。

内部实现迭代器的可迭代对象有:string,array,set,map,类数组(nodelist,arguments),解构赋值

用到迭代器的地方:Set集合、Map集合、for-of、展开运算符(...)、异步编程

数组迭代器实现源码:闭包

function iterator( item ){    
  var i = 0;
return {
    next:function(){
            const done = (i>=item.length);
            const value = !done?item[i++]:undefined;
            return {
                done:done,
                value:value
                };
          }
     }
}

const results = iterator([1,2,3]);
results.next();  //1
results.next();  //2
results.next(); //3
           

数组的原型链__proto__上包含迭代器方法,所以数组使用迭代器的方式:

const  arr = [1,2,3]
const itera = arr[Symbol.iterator]();  //表示调用数组的Symbol属性下的iterator方法
itera.next() ; //1
           

 Map的原型__proto__上包含迭代器方法,所以Map使用迭代器的方式和数组一样;

除了数组,Map,以下这些原型中也都同样定义了迭代器,可以直接调用:

1,Array

2,Map

3,Set

4,String

5,函数的arguments对象

6,TypeArray

7,NodeList对象

 iterator主要是为for of服务的,以上包含iterator的都可以使用for of。

for of相当于内部直接调用了iterator的next(),并且把value返回给item

const arr = ['a', 'b', 'c']
for(item of arr){
console.log(item) //a,b,c
}
           

生成器generator(es6)

 生成器作用: 返回一个迭代器

yield:属于es6新特性,每执行到yield就会停止执行接下来的代码。只能在生成器*中使用

生成器代码:

function *Generator(){
    console.log('hi');
    yield "name";
    yield 2;
    console.log('hello');
    yield 3;
}
const iterator = Generator();
iterator.next(); //hi 1
iterator.next(); //2
iterator.next(); //hello 3
           

装饰器decorator(es7)

装饰器的作用: 为对象(类)添加额外的属性方法功能,相比于继承更加灵活。

1,是继承的一种代替

2,给动态的类添加额外的 功能

3,在不 改变接口的情况下,增强类的性能

 示例,给类person添加一个静态属性name。只要在被添加类的上头添加一行@函数名

//test.js
@addName
class Person{}

function addName(target){
targer.name = "bob"
}

const adult = new Person();
console.log(adult.name);
           

但是装饰器在es7语法中只是提案,还没有实现,所以需要一些依赖去解析:

1,@babel/core

2,@babel/cli

3,@babel/plugin-proposal-decorators

4,@babel/preset-env

需要创建 .babelrc文件并配置:

javascript高级进阶

 然后将test.js文件编译成es5:执行文件babelrc.cmd

npx babel test.js --dir-out lib
           

 再运行生成的lib/test.js文件:node ./lib/test.js   //打印 “bob"

39,继承 

继承的7种方式:

方式 一:原型链。优点(简单),缺点(不可传参,子类共用父类引用型变量)

function F(){ }
function G(){ }
G.prototype = new F();
           

 方式二:构造函数。优点(可传参,子类有自己的引用型属性),缺点( 无父类prototype)

function Parent(val){}
function child(val){
    Parent.call(this,val);  //相当于执行Parent函数,执行this.name = 'baba'这句代码
}
           

方法三:组合继承(常用)。优点(可传参,属性独有,有父类prototype),缺点(执行两遍父类构造器)

function Parent(){}   
function Child(){ 
    Parent.call(this); //child拥有constructor
}
Child.prototype = new Parent();   //child拥有constructor和prototype
Child.prototype.constructor = Child;   //相当于new Parent().constructor = Child
           

 方式四:寄生组合继承。优点(可传参,属性独有,有父类prototype,执行一次父类构造器)

function A(){}
function B(){
A.call(this)
}
B.prototype = Object.create(A.prototype)
           

40,安全

xss

41,http缓存策略

缓存分为浏览器缓存,http缓存 ,应用程序缓存。

javascript高级进阶

1,http为了避免曾经请求过的资源再次重新请求,给服务器造成无谓的压力,产生了http缓存策略

第一次访问某个资源:客户端直接到浏览器的缓存仓库里拿资源,结果发现没有,就去问服务器要资源,服务器把数据给客户端并把数据写入浏览器缓存仓库

javascript高级进阶

 强缓存:那去缓存仓库拿资源的时候怎么判断有没有资源?这个时候缓存仓库保存了上次的资源并返回给客户端(也是有响应头、数据....),这个返回的头中expires字段规定了这个资源过期的时间:

javascript高级进阶

,以及cache-control字段:

javascript高级进阶

,怎么看出来这个响应是从缓存仓库返回的呢?来看响应的状态码后缀:

javascript高级进阶

,可以看出包含了from memory cache提示。

 协商缓存(对比缓存):其实客户端每次都先去缓存仓库拿资源,但是有时候响应头expires或者cache-control字段显示资源已经过期了,这个时候就会进入协商缓存。

       判断缓存仓库返回的响应头中是否存在etag(etag用来判断资源(内容)是否修改过)字段,如果存在则发起http请求,请求头的If-None-Match 字段=缓存仓库的etag,服务器接收到请求中的该字段进行判断资源是否更改,如果没更改,则返回状态码304,客户端此时将依然从缓存仓库中读取数据,如果发现更改了,则返回状态码200,并返回最新的数据,更新缓存仓库。

     如果响应头中不存在etag字段,则判断头中是否存在last-modified,如果存在则发起http请求,请求头的if-modified-Since字段 = 缓存仓库中的last-modified值,服务器接收到请求中的该字段进行判断资源是否更改,如果没更改,则返回状态码304,客户端此时将依然从缓存仓库中读取数据,如果发现更改了,则返回状态码200,并返回最新的数据,更新缓存仓库。

    但是如果响应头既不存在etag字段也不存在last-modified字段,则直接向服务器发起资源请求,服务器返回资源数据。

补充:优先级  cache-control>expires、Etag>last-modified

javascript高级进阶

 详细参考链接

42,事件总线event bus

 事件总线是事件监听addEventListener的实现原理,它的原理是基于发布订阅者模式,比如:

document.addEventListener("click", function(){})

这个click事件监听中,会有一个触发click的动作,这个动作叫做发布者,function(){},

概念性问题 

1,es6与es5的区别?

es6模块化规范是由es module语言层面上定义的,es5模块化主要是通过引用commonJS规范的require.js库实现。

es6相对于es5新增了以下几个新属性:

let,const,箭头函数,解构赋值,模板字符串,展开运算符(...),class,async/await,promise,for of,proxy,set,symbol, @decorator

2,var,let,const的区别?

let,const是块级作用域,且无法进行变量提升,在同一作用域下不可以重复定义,二者声明的变量不与window绑定。

var不受限于块级,能进行变量提升,在同一作用域下可以重复定义,var声明的全局变量是window的属性。

3,使用箭头函数应该注意的?

this不再执行调用者,而是指向定义处父级

不能使用arguments

不能做构造函数 

4,实现一个类模板字符串的功能?

const name = "bob";
const age = 12;
let   str = '${bob}已经${age}岁了';
str.replace(/\$\{([^}]*)\}/g,function(){
   return eval(arguments[1]
})
           

5,set和Map的区别?

set类似数组,没有键名;Map类似集合,是键值对;

6,promise有几种状态,什么时候会进入catch?

三种状态:pending,resolved,rejected;从pending->rejected会进入catch

7,使用结构赋值实现两个变量交换值?

数组的解构赋值与顺序有关,对象的解构赋值与属性名有关

let a = 1;
let b = 2;
[a,b] = [b,a]
           

8,遍历一个包含symbol类型的对象?

let  name = Symbol('name');
let obj = {
 [name]:"bob",
 "age":12
}
Reflect.ownKeys(obj);  //["age",Symbol(name)]
           

9,Refect.ownKeys和Object.keys区别?

Reflect.ownKeys返回一个数组,包含了对象的所有属性;Object.keys返回一个数组,包含了对象所有可枚举(enumerable)的属性。

10,哪些情况会出现不可枚举的属性?

Object.defineProperty定义的属性默认是不可枚举的,除此之外还有第八题出现的Symbol,以及基本数据类型的原型属性(Number,Object,String ......)

Object.defineProperty(obj,'method',{
    value:
    enumerable:false
    get:
    set:
    writable:
    configurable:
}
           

 11,以下Set的size是多少?

const a = new Set([1,1,1,2]);
a.size  // 2
           
const b = new Set();
b.add([1]);
b.add([1]);
b.size  // 2
           

 12,Promise中reject和catch?

reject是Promise的方法,用来抛出异常;

catch是new Promise()的方法,用来处理异常;

reject()以后直接进入.then方法的第二个参数,如果没有,则进入catch中。

13,使用class实现一个Promise?

class Promise{


}
           

14,如何使用Set()去重?

const a = [1,1,1,2];
const b = [...new Set(a)];   //[1,2]
           

15, 将for循环改为for of?

const a = [1,2,3,4]
let sum = 0;
for(let i=0;i<a.length;i++){
  sum+ = a[i]
}
           
for(let i of a){
 sum+ = i
}
           

16,async/await和Generator相比优势?

Generator每一步执行需要调用一次next(),yield后面只能跟一个生成器函数、或其他可迭代的对象(如一个数组、字符串、arguments对象、Promise对象、number)。

yield可以暂停和启动一个语句。

//Generator定义方式
function* func(){
    yield 'bob';
    yield '12' ;
    return 'number';
}    
function* getN(){
 const a = yield* func();
 console(a);
 yield 3;
 return 
}
const b = getN();
b.next(); //{value:'bob',done:false}
b.next(); //{value:'12',done:false}
b.next(); //number  {value:3,done:false}
b.next(); 
           

async较Generator的优势:

(1)内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样

(2)更好的语义。async 和 await 相较于 * 和 yield 更加语义化  

(3)更广的适用性。async函数的await后面可以是Promise也可以是原始类型的值

(4)返回值是 Promise。async 函数返回的是 Promise 对象,比Generator函数返回的Iterator对象方便,可以直接使用 then() 方法进行调用 

17,for Each,for in,for of的区别?

forEach循环数组

for in循环对象或者json

for of循环数组或者对象

18,使用json实现深拷贝?

const obj = {a:1,b:2}
const obj2 = JSON.parse(JSON.stringfy(obj))
obj2.a = 1000;
obj.a   // 1 
           

局限性:

假如包含函数,undefined,序列化后会丢失

假如包含实例化对象(new Person()),会丢失constructor

假如包含日期,被序列化后不再是对象,而是字符串

19,symbol的用处?

1,作为对象的属性

2,作为class的私有属性、方法

//对象的唯一属性
const name = Symbol('name');
const obj = {
   [name]:'bob'
    age:12
}

//class的私有方法,属性
const getName = Symbol();
class Person{
 constructor(){}
 [getName](){
    console.log(12)
 }
}
const obj = new Person();
obj.[getName]();  //报错,私有方法
           

20,super作为函数和对象在class中的作用? 

21,exports和module.exports区别?

相当于执行了  let exports = module.exports

不可以exports = {}来导出内容,这样上面的公式就不成立了。可以exports.aa = aa;

exports和module.exports同时导出,exports失效。

22,解构赋值的原理?

内部是由迭代器iterator实现,经过for 循环遍历获取对应的值进行赋值。

23,使用promise实现动态加载图片失败后用默认图片地址

function loadImage(url){
    image.src= url;
   return new Promise((resolve,reject)=>{
    image.onload = function(){
        resove('load success');
   }
    image.onerror = function(e){
        image.src = "https://www.qiantu.com/bird"
    }
})

}
           

24,delete和vue.delete的区别?

首先delete是es6中用来删除对象属性的,vue.delete另外一种写法this.$delete,与此相对应的是this.$set表示添加或者修改属性,vue的这一方法是为了解决defineProperty无法监听data对象新增属性的响应。

区别:delete删除数组的值,但是还占据内存;vue.delete直接内存也没了

const arr1 = [1,2,3]
const arr2 = [2,3,4]
delete arr1[0]
this.$delete(arr2,0)

console.log(arr1)  //empty ,2,3
console.log(arr2)  //3,4
           

25,纯函数概念?

1,一个函数的返回结果只依赖于它的参数

2,函数执行过程没有副作用

条件一

const a = 1

const foo = (b) => a + b

这就不是一个纯函数了。如果a改变,结果就可能改改变

条件二

const  obj = {name:"bob"}

const  func = (obj ) =>{ obj.name = "amy"  }

这也不是一个纯函数,因为它对外部obj进行了修改,产生了副作用

纯函数好处:靠谱

26,比较 !![ ],以及[ ] == true返回值?

因为[ ] 转化为boolean是true

转化为number是 0 

转化为string是“ ”

所以 !![ ] 得到 true

[ ] == true相当于  0 == 1,是false

27,js延迟加载的方式有哪些?

所谓js延迟加载,就是等页面加载完以后再加载js,有以下几种方式:

1,defer:告诉浏览器可以下载,但是不需要加载。等页面被解析完毕再加载。

<script  defer = "defer"  src = "test.js" ></script>

2,async:加载完页面才下载,执行js,但是一定在页面load之前执行完毕

<script  src="test.js"  async ></script>

3,动态加载js文件,需要放在</body>上面执行

ele =  document.createElement("script");

ele.src = "test.js";

document.body.appendChild(ele)

4,把<script >放在页面最底部

5,setTimeout

 28,高阶函数?

A函数的参数是B函数,那么我门称B函数为回调函数,A函数为高阶函数。即:一个函数的参数是个函数,那么这个函数叫高阶函数。

es中的高阶函数:map,reduce,filter,find,every,some

继续阅读