web前端面试

文章目录

JavaScript模块

ECMAScript了解

ECMAScript是一个语言标准,javascript则是基于这个标准实现的脚本语言。
ES6指es2015及以后的版本

url组成

  1. 协议(http),协议可以告知 浏览器客户端怎样访问资源,这里 URL 说明要使用 HTTP 协议,也可以是其他协议,如 https 、ftp 、rtsp、smtp 等
  2. 主机名又叫域名(www.abc.com),域名会对应一个ip地址,这部分告知 Web 客户端资源位于何处
  3. 路径(index.html),资源路径说明了请求的是服务器上哪个特定的本地资源

url语法

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

  1. 协议(scheme):访问服务器以获取资源时要使用哪种协议,比如htttps
  2. 用户(user):某些方案访问资源时需要用户名和密码来认证,默认值是匿名用户,如ftp
  3. 密码(password):用户名后面可能要包含的密码,中间由冒号:分隔
  4. 主机(host):资源宿主服务器的主机名或点分 IP 地址,如localhost
  5. 端口(port):资源宿主服务器正在监听的端口号,每个方案都有默认的端口号,如 HTTP 的默认端口号为 80
  6. 路径(path):服务器上资源的本地名,由一个斜杠/将其与前面的 URL 组件分隔开来,如 /index.html
  7. 参数(params):参数为应用程序提供了访问资源所需的所有附加信息(如 type=d 表明访问的资源是个目录),参数为 key/value 对,URL 中可以包含多个参数,用分号;分隔
  8. 查询(query):用来查询某类资源,用问号?与其他组件隔开,如果有多个查询,则用&隔开,如 https://www.taobao.com/inventory.cig?item=12731&color=blue
  9. 片段(frag):对一个带有章节的大型文本文档来说,资源的 URL 会指向整个文本文档,但是我们可以根据片段来显示我们感兴趣的章节,片段表示一小片或一部分资源的名字,用#与其他组件隔开

this指向问题

  1. 全局作用域中,this -> window
  2. 普通函数中,this 取决于谁调用,谁调用我,this就指向谁
  3. 箭头函数中,箭头函数没有自己的this,箭头函数的this就是上下文中定义的this,因为箭头函数没有自己的this所以不能用做构造函数
  4. 事件绑定中的this,事件源.onclik = function(){ } //this -> 事件源,事件源.addEventListener(function(){ }) ,this->事件源
  5. 定时器中的this,定时器中的this->window,因为定时器中采用回调函数作为处理函数,而回调函数的this->window
  6. 构造函数中的this,构造函数配合new使用, 而new关键字会将构造函数中的this指向实例化对象,所以构造函数中的this->实例化对象

var、const、let 对比

12345
var有变量提升可以重复声明,后面的值会覆盖前面的在函数内声明的变量是局部的,函数外声明的变量是全局的可以修改
let无变量提升不允许相同区域内的重复声明在块级作用域内有效可以修改
const无变量提升不允许相同区域内的重复声明在块级作用域内有效声明时必须初始化,常量的值不能改变,常量对象不能重新赋值,但是可以添加或修改内部对象

对const的个人理解 :当const为一个对象时,它应该是指向一块内存的首地址,重新赋值会改变这个首地址,js就能检测到,但添加和修改属性都是对这一块地址内部的改变或者追加,检测不到

数据类型

  • 基本类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol,js中只有undefined不属于对象
  • 引用数据类型(对象类型):对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)

js数据类型判断方法有哪些

  • typeof(),null返回object,function返回function,其他复杂类型返回object
  • object.prototype.toString.call 可以检测复杂数据类型
  • obj instanceof Object,只返回布尔值,obj为要检测的对象,object为校验类型,实质是判断右边的原型是否在左边的原型链上,所以只能判断复杂数据类型

数据类型转换

其他类型转string

  1. 调用String( str )
  2. 在后面加空串
    这两种方法null和undefined都适用,直接给他们加上双引号,str.toString()对这两种不适用。

其他类型转number

  1. Number(num)
    undefined、含非数字字符串 返回 NaN;
    false、null、空串、空格串 返回0;
    true返回1;
  2. parseInt(num)parseFloat(num)
    专门针对字符串的,截取字符串中从索引0开始有效的数字

其他类型转Boolean

Boolean(x)函数
除了数字0、空串’'、NaN、null、undefined,其他任何类型都是true

字符串转数组

用字符串的分割方法就行,a = "123".splict("") // ["1", "2", “3”]

数组常用方法

  1. push(),在数组最后追加数据,返回执行后的数组
  2. pop()删除数组的最后一项,返回被删除的数据
  3. reverse()反转数组,返回处理后的数组
  4. splice(开始索引,数量,要插入的数据)删除从索引开始的那么多数量个数据,然后在被删除的地方插入要插入的数,不写代表不插入数据,以数组的形式返回被删除的数据
  5. sort(函数)数组排序,默认按照位从小到大排序,其他排序方法需要自己写函数来传递
  6. join(连接符)将数组用连接符连接成一个字符串,并返回这个字符串
  7. concat(数组)拼接两个数组,返回一个数组
  8. slice(开始索引,结束索引) ,截取数组,包前不包后
  9. indexof(数据)方法,返回数据在数组中第一次出现的位置,没有则返回-1
  10. forEach(函数)方法,遍历数组,无返回值,对数组中每个数据执行传递的函数,传递的函数最多可接受三个参数,item(数据),index(索引),arr(原始整个数组),forEach没有返回,不可以改变数组内容
  11. map(函数)方法,按照函数方法返回映射后的数组map可以有返回值,可以改变数组内容,以return返回
  12. filter(函数)方法,按照传递的函数方法过滤数组,返回过滤后的数组,以return返回
  13. every(函数)方法,按照函数判断数组是否每一项都满足条件,返回值是布尔
  14. some(函数)方法,判断数组是否有某一项满足函数条件,返回值是布尔

数组reduce方法

reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素

arr.reduce(temp, item, index, data), [初始值]),回调函数的四个参数分别是 初始值(上次返回值), 当前元素值, 当前索引, 原数组

每调用一次回调函数,初始值(回调函数)变为上次回调的返回值,当前元素和索引向后挪一位;

  • 当初始值(第二个参数)没有设置时,初始值为数组索引0,当前索引对应为数组索引1;
  • 当数组只有一个参数且没有传入初始值时,直接返回数组元素;
  • 当数组为空时报错。

简单示例

arr = [1, 2, 3];
console.log(arr.reduce((temp, item, index, data) => {
    return temp + item;
  }, 10))
  
 // 16
let names = ["Alice", "Jack", "Lucy", "Bruce", "Alice", "Jack", "Lucy", "Alice", "Jack", "Jack"];
let sum = names.reduce((count, item) => {
  if (item in count)
    count[item]++;
  else
    count[item] = 1;
  return count;
},{})

console.log(sum);

// { Alice: 3, Jack: 4, Lucy: 2, Bruce: 1 }

字符串常用方法

  1. charAt(索引),返回索引位置的字符
  2. toLowerCase()全部转成小写
  3. toUpperCase()全部转成大写
  4. replace(被换内容,替换内容),字符串内第一个被换内容片段用替换内容替换,返回新的字符串,注意只替换一个片段
  5. splict(分割符)按照分隔符切割字符串,最后返回这个数组,可以用空串分割
  6. substr(开始索引,截取个数)截取字符串
  7. substring(开始索引,结束索引)截取字符串包前不包后
  8. slice(开始索引,结束索引)截取字符串包前不包后

数字常用方法

  1. Math.random(),返回0~1之间的随机小数
  2. Math.round(数字)四舍五入取整数
  3. Math.ceil(数字)向上取整
  4. Math.floor(数字)向下取整
  5. Math.pow(底数,指数)取幂,底数的指数次方运算结果
  6. Math.PI ,不是方法,是属性近似Π值

随机整数

想要获取 x ~ y之间的随机整数,先获取0 ~ (y-x)之间的随机整数,然后给这个数字加上x就可以;
那么相对随机的获取0 ~ y-x之间的随机整数呢,random()可以获得0 ~ 1之间的随机小数,我们把它乘以x,就可以得到0~y-x之间的随机数,然后取整就可以,重点在于取整,四舍五入会导致0和x的取值概率小于其他数字,这里应该选择random()乘以x+1,然后向下取整即可.

 function randomNum(x, y) {
  	var a = Math.floor(Math.random()*(y-x+1))+x;
	return a;
}
console.log(randomNum(10,12));

深拷贝

  1. 递归实现数组、对象的深拷贝;
    function deepClone1(obj,newObj) {
    	//判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
    	for(var k in obj){
    		var item = obj[k];
    		// 数组的判断要放在对象之前,因为对象属于数组
    		if(item instanceof Array){
    			newObj[k] = [];
    			deepClone1(item, newObj[k])
    		}
    		else if(item instanceOf Object){
    			newObj[k] = {};
    			deepClone1(item, newObj[k])
    		}
    		else
    			newObj[k] = item;
    	}
    }
    
  2. 通过jQuery的extend方法实现深拷贝
    //通过jQuery的extend方法实现深拷贝
    var array = [1,2,[3,4]];
    // 这里第一个参数表示是否深度合并对象,默认为false,所以我们要设为true,写deep也可以,第二个参数是目标对象
    var newArray = jQuery.extend(true,[],array);
    

new 的过程

构造函数就是通过new关键字来调用的函数,new的执行过程如下:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给这个对象(因此this就指向了这个对象),通过原型实现;
  3. 执行构造函数中的代码(为这个对象添加属性和方法,以及执行构造函数中其他的代码);
  4. 把这个新对象返回

类数组对象 arguments

arguments对象是所有(非箭头)函数中都可用的局部变量

  1. 可以使用arguments对象在函数中引用函数的参数,索引从0开始;
  2. length 属性,表示参数长度,除此之外没有任何Array属性;
  3. callee 属性,保存我们调用这个对象时的函数代码
  4. arguments对象可转换为正真的Array
    var args = Array.prototype.slice.call(arguments);
    var args = [].slice.call(arguments);
    // ES2015
    const args = Array.from(arguments);
    const args = [...arguments];
    

call apply bind

  1. call 方法
    立即执行;
    第一个参数是要绑定给this的值,后面传入的是一个参数列表
    当第一个参数为null、undefined的时候,默认指向window。

    var obj = {
        message: 'My name is: '
    }
    function getName(firstName, lastName) {
        console.log(this.message + firstName + ' ' + lastName)
    }
    getName.call(obj, 'Dot', 'Dolby')
    
  2. apply 方法
    立即执行;
    接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组
    当第一个参数为null、undefined的时候,默认指向window。

    var obj = {
        message: 'My name is: '
    }
    function getName(firstName, lastName) {
        console.log(this.message + firstName + ' ' + lastName)
    }
    getName.apply(obj, ['Dot', 'Dolby'])// My name is: Dot Dolby
    
  3. bind 方法
    不执行,返回一个函数;
    可以分多次传参,因为函数不立即执行;
    第一个参数是this的指向,从第二个参数开始是接收的参数列表;
    bind 转换后的函数可以作为构造函数使用。
    特别注意,bind改变的函数this始终为第一次调用bind时传入的参数,后面不会再变

    function fn(a, b, c) {
        console.log(a, b, c);
    }
    fn.bind('X','Y','Z')();    // X Y Z
    var fn1 = fn.bind(null, 'Dot');
    fn('A', 'B', 'C');            // A B C
    fn1('A', 'B', 'C');           // Dot A B
    fn1('B', 'C');                // Dot B C
    fn.call(null, 'Dot');      // Dot undefined undefined
    
  4. call和apply可用来借用别的对象的方法
    以call为例

    var Person1  = function () {
        this.name = 'Dot';
    }
    var Person2 = function () {
        this.getname = function () {
            console.log(this.name);
        }
        // call
        Person1.call(this);
    }
    var person = new Person2();
    person.getname();       // Dot
    

bind实现函数柯里化

  • 函数柯里化
    柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)
    柯里化不会调用函数,它只是对函数进行转换。
    柯里化使得函数更灵活,在某些场景发挥作用。
  • bind实现柯里化
    var add = function(x,y) {
    	return x + y;
    };
    var addone = add.bind(this,1);
    addone(2); // 3
    

手写 call

先要清楚call完成的工作是什么,首先改变this指向,为第一个传入的参数,然后把后面传进去的参数作为函数的参数,并且立即执行。

Function.prototype.call1 = function (context, ...args) {
    // 在传入的对象上设置属性为待执行函数
    // 这里的this就是调用call1的方法,把他添加到context上
    context.fn = this;
    // 执行函数,此时context调用fn,fn内部的this就是context
    const res = context.fn(...args);
    // 删除属性
    delete context.fn;
    // 返回执行结果
    return res;
};

手写 apply

apply 的实现思路和 call 一样,需要考虑的是 apply 只有两个参数;
需要考虑第二个参数是否非空,不为空的话,由于是个数组,需要展开。

Function.prototype.apply1 = function (context, args) {
    context.fn = this;
    // 判断第二个参数是否存在,不存在直接执行,否则拼接参数执行,并存储函数执行结果
    let res = !args ? context.fn() : context.fn(...args)
    delete context.fn;
    return res;
}

手写 bind

因为bind 转换后的函数可以作为构造函数使用,此时this应该指向构造出的实例,而不是bind绑定的参数
示例:fn.bind(obj,1)(2)(3)

Function.prototype.myBind = function(context) {
    // 因为后面要返回一个函数,为fn传入部分参数后的函数,这里就需要先把fn保存起来
    const _this = this
    const args = [...arguments].slice(1)
    // 返回一个函数
    return function F() {
    	// 返回的函数由调用fn且传入部分参数立即执行得到
        // 对于返回的函数,我们要判断是不是被当作构造函数调用的,this的指向不同
        if (this instanceof F) {
        	// 这里根据返回的函数调用时this指向自动改变了,说明是被new了,使用_this拿到最初的函数,作为构造函数用
            return new _this(...args, ...arguments)
        }
        // 这里是没有使用new,那么要保持之前传入的context
        return _this.apply(context, args.concat(...arguments))   // 使用call时数据应为 ...args,...arguments
    }
}

垃圾回收机制

JS的垃圾回收机制
为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。

变量生命周期
当一个变量的生命周期结束之后它所指向的内存就应该被释放。JS有两种变量,全局变量和在函数中产生的局部变量。局部变量的生命周期在函数执行过后就结束了,此时便可将它引用的内存释放(即垃圾回收),但全局变量生命周期会持续到浏览器关闭页面

引起内存泄漏的一些情况:

  1. 意外的全局变量引起的内存泄漏
    全局变量,不会被回收。
  2. 闭包引起的内存泄漏
    闭包可以维持函数内局部变量,使其得不到释放。
  3. 没有清理的DOM元素引用
    虽然别的地方删除了,但是对象中还存在对dom的引用。
  4. 被遗忘的定时器或者回调
    定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。
  5. 子元素存在引用引起的内存泄漏
    div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。

闭包

自由变量:是指在函数中使用的变量即不是函数参数arguments也不是函数内部声明的变量,那这个变量就是自由变量。

什么是闭包

如果一个函数访问了此函数的父级及父级以上的作用域变量,那么这个函数就是一个闭包;本质上,JS中的每个函数都是一个闭包,因为每个函数都可以访问全局变量。

闭包的执行过程

示例
function a() {
    var i = '初始值';
    i = i + "—_执行a"
    function b() {
        i = i + "_执行b"
        console.log(i)
    }
    return b;
}
var c = a(); // 此时 i 的值为 :初始值—_执行a
c()          // 此时 i 的值为 :初始值—_执行a_执行b
c()          // 此时 i 的值为 :初始值—_执行a_执行b_执行b
  1. 第一次执行 a(),局部变量 i 的值变为初始值—_执行a,并返回函数b,此时全局变量c的值为闭包函数b的引用。这里虽然a函数已经执行完了,但它包含闭包函数b,所以并不会销毁b函数执行的上下文,也就是变量 i 会留存在内存中
  2. 然后第一次执行c(),也就是执行了函数b,i 的值为初始值—_执行a_执行b,这里改变的i就是在内存中的 i,相当于一个全局变量了;
  3. 第二次执行c(),i 值为初始值—_执行a_执行b_执行b

闭包的应用场景

  • 读取函数内部的变量
  • 让这些变量的值始终保持在内存中。
  • 方便调用上下文的局部变量。利于代码封装。
  • 防抖节流函数、回调函数、封装变量

闭包的优缺点

优点:

  • 可以减少全局变量的定义,避免全局变量的污染
  • 能够读取函数内部的变量
  • 在内存中维护一个变量,可以用做缓存

缺点:

  • 造成内存泄露,闭包会使函数中的变量一直保存在内存中
  • 可能在父函数外部,改变父函数内部变量的值。
  • 造成性能损失,涉及了跨作用域的访问

闭包面试题

function fun(n, o) {
  console.log(o);
  return {
    fun: function (m) {
      return fun(m, n);
    }
  };
}

var a = fun(0);  //
a.fun(1);  //
a.fun(2);  //
a.fun(3);  //
var b = fun(0).fun(1).fun(2).fun(3); // 
var c = fun(0).fun(1); //
c.fun(2); //
c.fun(3); //

在这里插入图片描述

原型链

特点指向指向作用
prototype函数独有函数–>对象函数的原型对象,一个属于函数的对象,可以给函数的所有实例添加共享的属性和方法(函数在创建的时候,就会默认创建其prototype对象
__proto __对象独有对象–>对象当访问对象的属性的时候,相当于一个指针,如果没有,就会去 _ _proto _ _所指向的对象中去找,找不到的话再往上层 __proto __去找,直到找到null为止。
constructor对象独有对象–>函数指向该对象的构造函数,其中constructor可以从 __proto __中继承而来

每个函数都有一个prototype属性,这个prototype属性就是我们的原型对象,我们拿这个函数通过new构造函数创建出来的实例对象,这个实例对象自己会有一个指针(proto)指向他的构造函数的原型对象!这样构造函数和实例对象之间就通过( proto )连接在一起形成了一条链子。
原型对象中的内容相当于是暴露共享的。

继承的实现方法

1. 原型链继承

核心: 将父类的实例作为子类的原型

// 原型链
     function SuperType() {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function () {
        return this.property;
    };

    function SubType() {
        this.subproperty = false;
    }
    //继承SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function () {
        return this.subproperty;
    };
    let instance = new SubType();
    console.log(instance.getSuperValue()); // true

优点:

  • 实现了函数复用
  • 父类新增原型方法/原型属性,子类都能访问到

缺点:

  • 无法实现多继承
  • 来自原型对象的引用属性是所有实例共享的
  • 创建子类实例时,无法向父类构造函数传参

2. 构造函数继承

核心: 在子类构造函数中调用父类构造函数,可以用.call().apply()将父类构造函数引入子类函数。

 //父类
    function SuperType(name) {
        this.students= ["jackson", "bear"];
        this.name = name;
    }

    function SubType() {
        // 继承 SuperType
        SuperType.call(this,"Jacy");
        // 这里的原理就是把父函数的this替换为子函数的this,并且在这里执行了一遍,这样子函数就有了父函数的实例
        this.age = 20;
    }
    let instance1 = new SubType();
    instance1.students.push("daxiong");
    console.log(instance1.students); // ["jackson", "bear", "daxiong"]
    let instance2 = new SubType();
    console.log(instance2.students); // ["jackson", "bear"]
    // 测试传参
	console.log(instance.name); // "Jacy";
    console.log(instance.age); // 22 

优点:

  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承(call多个父类对象)

缺点:

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

3. 组合继承

**核心:**通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

// 父类
function Animal(name) {
  this.name = name || 'Animal';
  this.sleep = function(){
    return (this.name + '正在睡觉!');
  }
}
Animal.prototype.eat = function(food) {
  return (this.name + '正在吃:' + food);
};

// 子类
function Cat(name) {
  // 构造函数
  Animal.call(this);
  this.name = name || 'Tom';
}
// 原型链
Cat.prototype = new Animal();
// 这里暂时不知道有什么用
Cat.prototype.constructor = Cat;

// 测试
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom正在睡觉!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

优点:

  • 可以继承实例属性/方法,也可以继承原型属性/方法
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用

缺点:

  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

4. 寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

// 父类
function Animal(name) {
  this.name = name || 'Animal';
  this.sleep = function(){
    return (this.name + '正在睡觉!');
  }
}
Animal.prototype.eat = function(food) {
 return (this.name + '正在吃:' + food);
};

// 子类
function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
// 立即执行
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
  // 暂时不知道修复的作用
  Cat.prototype.constructor = Cat; // 需要修复下构造函数
})();

// Test Code
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom正在睡觉
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

优点: 这种继承方法是最推荐的,非常完美。

promise

我的promise笔记

简述事件循环机制

主线程

就是访问到的script标签里面包含的内容,或者是直接访问某一个js文件的时候,里面的可以在当前作用域直接执行的所有内容(执行的方法,new出来的对象等)

宏任务

setTimeout、setInterval、setImmediate、I/O、UI rendering

微任务

promise.then、process.nextTick

requesIdleCallback

不属于任何队列,只有在浏览器空闲时才会执行,也就是说所有任务执行完以后才会执行

执行顺序

  1. 先执行主线程
  2. 遇到宏任务放到宏队列
  3. 遇到微任务放到微队列
  4. 主线程执行完毕
  5. 执行并清空微队列
  6. 执行一次宏队列中的一个任务
  7. 循环步骤5、6,直至宏队列中所有任务执行完,微队列也执行完

注意两点,1. 对于promise,它的构造函数执行时,对应的then或者其他指定回调方法也会开始执行,放入微任务队列; 2. requesIdleCallback在程序执行完后执行

示例1:

console.log(1)
process.nextTick(() => {
  console.log(8)
  setTimeout(() => {
    console.log(9)
  })
})
setTimeout(() => {
  console.log(2)
  new Promise(() => {
    console.log(11)
  })
})
requestIdleCallback(() => {
  console.log(7)
})// 特殊说明: new Promise()属于主线程任务
let promise = new Promise((resolve,reject) => {
  setTimeout(() => {
    console.log(10)
  })
  resolve()  // 这个console也属于主线程任务
  console.log(4)
})
fn()
console.log(3)
promise.then(() => {
  console.log(12)
})
function fn(){
  console.log(6)
}

结果是1 4 6 3 8 12 2 11 10 9 7

示例2:

setTimeout(function () {
  console.log(" set1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2 ");
  });
});
new Promise(function (resolve) {
  console.log("pr1");
  resolve();
}).then(function () {
  console.log("then1");
});
setTimeout(function () {
  console.log("set2");
});
console.log(2);
new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});

结果: pr1 2 then1 then3 set1 then2 then4 set2

将扁平的数据转为树形结构

扁平数据

var data = [
  { pid: 0, id: 'a', value: '陕西' },
  { pid: 'a', id: 01, value: '西安' },
  { pid: 01, id: 301, value: '雁塔区' },
  { pid: 01, id: 302, value: '高新区' },
  { pid: 'a', id: 02, value: '渭南' },
  { pid: 'a', id: 03, value: '咸阳' },
  { pid: 0, id: 'b', value: '广东' },
  { pid: 'b', id: 11, value: '广州' },
  { pid: 'b', id: 12, value: '深圳' },
  { pid: 'b', id: 13, value: '潮汕' },
  { pid: 0, id: 'c', value: '湖南' },
  { pid: 'c', id: 21, value: '长沙' },
  { pid: 'c', id: 22, value: '常德' },
  { pid: 'c', id: 23, value: '岳阳' },
];

树状数据

var data = [
  {
    id: "a", pid: 0, value: "陕西", children: [
      { id: 01, pid: "a", value: "西安" },
      { id: 02, pid: "a", value: "渭南" },
      { id: 03, pid: "a", value: "咸阳" },
    ]
  },
  {
    id: "b", pid: 0, value: "广东", children: [
      { id: 11, pid: "b", value: "广州" },
      { id: 12, pid: "b", value: "深圳" },
      { id: 13, pid: "b", value: "潮汕" },
    ]
  },
  {
    id: "c", pid: 0, value: "湖南", children: [
      { id: 21, pid: "c", value: "长沙" },
      { id: 22, pid: "c", value: "常德" },
      { id: 23, pid: "c", value: "岳阳" },
    ]
  },
];

扁平转树状

递归法
递归法的思路是我们先找到外层的数据,再找子数据,找到的子数据又作为外层数据,递归找他的子数据;另一种理解方式就是,假设我们找到的元素它的子元素都是已经排列好的。

数据

var data = [
  {
    id: 1,
    pid: 0,
    name: "xxx"
  },
  {
    id: 2,
    pid: 1,
    name: "qqq"
  },
  {
    id: 3,
    pid: 2,
    name: "www"
  },
  {
    id: 4,
    pid: 0,
    name: "eee"
  },
  {
    id: 5,
    pid: 4,
    name: "ttt"
  },
  {
    id: 6,
    pid: 5,
    name: "yyy"
  }];

简洁版

function formatRouterTree(data) {
  let parents = data.filter(p => p.pid === 0);
  let children = data.filter(c => c.pid !== 0);
  dataToTree(parents, children);
  return parents;
}

function dataToTree(parents, children) {
  parents.map(p => {
    children.map((c, i) => {
      if (p.id === c.pid) {
        let _children = JSON.parse(JSON.stringify(children));
        _children.splice(i, 1);
        dataToTree([c], _children);
        if (p.children)
          p.children.push(c);
        else
          p.children = [c];
      }
    })
  })
}

详细注释版

function formatRouterTree(data) {
  // 先把最外层的父元素找出来,作为函数最后的返回
  let parents = data.filter(p => p.pid === 0);
  let children = data.filter(c => c.pid !== 0);
  // 使用树状结构化函数
  dataToTree(parents, children);

  // 树状结构化函数的定义,也可以写在函数外面
  function dataToTree(parents, children) {
    // 使用map处理每一个元素,返回处理后的数组
    parents.map(p => {
      // 对每一个父元素,外面要查找所有子元素集合,找到对应的所属子元素
      children.map((c, i) => {
        // pid对应id
        if (p.id === c.pid) {
          // 这里是进行深拷贝,每次找到一个子元素后删除它,提高效率(用于只有一个父元素场景)
          let _children = JSON.parse(JSON.stringify(children));
          _children.splice(i, 1);
          // 得到处理好的子元素
          // 这里特别注意,子元素c不是数组,把他变成数组才能正确递归
          dataToTree([c], _children);
          // 给父元素添加处理过后的子元素
          if (p.children)
            p.children.push(c);
          else
            p.children = [c];
        }
      })
    })
  }
  // 返回的是最外层父元素
  return parents;
}

树状转扁平

用一个新的数组保存扁平化处理后的数组;
处理过程:对于原数组中的每一个数据,我们先添加它(他的所有属性)到新数组中,然后再看它有没有孩子,有孩子就递归处理它的孩子。

// 数据
var data = [ 
{
    id: 1,
    name: "Jack",
    children: [{
        id:2,
        name: "qqq",
        children: [{
            id: 3,
            name: "www"
          }]
      }]
  },
  {
    id: 4,
    name: "eee",
    children: [{
      id: 5,
      name: "ttt",
        children: [{
        id: 6,
        pid: 5,
        name: "yyy"
          }]
      }]
  }
]

// 方法
function treeToData(data) {
  let list = [];
  deep(data);

  function deep(data) {
    data.forEach(item => {
      list.push({
        id: item.id,
        name: item.name
      })
      if (item.children)
        deep(item.children);
    });
  };
  return list;
}
console.log(treeToData(data));

数组扁平化

  1. 递归+reduce
    reduce处理每一项,若为数组,则递归处理,若为数据则将他添加到处理数组中。
    // 使用reduce实现
    function flatten(arr) {
      return arr.reduce((res, item) => {
        return res.concat(Array.isArray(item) ? flatten(item) : item);
      }, []);
    }
    
  2. 转成字符串再切割
    先把数组转换为字符串,此时数字间都有逗号,以逗号为分隔符切割字符串,并且把每一个数据转换为数字。
    function flatten(arr) {
      return arr.toString().split(',').map(p => {
        return Number(p);
      })
    }
    
  3. 纯递归
    递归每一项,不是数组就添加到新数组,是数组就递归。
    function flatten(arr) {
      let res = [];
      arr.map(p => {
        if (p.length)
          res = res.concat(flatten(p));
        else
          res.push(p);
      })
      return res;
    }
    

跨域及其解决办法

什么是跨域

首先我们要了解什么是同源策略。
同源策略:指 协议+域名+端口 三者相同(这三个就是源),即便两个不同的域名指向同一个 ip 地址,也非同源。

跨域违背了同源策略,即协议、域名、端口号 任何一项与当前url不同就存在跨域问题。

html中 <link> <a> <script> <frame> <img> 等这些标签具有跨域特性,跨域直接访问。

跨域的解决办法

四种常用方法
  1. jsonp
    原理:动态生成script标签,通过src属性加载

  2. 中间代理服务器
    前端访问未跨域的代理服务器,让代理服务器完成前端需要的访问任务

  3. CORS跨域资源共享
    服务器进行配置,加一个响应头

  4. Vue反向代理配置
    vue可以通过proxy设置反向代理服务器,同一个url跨域配置多个。

vue配置反向代理
  1. 方式一:单个代理
    进入Vue脚手架vue-cli配置参考栏,找到devServer.proxy代理服务器这里,根据官方文档配置好。
    请添加图片描述

    devServer: {
    	// 开启代理服务器
    	// 注意url,这个代理服务器和我们的请求页面所处环境一样,所以不要写代理服务器的url,
    	// 而是给他配置它要转发请求给服务器的完整url
      	proxy: 'http://localhost:5000'
    }
    

    脚手架中的public文件夹下的内容就是代理服务的内容,当请求的内容在代理服务器中有时,代理服务器不会转发请求。
    这种方式的缺点是,不能配置多个代理,只能把请求转发给一个服务器,还有就是不能灵活控制走不走代理,代理服务有的话就会自动不走代理。

  2. 方式二:多个代理
    还是在配置参考里,官方已经示范了第二种配置多个代理方式
    请添加图片描述
    按照这个格式就可以写任意多套代理,不要和方式一重复哈,只能选择其中一种代理方式。

      devServer: {
        proxy: {
          '/api': { // '/api'是请求前缀,紧跟在端口号后面,有这个前缀就走代理,没有就不走代理(前缀名api可以随便写)
            target: 'http://localhost:5000',  // 目标服务器
            pathRewrite: {'^/api':''},  // 重写路径配置,key:value形式,这里key我们用正则表达式写
            ws: true, // 用于支持websocket
            changeOrigin: true  // 这个配置用于向服务器发送自己的url时,选择原始真实的url还是符合服务器的虚拟url
          },
          '/demo1': { // 第二个
            target: 'http://localhost:5000',
            pathRewrite: {'^/demo1':''}, 
            // ws、changeOrigin默认为true
          },
          '/demo2': { // 第二个
            target: 'http://localhost:5000',
            pathRewrite: {'^/demo2':''}, 
            // ws、changeOrigin默认为true
          },
        }
      },
    
    • ‘/api’:请求前缀,紧跟在端口号后面,有这个前缀就走代理,没有就不走代理(前缀名api可以随便写)
    • target:目标服务器url
    • pathRewrite:重写路径(官方文档没有这个配置),这里是跨域配置/api在实际访问中显示在url中的样式。
    • ws:是否支持websocket
    • changeOrigin:是否改变源地址,这个配置用于向服务器发送自己的url时,选择原始真实的url(false)还是符合服务器的虚拟url(true)

    特别注意:
    这里配置了pathRewrite,重写路径,这是因为我们这里写了前缀,代理服务器转发这个请求url时是带着前缀转发的,这与客户端最开始想要请求的url不一致 ,后端没有配置的话就会访问不到返回404,所以这里重写, 用正则表达式匹配到前缀,把它替换为空串

事件处理机制

任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段

事件:是用户与网页进行交互时发生的事情
捕获:父 => 子
冒泡:子 => 父

var div1 =document.getElementById('div1');
var div2 =document.getElementById('div2');
var div3 =document.getElementById('div3');
    div1.addEventListener('click',function(){
        alert('div1');
      },true);
    div2.addEventListener('click',function(){
        alert('div2');
      },false);
    div3.addEventListener('click',function(){
        alert('div3');

这里addEventListener的第三个参数,是false就是冒泡,是true就是捕获。
那么点击div3,事件顺序就是 1,3,2

栈和堆的区别

内存的地址分配是连续的
栈内存的特点:存取速度快但不灵活,在后续也不能对其进行进一步的扩充或者删除,同时由于结构简单,在变量使用完成后就可以将其释放,内存回收容易实现

内存存储变量时没有规律
堆内存的特点:使用灵活可以动态增加或删除空间,但是存取比较慢

我们在访问引用类型时,需要在栈内存中查找 对应的地址,在去堆内存中取得真正的值,访问速度自然不及栈内存。

计网模块

前端常见的鉴权方法

什么是鉴权

鉴权也叫身份认证,指验证用户是否有系统的访问权限

目前常用的四种鉴权方法:

  • HTTP Basic Authentication (HTTP基本认证)
  • session-cookie
  • Token 验证(包括JWT,SSO)
  • OAuth(开放授权)

HTTP基本认证

http1.0或1.1规范的客户端(如IE,FIREFOX)收到401返回值时(请求用户认证),将自动弹出一个登录窗口,要求用户输入用户名和密码。 用户名及密码以BASE64加密方式加密(base64不安全!)。
客户端未未认证的时候,会弹出用户名密码输入框,这个时候请求时属于pending状态
用户输入用户名和密码后,会带在http request header上,发送给服务器进行校验,格式如下:

 Get /index.html HTTP/1.0 
 Host:www.google.com 
 Authorization: Basic d2FuZzp3YW5n

session-cookie验证

利用服务器端的session(会话)和浏览器端的cookie来实现前后端的认证

服务器端创建一个会话(session),将同一个客户端的请求都维护在各自的会话中,每当请求到达服务器端的时候先去查一下该客户端有没有在服务器端创建session,如果有则已经认证成功了,否则就没有认证。

简言之就是客户端会在服务器端建立session,档服务器收到某个客户端的请求时,先检查这个客户端有没有在服务端建立session。

缺点
服务器内存消耗大:session一般保存在内存中,随着认证用户的增加,服务器的消耗就会很大.
易受到CSRF攻击: 基于cookie的一种跨站伪造攻击, 基于cookie来进行识别用户的话,用户本身就携带了值,cookie被截获,用户就很容易被伪造
不利于服务器扩展:如果session保存在内存中,当增加为多台服务器时,会涉及到session共享的问题

Token 验证

token(令牌)是用户身份的验证方式。当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。

uid(用户唯一的身份标识)time(当前时间的时间戳)sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。
还可以把不变的参数也放进token,避免多次查库。

缺点
占带宽: 正常情况下token要比 session_id更大,需要消耗更多流量,挤占更多带宽.(不过几乎可以忽略)
性能低一些:相比于session-cookie来说,token需要服务端花费更多的时间和性能来对token进行解密验证.其实Token相比于session-cookie来说就是一个"时间换空间"的方案

OAuth开放授权

目前最流行的授权机制,用来授权第三方应用,获取用户数据。

简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用

非法IP

ipv4是32位的,用.隔开,每一个数在0~255

HTTP常见状态码

开头数字整体含义常用码常用码常用码常用码
1XX信息,服务器收到请求,需要请求者继续执行操作100:Continue、继续,客户端应继续其请求101:Switching Protocols、切换协议,服务器根据客户端的请求切换协议,只能切换到更高级的协议
2XX成功,操作被成功接收并处理200:OK、请求成功,一般用于GET与POST请求204 No Content:请求处理成功,但没有任何资源返回给客户端206:Partial Content、部分内容,服务器成功处理了部分GET请求
3XX重定向,需要进一步的操作以完成请求300:Multiple Choices 多种选择,请求的资源可包括多个位置,301 Moved Permanently:**永久性重定向**302 Found:临时性重定向303 See Other:查看其它地址
4XX客户端错误,请求包含语法错误或无法完成请求400 Bad Request:服务器端无法理解客户端发送的请求,请求报文中可能存在语法错误401 Unauthorized:请求要求用户的身份认证403:Forbidden,服务器理解请求客户端的请求,但是拒绝执行此请求404 Not Found:服务器上没有请求的资源
5XX服务器错误,服务器在处理请求的过程中发生了错误500:Internal Server Error,服务器内部遇到未知错误,无法完成请求503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。

http版本更新变化,http1.0,1.1和http2.0,3.0的区别

时间版本功能
1991年HTTP/0.9仅支持GET请求,不支持请求头
1996年HTTP/1.0默认短连接(一次请求建议一次TCP连接,请求完就断开),支持GET、POST、 HEAD请求
1999年HTTP/1.1默认长连接(一次TCP连接可以多次请求);支持PUT、DELETE、PATCH等六种请求增加host头,支持虚拟主机;支持断点续传功能
2015年HTTP/2.0多路复用,降低开销(一次TCP连接可以处理多个请求);服务器主动推送(相关资源一个请求全部推送);解析基于二进制,解析错误少,更高效(HTTP/1.X解析基于文本);报头压缩,降低开销。
2018年HTTP/3.0基于google的QUIC协议,而quic协议是使用udp实现的;减少了tcp三次握手时间,以及tls握手时间;解决了http 2.0中前一个stream丢包导致后一个stream被阻塞的问题;优化了重传策略,重传包和原包的编号不同,降低后续重传计算的消耗;连接迁移,不再用tcp四元组确定一个连接,而是用一个64位随机数来确定这个连接;更合适的流量控制

浏览器缓存

关于浏览器缓存,看了好多文章,说法不一,这里就暂时不管了,参考图片就行
前端缓存

http 缓存

什么是http缓存

http缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。

常见的http缓存只能缓存get请求响应的资源,对于其他类型的响应则无能为力,所以后续说的请求缓存都是指GET请求。

http缓存都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在respone header头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存就直接200, 否则就把请求参数加到request header头中传给服务器看是否命中协商缓存,命中则返回304,否则服务器会返回新的资源。

缓存的分类

前端缓存可分为两大类:http缓存浏览器缓存
是否需要重新向服务器发起请求:强制缓存协商缓存
是否可以被单个或者多个用户使用:私有缓存共享缓存

强(制)缓存

强制缓存缓存数据未失效(即Cache-Controlmax-age没有过期或者Expires的缓存时间没有过期)的情况下,那么就会直接使用浏览器的缓存数据,不会再向服务器发送任何请求。强制缓存生效时,http状态码为200

这种方式页面的加载速度是最快的,性能也是很好的,但是在这期间,如果服务器端的资源修改了,页面上是拿不到的,因为它不会再向服务器发请求了。这种情况就是我们在开发种经常遇到的,比如你修改了页面上的某个样式,在页面上刷新了但没有生效,因为走的是强缓存,所以Ctrl + F5一顿操作之后就好了。

强制缓存相关字段:

  1. expires: 时间,http1.0,时间参数是强制缓存有效的截止时间
    res.writeHead(200, {
      'Expires': new Date('2022-09-28 14:45:00').toUTCString()
    })
    
  2. cache-control: 参数,http1.1,对expires的完善和扩展,功能更强大
    • no-cache/no-store:强制进行协商缓存/禁止任何缓存策略
    • public/private:公有(可进行代理服务器缓存)/私有(只有浏览器课缓存,代理服务器不可)
    • max-age/s-maxage:强制缓存有效时长/代理服务器强制缓存有效时长,所以s-maxage只有在public下有效
    res.writeHead(200, {
      'Cache-control': 'max-age=300'
    })
    

协商缓存

在使用本地缓存之前,先向服务器发起一次GET请求,与之协商当前浏览器保存的本地缓存是否已经过期
实现方法:

  1. ifmodified-since,请求头中包含ifmodified-since字段,表示所请求本地缓存的最近一次修改时间戳(上次响应头中的last-modified),服务器收到ifmodified-since字段后,会与资源当前实际修改时间戳比较,若时间相同则说明缓存仍然有效,返回304,返回全新的资源

    const fs = require('fs'); 
    // 拿到资源上次修改时间和请求头中传过来的浏览器资源保存时间
    const { mtime } = fs.statSync('./z.cpp')
    const ifModifiedSince = req.header['if-modified-since']
    // 判断资源是否有效
    if (ifModifiedSince === mtime.toUTCString()) {
      // 资源仍然有效,返回304状态码
      resizeBy.statusCode = 304
      res.end()
      return;
    }
    // 资源失效,返回新资源并更新数据
    res.setHeader('last-modified', mtime.toUTCString())
    res.setHeader('cache-control', 'no-cache')
    res.end(data)
    

    缺点:

    • 当文件资源进行了编辑但内容没有任何变化时,也会更新时间戳,造成浪费
    • 如果文件资源更新非常快,是毫秒级别,那么这种方式检测不出来更新
  2. ETag,为了弥补通过时间戳判断方式的不足,http1.1增加了ETag头信息(实体标签Entity Tag),原理是服务器为不同内容资源生成一个哈希串,只要文件内容编码不同,ETag标签值就不同,这样每次请求只要比对哈希串就行了;
    在第一次请求时,响应头中会包含etag字段,浏览器接收到后保存,下一次请求时,以if-none-match请求头字段发送。

    const fs = require('fs');
    const data = fs.readFileSync('./z.cpp');
    const etagContent = etag(data);
    
    const ifNoneMatch = res.headers['if-none-match'];
    
    if (ifNoneMatch === etagContent) {
      res.statusCode = 304;
      res.end();
      return;
    }
    
    res.setHeader('etag', etagContent);
    res.setHeader('catch-control', 'no-cache');
    res.end(data);
    

私有缓存

浏览器级缓存
私有缓存只能用于单独的用户:Cache-Control: Private

共享缓存

代理级缓存
共享缓存可以被多个用户使用: Cache-Control: Public

为什么要使用HTTP缓存

  1. 减少了冗余的数据传输,节省了网费。
  2. 缓解了服务器的压力, 大大提高了网站的性能
  3. 加快了客户端加载网页的速度

从输入URL到页面展示之间发生了什么?

参考文章:https://www.jianshu.com/p/a5f26487f097
假设输入 www.google.com

  1. DNS解析:将域名解析成IP地址

    首先在本地域名服务器查找是否有缓存,如果没有去问根域名服务器 根域名服务器如果找不到,则会去问COM
    顶级域名服务器

    COM顶级域名服务器会告诉去下一级的域名服务器google.com域名服务器查找IP
    google.com域名服务器终于找到了IP,并返回IP给本地域名服务器
    本地域名服务器将IP地址告诉浏览器,浏览器进行下一步操作 同时,本地域名服务器还会将这个IP地址缓存,以便下次快速寻找IP

  2. TCP 连接:TCP 三次握手

  3. 发送 HTTP 请求

  4. 服务器处理请求并返回 HTTP 报文

  5. 浏览器解析渲染页面
    浏览器内核拿到内容后,渲染步骤大致可以分为以下几步:

    1. 解析HTML,构建DOM树
    2. 解析CSS,生成CSS规则树
    3. 合并DOM树和CSS规则树,生成render树
    4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
    5. 绘制render树(paint),绘制页面像素信息
  6. 断开连接:TCP 四次挥手

怎么实现可靠传输的?

  • 停止等待机制:发送一个报文后要等待ack,然后再发下一个报文
  • 重传机制:超时、ack重复
  • 流量控制:滑动窗口协议与累计确认
  • 拥塞控制:慢启动、快恢复

http与https的区别

  • http 明文传输,数据都是未加密的,安全性较差,https(ssl+http) 数据传输过程是加密的,安全性较好。
  • http页面响应速度比 https快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 https除了tcp的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。

https加密过程

  • 第一步:客服端给出支持SSL协议版本号,一个客户端随机数(Client random,请注意这是第一个随机数),客户端支持的加密方法等信息;
  • 第二步:服务器收到信息后,确认双方使用的加密方法,并返回数字证书,一个服务器生成的随机数(Server random,注意这是第二个随机数)等信息;
  • 第三步:客服端确认数字证书的有效性,然后生成一个新的随机数(Premaster secret),然后使用数字证书中的公钥,加密这个随机数,发给服务器。
  • 第四步:服务器使用自己的私钥,获取客服端发来的随机数(即Premaster secret);(第三、四步就是非对称加密的过程了)
  • 第五步:客服端和服务器通过约定的加密方法(通常是AES算法),使用前面三个随机数,生成对话密钥,用来加密接下来的通信内容;

https通信过程

先三次握手,然后非对称传递密钥,之后对称加密通话
先三次握手(syn,ack/syn,ack),客服端发送支持的加密方法给服务器选,服务器确认加密方式以后返回一个证书,客服端验证这个证书,然后用公钥加密一个生成的密钥传给服务器,服务器接收后用私钥解密,后面双方使用这个密钥的对称加密算法进行通信。

https 交换随机数时候中间人监听即后端收到的随机数是经过中间人修改过的,同时后端返回给前端的随机数也是修改过了,这样有可能吗

有可能

tcp/udp的区别

  • TCP是面向连接的,UDP是无连接的;
  • TCP提供的是可靠传输服务,无差错,不丢失,不重复,按序到达,UDP可能丢包;
  • TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流UDP是面向报文的
  • UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
  • 每一条TCP连接只能是点到点的,UDP支持一对一,一对多,多对一和多对多的交互通信。
  • TCP首部开销20字节,UDP的首部开销小,只有8个字节。
  • 重点:TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

tcp三次握手和四次挥手

  • 三次握手:syn,ack+syn,ack
    你好,能听见吗?
    能听见,你能听见我说话吗?
    能听见。
  • 四次挥手:fin,ack+fin,ack,
    客服端发送fin,表示请求断开连接,服务端收到后返回ack表示收到fin,剩余数据处理完后关闭,发送fin告知客服端现在断开连接,客服端收到这个fin后返回ack表示可以了,服务器收到ack,释放连接,客服端等待一段时间后释放连接。

css 模块

谈谈你对css布局的理解

  1. 固定布局
    固定宽度布局的设计不会因为用户扩大或缩小浏览器窗口而发生变化,这种设计通常以像素作为衡量单位
  2. 流式布局
    流式布局就是百分比布局
  3. 弹性布局
    弹性布局(flex container),页面中任何一个元素只要设置了display:flex属性,那么当前盒子都称为弹性盒子;弹性盒子默认有两条轴: 默认水平显示的主轴 和 始终要垂直于主轴的侧轴(也叫交叉轴),在弹性盒子中所有的子元素都是沿着主轴方向显示。(主轴是可以设置的)

父元素为弹性盒子时,子元素可以设置flex属性,一般用于子元素为奇数一列展示时,不方便设置padding值时使用。

  1. 浮动布局
    当元素浮动以后可以向左或向右移动,直到它的外边缘碰到包含它的框或者另外一个浮动元素的边框为止,元素浮动以后会脱离正常的文档流,所以文档的普通流中的框就变现的好像浮动元素不存在一样。

    这种布局在图文混排的时候可以很好的使文字环绕在图片周围;
    对块元素一个就占一行,但是有时候我们希望他们能并排,就可以用这种方式让他们浮动起来。

  2. 定位布局
    css中定位布局指的是元素可以脱离原来的位置,定位到页面中任意位置的方式;定位布局可以分为静态定位(static)、绝对定位(absolute)、相对定位(relative)、固定定位(fixed)和 粘性定位(sticky)五种定位方式。

  3. margin 和 padding
    margin外边距,padding内边距

请列举几种可以清除浮动的方法

为了避免浮动元素覆盖后面不浮动的元素,有几种方法清浮动:

  1. 把浮动元素的高度固定。之所以会覆盖是因为元素一旦浮动,其高度就默认为0
  2. 清浮动,即使用clear属性,给不想被覆盖的元素做限制
  3. 在浮动元素后面增加一个清浮动的盒子,不设置宽高,只设置clear:both
  4. overflow:hidden; 给浮动元素的外层盒子设置这样一个属性,那么盒子内部的排列布局不影响盒子之外的元素

请列举几种隐藏元素的方法

  1. display: none,彻底隐藏元素,不占空间,无法点击
  2. visibility: hidden占据空间,无法点击
  3. opacity:0,只是透明度设置为0,占据空间,可以点击
  4. position:absolute,绝对定位,水平位置和垂直位置移出页面外,不影响布局,可以交互
  5. clip/clip-path,裁剪,给元素裁剪到0px,占据空间,无法点击
  6. transform: scale(0,0),缩放,给元素缩放到0,占据空间,无法点击

定位有哪几种

  1. static默认值,没有定位
  2. relative相对定位,相对于自身位置定位
  3. absolute绝对定位,相对于static以外的第一个父元素定位
  4. fixed固定定位,相对于浏览器窗口定位
  5. sticky 粘性定位,当前元素设置了粘性定位,滚动到顶部时就会吸附在顶部,往下滑动能回到原来的位置

z-index规则

  1. 值可以是正整数、负整数或0,数值越大,盒子越靠上;
  2. 如果属性值相同,则按照书写顺序,后来居上
  3. 数字后面不能加单位
  4. z-index 只能应用于相对定位、绝对定位和固定定位的元素,其他标准流、浮动和静态定位无效

html设计制作中,css四种引入方式

  1. 内联样式
    内联样式,也叫行内样式,指的是直接在 HTML 标签中的 style 属性中添加 CSS。
    <div style="display: none;background:red"></div>
    
  2. 嵌入样式
    嵌入方式指的是在 HTML 头部中的 < style> 标签下书写 CSS 代码。
    <head>
        <style>
          .content {
             background: red;
           }
        </style>
    </head>
    
  3. 链接样式link
    链接方式指的是使用 HTML 头部的 标签引入外部的 CSS 文件。
    <head>
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    
  4. 导入样式@import
    导入方式指的是使用 CSS 规则引入外部 CSS 文件。
    <style>
        @import url(style.css);
    </style>
    

页面导入时,使用link和@import有什么区别

  1. link 属于 HTML,通过 标签中的 href 属性来引入外部文件,而 @import 属于 CSS,所以导入语句应写在 CSS 中,要注意的是导入语句应写在样式表的开头,否则无法正确导入外部文件;
  2. @import 是 CSS2.1 才出现的概念,所以如果浏览器版本较低,无法正确导入外部样式文件;
  3. **当 HTML 文件被加载时,link 引用的文件会同时被加载,而 @import 引用的文件则会等页面全部下载完毕再被加载;**所以更建议使用link加载CSS。

简述src和href的区别

  1. 当浏览器遇到 href并行下载资源并且不会停止对当前文档的处理。
  2. 当浏览器解析到src会暂停其他资源的下载和处理直到将该资源加载或执行完毕

HTML5新增语义化标签

语义化标签旨在让人一眼就看出来每一个标签的作用和含义

  • 头部标签:<header></header>
  • 导航标签:<nav></nav>
  • 内容区块表签:<section></section>
  • 页脚标签:<footer></footer>
  • 侧边栏:<aside></aside>
  • 页面内独立的内容区域:<article></article>

rem、px、em

rem是一个相对单位,rem的是相对于html元素的字体大小,没有继承性

em是一个相对单位,是相对于父元素字体大小,有继承性

px是一个“绝对单位”,就是css中定义的像素,利用px设置字体大小及元素的宽高等,比较稳定和精确,不同的设备px不同。

什么是视口

视口简单理解就是可视区域大小

PC端,视口大小就是浏览器窗口可视区域的大小

移动端存在着三种视口:布局视口、视觉视口、理想视口

  • 布局视口的默认宽度一般为980px
  • 视觉视口是指用户正在看到的网站的区域,这个区域的宽度等同于移动设备的浏览器窗口的宽度
  • 理想视口是指对设备来讲最理想的视口尺寸,在理想视口情况下,布局视口的大小和屏幕宽度是一致的

响应式布局有哪些方式

  1. 媒体查询
    使用@media可以根据不同的屏幕定义不同的样式
    /* iphone 5 */
    @media screen and (max-width: 320px) {
        body {
          background-color: red;
        }
    }
    /* iphoneX */
    @media screen and (min-width: 375px) and (-webkit-device-pixel-ratio: 3) {
        body {
          background-color: #0FF000;
        }
    }
    
  2. 百分比布局
    相对于父元素的布局(无父元素相对于视口),但是无法对字体,边框等比例缩放
    	/* ipad pro */
    @media screen and (max-width: 1024px) {
        aside {
          width: 8%;
          background-color: yellow;
        }
    }
    
  3. 视口单位vw/vh
    vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度,系统会将视口的宽度和高度分为100份,1vw占用视口宽度的百分之一,1vh占用视口高度的百分之一。
  4. rem
    rem是指相对于根元素的字体大小的单位,rem只是一个相对单位
    body {
        background-color: yellow;
        font-size: 1.5rem;
    }
    

H5新增特性

  1. doctype不不区分大小写
  2. 部分标记元素可省略
    • 一些元素不允许写结束标记,如br、img、input
    • 一些标签可以省略结束标记,如li、p、dt、dd
    • 一些标签可以省略全部标记,如:html、head、body等
  3. 属性值可以用单引号
  4. 新增语义化标签
  5. 新增音视频标签 audio、video
  6. 增强表单input的属性
  7. 新增选项列表 datalist

CSS3新增特性

  1. 新增选择器
  2. 新增盒子模型
  3. CSS3动画过渡
  4. 媒体查询@media,多栏布局
  5. 2D转换、3D转换
  6. 新增选择器
  7. 渐变

Doctype作用? 标准模式与兼容模式(兼容模式)如何区分?

文档声明,用于告知浏览器该以何种模式来渲染文档。

严格模式和混杂模式的区别:
严格模式:页面排版及 JS 解析是以该浏览器支持的最高标准来执行
混杂模式:不严格按照标准执行,主要用来兼容旧的浏览器,向后兼容

什么是回流什么是重绘?

回流

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建rende tree.

重绘

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘.

回流一定引起重绘,重绘不一定回流

浏览器是如何渲染页面的

  1. 解析 HTML
  2. 构建DOM树
  3. 构建CSSOM树
  4. 构建 Render 树(结合DOM树和CSSOM树)
  5. 布局 / 回流
  6. 渲染 / 重绘

两栏布局 / 三栏布局

  1. 两栏布局
    一边宽度固定,另一边自适应
  2. 三栏布局
    • 绝对定位,左右栏固定,中间栏自适应
    • 浮动布局,左右浮动,中间自适应
    • flex布局,左右固定,使用剩下的位置
    • 网格布局
    • 圣杯布局和双飞翼布局

常见的块级、行级、空元素

  • 行内元素有:span a b i img input select strong
  • 块级元素有:div p h1-h6 table form ul ol li dl dt dd
  • 空元素(没有内容): <hr> <img> <input> <link> <meta>

Vue模块

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值