当前位置:   article > 正文

前端面试总结(js基础篇)_前端面试 js 基础

前端面试 js 基础

js 中的堆和栈

基本数据类型

undefined、null、number、boolean、string;

基本数据类型值指保存在栈内存中的简单数据段。访问方式是按值访问。

引用数据类型:

Object、array、function、data

引用数据类型值指保存在堆内存中的对象。也就是,变量中保存的实际上的只是一个指针,这个指针指向内存中的另一个位置,该位置保存着对象。访问方式是按引用访问

== 和 === 区别

1、对于string,number等基础类型,==和===是有区别的

1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等

2)同类型比较,直接进行“值”比较,两者结果一样

 

2、对于Array,Object等高级类型,==和===是没有区别的

进行“指针地址”比较

 

3、基础类型与高级类型,==和===是有区别的

1)对于==,将高级转化为基础类型,进行“值”比较

2)因为类型不同,===结果为false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

————————————————
版权声明:本文为CSDN博主「Bliss_妍」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yyychocolate/article/details/108089477

== 比较规则

"0" == null   // false
"0" == undefined // false
"0" == false // false -> 0 比较 “0” == 0 =》"0" -> 0 => 0==0 => true
"0" == NaN  // false
"0" == 0 // ''0" -> 0 => 0 == 0=>0===0 => true
"0" == "" // "0" === "" => false

false == null // false null 和任意都不等(除undefined和null)
false == undefinded // false
false == NaN // false 
false == 0 //   false -> 0 => 0== 0 => 0===0 => true
false == "" // false -> 0 => 0 == "" => "" -> 0  => 0 == 0 => 0 === 0 => true
false == [] 
// false -> 0 => 0 == [] => [].toString() = '' => 0 == '' => 0 === 0 => true
false == {} 
// false -> 0 => 0 == {} => Object.prototype.toString.call({})="[object Object]"
// 0=="[object Object]" = >Number( "[object Array]" ) = NaN 
// => 0 == NaN =>  false

"" == null  // false
"" == undefined //false
"" == NaN //false
"" == 0 // ''-> 0 => 0===0 => true
"" == [] // [] -> '' => ""=="" => true
"" == {} // {} -> NaN => "" == NaN => false

0 == null // false
0 == undefinded //false
0 == NaN // false
0 == [] // [] -> "" => 0 == "" => 0 == 0 => 0===0 => true
0 == {} // {} -> NaN => 0 == NaN => false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • undefined/ null 除了彼此和自身相等外和任何值都不相等
  • [] 和字符串比较,最终转成空字符串 “”, 和数字比较最终转换成0
  • {}和基本类型比较,最终转换成 NaN, NaN和 谁都不等;

空数组、空对象和 false 之间使用==判定规律总结

引用类型变量的复制:

复制的是存储在栈中的指针,将指针复制到栈中未新变量分配的空间中,而这个指针副本和原指针指向存储在堆中的同一个对象;复制操作结束后,两个变量实际上将引用同一个对象。因此,在使用时,改变其中的一个变量的值,将影响另一个变量。

对象值都是引用,所以的对象的比较也叫引用的比较,当且当他们都指向同一个引用时,即都引用的同一个基对象时,它们才相等

let a=[0,1,2,3,4],
    b=a
console.log(a===b);  //true
a[0]=1;
console.log(a,b); [1,2,3,4] , [1,2,3,4]
  • 1
  • 2
  • 3
  • 4
  • 5
let a1 = {name:'aa',age:18}
let b1 = {name:'aa',age:18}
alert(a1===b1); //false
  • 1
  • 2
  • 3
a. 引用类型的值是可以改变的,例如对象就可以通过修改对象属性值更改对象。
b. 引用类型可以添加属性和方法。
c. 引用类型的赋值是对象引用,即声明的变量标识符,存储的只是对象的指针地址。
d. 引用类型的比较是引用(指针地址)的比较。
e. 引用类型是同时保存在栈区和堆区中的,栈区保存变量标识符和指向堆内存的地址。
  • 1
  • 2
  • 3
  • 4
  • 5

js 中深拷贝和浅拷贝的区别

浅拷贝:
只是增加了一个指针指向已存在的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深拷贝:
在计算机中开辟一块新的内存地址用于存放复制的对象。

实现浅拷贝的方法:

1、for···in只循环第一层

// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
  }
   return obj2;
}
var obj1 = {
   a: 1,
   b: 2,
   c: {
         d: 3
      }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2、直接用=赋值

let a=[0,1,2,3,4],
    b=a;
console.log(a===b);
a[0]=1;
console.log(a,b);
  • 1
  • 2
  • 3
  • 4
  • 5

3、赋值与浅拷贝的区别

  • 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
  • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

实现深拷贝的方法

1、采用递归去拷贝所有层级属性

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                //所谓的递归函数就是在函数体内调用本函数
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2、通过JSON对象来实现深拷贝

var test ={
	  	name:{
	  	 xing:{ 
	  	     first:'张',
	  	     second:'李'
	  	},
	  	ming:'老头'
	  },
	  age :40,
	  friend :['隔壁老王','宋经纪','同事']
	 }
	  var result = JSON.parse(JSON.stringify(test))
	  result.age = 30
	  result.name.xing.first = '往'
	  result.friend.push('fdagldf;ghad')
	  console.dir(test)
	  console.dir(result)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

缺点: 无法实现对对象中方法的深拷贝,会显示为undefined

3、通过jQuery的extend方法实现深拷贝

var array = [1,2,3,4];
var newArray = $.extend(true,[],array); // true为深拷贝,false为浅拷贝
  • 1
  • 2

js 中防抖节流

防抖

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

  • 防抖的中心思想在于:我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。
/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func,wait,immediate) {
    let timeout;

    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

简单版

	function debounce(fn,wait){
		let timeOut = null
		return args =>{
			if(timeOut) clearTimeout(timeOut)
			timeOut = setTimeout(fn,wait)
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

节流

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

  • throttle 的中心思想在于:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应

对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。

function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

防抖应用场景

文本输入搜索联想
文本输入验证(包括 Ajax 后端验证)
  • 1
  • 2

节流应用场景

鼠标点击
监听滚动 scroll
窗口 resize
mousemove 拖拽
  • 1
  • 2
  • 3
  • 4

xss攻击和csrf攻击的区别与防范

CSRF(Cross-site request forgery):跨站请求伪造。

(1)、攻击原理:

用户访问登录信任网站A,验证成功后会产生A的cookie
用户在没有的登出A网站的情况下访问危险网站B,而网站B诱导用户访问A并发出请求
浏览器会带着A网站产生的cookie去执行这个请求
A网站会根据用户权限去处理这个请求,这样网站B就达到了模拟用户操作的目的了

  • 1
  • 2
  • 3
  • 4
  • 5

(2)、防御措施:

  • token验证:登陆成功后服务器下发token令牌存到用户本地,再次访问时要主动发送token,浏览器只能主动发cookie,做不到主动发token
  • referer验证:判断页面来源是否自己站点的页面,不是不执行请求
  • 隐藏令牌: 令牌放在http header头中,而不是链接中

XSS(Cross Site Scripting):跨域脚本攻击。

(1)、攻击原理:

不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、html代码块等)。

(2)、防御措施:

设置Cookie的属性为Http only,这样js就无法获取Cookie值;
严格检查表单提交的类型,并且后端服务一定要做,不能信任前段的数据;
对用户提交的数据就行Html encode处理,将其转化为HTML实体字符的普通文本;
过滤或移除特殊的HTML标签,如<script>、<iframe>等;
过滤js事件的标签,如onclick=、onfoucs=等、
  • 1
  • 2
  • 3
  • 4
  • 5

(3)、分类

  • 反射型XSS:<非持久化> 攻击者事先制作好攻击链接,
    需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。
  • 存储型XSS:<持久化>
    代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。
  • DOM型XSS:基于文档对象模型Document Objeet
    Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如url,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生XSS漏洞。

区别

CSRF:需要用户先登录网站A,获取 cookie。XSS:不需要登录。

CSRF:是利用网站A本身的漏洞,去请求网站A的api。XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网站A的内容。

js函数中的arguments

arguments 是一个对应于传递给函数的参数的类数组对象
  • 1
  • arguments 对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments 对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。例如,如果一个函数传递了三个参数,你可以以如下方式引用他们:
arguments[0]
arguments[1]
arguments[2]
  • 1
  • 2
  • 3

null 和 undefined的区别

null 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。 null 表示"没有对象",即该处不应该有值。 null 典型用法是:

作为函数的参数,表示该函数的参数不是对象。 
作为对象原型链的终点。
  • 1
  • 2

当声明的变量还未被初始化时,变量的默认值为 undefined。 undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。

变量被声明了,但没有赋值时,就等于 undefined。 
调用函数时,应该提供的参数没有提供,该参数等于 undefined。 
对象没有赋值的属性,该属性的值为 undefined。 
函数没有返回值时,默认返回 undefined。
未定义的值和定义未赋值的为 undefined,null 是一种特殊的 object,NaN 是一种特殊的 number。
  • 1
  • 2
  • 3
  • 4
  • 5

原型与原型链的理解

JavaScript 的每个对象都继承另一个父级对象,父级对象称为原型 (prototype)对象。
  • 1
  • 原型也是一个对象,原型对象上的所有属性和方法,都能被子对象 (派生对象) 共享 通过构造函数生成实例对象 时,会自动为实例对象分配原型对象。 而每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。
  • 从一个实例对象向上找有一个构造实例的原型对象,这个原型对象又有构造它的上一级原型对象,如此一级一级的关系链,就构成了原型链
  • 每个对象都会在其内部初始化⼀个属性,就是 prototype (原型)
    当我们访问⼀个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 prototype ⾥找这个属性,这个 prototype⼜会有⾃⼰的 prototype ,这样⼀直找下去,也就是我们平时所说的原型链

HTTP 中 GET 与 POST 的区别

  • a. GET 是将参数写在 URL 中 ? 的后面,并用 & 分隔不同参数;而 POST 是将信息存放在 Message Body中传送,参数‘不会’显示在 URL中(Restful规范中是这样,但post在有需要时可以把参数放URL里)。GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。也就是说Get是通过地址栏来传值,而Post是通过提交表单来传值。
  • b. GET请求提交的数据有长度限制(HTTP 协议本身没有限制 URL 及正文长度,对 URL
    的限制大多是浏览器和服务器的原因),POST请求没有内容长度限制。
  • c. GET请求返回的内容会被浏览器缓存起来。而每次提交POST请求,浏览器不会缓存POST请求返回的内容。
  • d. GET对数据进行查询,POST主要对数据进行增删改!简单说,GET是只读,POST是写。 e. 关于安全性,GET请求方式从浏览器的 URL 地址就可以看到参数;所以post更安全,其实无论是 GET 还是 POST 其实都是不安全的,因为 HTTP协议是明文传输,只要拦截封包便能轻易获取重要资讯。想要安全传输资料,必须使用 SSL/TLS来加密封包,也就是 HTTPS。

js实现继承的几种方法

概念

通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承
  • 1

1. 借助构造函数实现继承

//  定义父类
function Parent1 () {
    this.name = '陈楚',
    this.age = 18
}
//  定义子类
function Child1 () {
    //通过call()方法改变Child1的this指向使子类的函数体内执行父级的构造函数从而实现继承效果
    Parent1.call(this)
    this.address = '洪山区'
}
//  构建子类的实例s1
var s1 = new Child1()
console.log(s1.name)  //陈楚
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
//  父类添加say方法
Parent1.prototype.say = function () {
    console.log('say bye bye')
}
//  子类中直接打印这个say方法
console.log(s1.say())  //报错
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

总结:构造函数继承法只能实现部分继承,如果我们在父类Parent1的原型链上添加属性或者方法的时候子类的实例无法继承到。

2. 借助原型链实现继承

function Parent2 () {
    this.name = '祝敏',
    this.age = 19,
    this.play = [1,2,3]
}
//  一样在父类添加say方法
Parent2.prototype = {
    say () {
        console.log('say bye bye')
    }
}
function Child2 () {
    this.address = '硚口区'
}
// 让子类的原型直接等于父类实例
Child2.prototype = new Parent2()
//  生成两个子类的实例s2、s3
var s2 = new Child2()
var s3 = new Child2()
// s2实例继承了父类中的name属性
console.log(s2.name)  //祝敏
//  s2实例也同样继承了父类原型上的say方法
console.log(s2.say())  //say bye bye
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
//  给s2实例继承的play属性的数组中push一个新数字
s2.play.push(4)
console.log(s2.play)  //[1, 2, 3, 4]
console.log(s3.play)  //[1, 2, 3, 4]
  • 1
  • 2
  • 3
  • 4

总结:借助原型链实现继承虽然解决了父类原型的方法能让子类实例对象继承的问题,但是如果我们通过子类的实例对象修改父类上的属性和方法,那么所有子类的所有实例对象上的属性和方法都会被改变。

3. 组合继承

function Parent3 () {
    this.name = '许风',
    this.age = 20,
    this.play = [4,5,6]
}
function Child3 () {
    Parent3.call(this)
    this.address = '江夏区'
}
Child3.prototype = new Parent3()
var s4 = new Child3()
var s5 = new Child3()
s4.play.push(7)
console.log(s4.play)  //  [4, 5, 6, 7]
console.log(s5.play)  //  [4, 5, 6]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

总结:通过call()方法改变子类的this指向然后将子类的原型对象等于父类的实例,从而实现了我们想要的效果。但是确存在着两个问题,第一个问题是性能占用,在call()方法和Child3.prototype = new Parent3()两次都调用了父级的构造函数,造成了不必要的性能浪费。第二个问题等优化了第一个问题之后我们再来看。

js面向对象的理解

概念

把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。
对同类对象抽象出其共性,形成类。
类中的大多数数据,只能用本类的方法进行处理。
类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。
程序流程由用户在使用中决定。
  • 1
  • 2
  • 3
  • 4
  • 5

理解

①面向对象是相对面向过程而言
②面向对象和面向过程都是一种思想
③面向过程
  强调的是功能行为
  关注的是解决问题需要哪些步骤
④面向对象
  将功能封装进对象,强调具备了功能的对象
  关注的是解决问题需要哪些对象
⑤面向对象是基于面向过程的。

面向对象的四大特性

多态性、 继承性、 抽象性、 封装性
  • 1

浏览器的缓存机制

概念

强制缓存优先于协商缓存进行,
若强制缓存(Expires和Cache-Control)生效则直接使用缓存,
若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),
协商缓存由服务器决定是否使用缓存,若协商缓存失效,
那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,
再存入浏览器缓存中;生效则返回304,继续使用缓存
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存

强缓存

不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:ExpiresCache-Control
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,协商缓存可以通过设置两种 HTTP Header 实现:Last-ModifiedETag

  1. Last-Modified和If-Modified-Since

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;

浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。

  1. ETag和If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

  1. 两者之间对比:

首先在精确度上,Etag要优于Last-Modified。
Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。

第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。

第三在优先级上,服务器校验优先考虑Etag

应用

1.频繁变动的资源

Cache-Control: no-cache
  • 1

对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

2.不常变化的资源

Cache-Control: max-age=31536000
  • 1

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

重绘与回流

重绘

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

回流

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

区别

回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流
当页面布局和几何属性改变时就需要回流
比如:添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变——边距、填充、边框、宽度和高度,内容改变

减少重绘回流的方法

不在布局信息改变时做 DOM 查询
使⽤ cssText 或者 className ⼀次性改变属性
使⽤ fragment
对于多次重排的元素,如动画,使⽤绝对定位脱离⽂档流,让他的改变不影响到其他元素
  • 1
  • 2
  • 3
  • 4

this指向问题

函数作为对象本身属性调用的时候,this 指向对象
  • 1
  1.call:参数1 this指向,参数2 任意类型

  2.apply:参数1 this指向,参数2 数组 (参数一为null指向的是本身)

  3.var一个变量保存this指向

  4.使用es6的箭头函数

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
	let obj={
        a:222,
        fn:function(){ 
               console.log(this)    //obj
            setTimeout(function(){
               console.log(this)    //window
            })
        }
    };
    obj.fn();







let obj={
    a:222,
    fn:function(){           
        setTimeout(()=>{
            console.log(this) //obj
        	console.log(this.a) //222
        });
    }
};
obj.fn();







//被嵌套的函数独立调用时this默认指向window
var obj = {
	a:2,
	foo:function(){
		   console.log(this) // obj
		  function text(){
		  	console.log(this)  //window
		  }
		  text();
	}
}
obj.foo()







//自执行函数内部中的this指向window

var a = 10;
function foo(){
	(function test(){
		console.log(this);  //window
	})()
}
var obj = {
	a:2,
	foo:foo
}
obj.foo();






//闭包 this默认指向了window

var a = 10;
var obj = {
	a:2,
	foo:function(){
		var c  = this.a
		return function test(){
			console.log(this)
			return c
		}
	}
}

var fn = obj.foo()
fn()




// 隐式丢失this的五种情况
// 1、函数赋值给另外变量
var a = 0;
function foo(){
	console.log(this) //window
	console.log(this.a)
}

var obj = {
	a:1,
	foo:foo
}
var bar = obj.foo;
bar();


//2、参数传递
var a = 0;
function foo(){
	console.log(this)
}

function bar(fn){
       fn()
}
var obj={
	a:1,
	foo:foo,
}
bar(obj.foo);

// 3、内置函数  setTimeout、setInterval第一个参数的回调函数中的this默认指向window
var a  = 0;
var obj = {
	a:1,
	foo:function(){
		console.log(this.a)
	}
}

setTimeout(obj.foo, 2000)





// 4、间接调用
function foo(){
    console.log(this.a)
}
var a = 0;
var obj={
	a:1,
	foo:foo
}

var p = {a:4};
obj.foo(); //1
(p.foo = obj.foo)(); //0





// 显式绑定
1、call,apply, bind
2、数组的forEach等方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159

call、apply与bind有什么区别?

  • call、apply与bind都用于改变this绑定,但call、apply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上方例子中bind后还加了一对括号
    ()的原因。
  • bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
  • call与apply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。

事件循环机制(Event Loop)的理解

  1. 事件循环开始时首先是整段代码作为宏任务进入主线程执行
  2. 主线程如果发现异步事件,将异步事件移交给异步模块,主线程继续执行后面的同步事件
  3. 当异步进程执行完毕之后,再回到事件队列中
  4. 主线程执行完毕,就会查询事件队列,如果事件队列中存在事件,事件队列就将事件推到主线程
  5. 主线程执行事件队列推过来的事件,执行完毕后再去事件队列中查询…以此循环,直到全部的任务都执行完成。 这个循环的过程就是事件循环
    同一事件循环中,微任务永远在宏任务之前

js中宏任务微任务

1.简单理解同步异步、宏任务和微任务

js是单线程的,所有的任务都要排队挨个执行,就好比做保健(执行js代码),保健师傅只有一个(单线程),顾客(js代码)需排队享受服务,排队的顺序按照顾客的种类(同步异步、宏任务微任务)和顾客到店顺序(在代码中的位置)执行;

同步与异步、宏任务和微任务分别是函数两个不同维度的描述。

异步任务:setTimeout和setInterval、ajax、事件绑定等

同步任务:除了异步任务外的所有任务

微任务:process.nextTick和 Promise后的then语句和catch语句等

宏任务:除了微任务以外的所有任务

2.执行顺序判断方法

先同步再异步,在此基础上先宏任务再微任务

setTimeout(function () {
new Promise(function (resolve, reject) {
console.log('异步宏任务promise');
resolve();
}).then(function () {
console.log('异步微任务then')
})
console.log('异步宏任务');
}, 0)
new Promise(function (resolve, reject) {
console.log('同步宏任务promise');
resolve();
}).then(function () {
console.log('同步微任务then')
})
console.log('同步宏任务')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在这里插入图片描述

函数声明与变量声明的优先级

函数声明会覆盖变量声明,但不会覆盖变量赋值

function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"
  • 1
  • 2
  • 3
  • 4
  • 5
function value(){
    return 1;
}
var value = 1;
alert(typeof value);    //"number"
  • 1
  • 2
  • 3
  • 4
  • 5

闭包相关

一句话解释:
能够读取其他函数内部变量的函数。

稍全面的回答:

在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

用处:
1、可以读取函数内部的变量
2、让这些变量的值始终保持在内存中。不会在函数调用后被清除

应用场景
在开发中, 其实我们随处可见闭包的身影, 大部分前端 JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用

  for (var i = 0; i < 10; i++) {
    setTimeout(function () {
      console.log(i) //10个10
    }, 1000)
  }
  • 1
  • 2
  • 3
  • 4
  • 5

js是单线程的,在执行for循环的时候,定时器被放到任务队列中等待执行,等到定时器可以执行的时候,for循环已经跑完了,此时i的值为10,因此打印出10个10

  for (var i = 0; i < 10; i++) {
    ((j) => {
      setTimeout(function () {
        console.log(j) //1-10
      }, 1000)
    })(i)
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们可以根据闭包的知识来更改一下for循环中的逻辑,利用闭包将i的值传递给a,或者如下

for (let i = 0; i < 10; i++) {
   setTimeout(() => {
      console.log(i);
   }, 1000 * i)
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
var的作用域是全局的,且有变量提升,因此for中定义一个变量,全局可以使用,循环中的每一次给变量i赋值都是给全局变量i赋值
let是块级作用域,只在代码块中起作用,在js中一个{}中的语句我们也称为叫一个代码块,每次循环会产生一个代码块,每个代码块中的都是一个新的变量
  • 1
  • 2
  • 通过循环给页面上多个dom节点绑定事件
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
  (function() {
    var temp = i // 调用时局部变量
    list[i].onclick = function() {
      alert(temp+1)
      console.log(temp+1)
    }
  })()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 封装私有变量
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();
 
alert(user.getUserInfo());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
我们用下划线来约定私有变量 __name 和 __age ,
它们被封装在闭包产生的作用域中,外部是访问不到这两个变量的,
这就避免了对全局的命令污染。
  • 1
  • 2
  • 3

new一个对象, 这个过程中发生了什么

var obj = new Object("name","sansan");
  • 1
 我们创建出一个空对象

 将构造函数的作用域赋值给了新对象,也就是说谁被new了,this就指向谁
 
 去执行构造函数内部的代码

 最终返回到this
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

数组去重的几种方法

//思路:主要是利用filter()方法过滤掉重复的元素
function ArrayToHeavy(arr) {
    //过滤掉原数组中重复的数字,返回新的数组
    return arr.filter((item, index)=> {
        //遍历出数组中数字第一次出现的下标,与数字所在数组的下标相比较,
        //为true就是第一次出现
        return arr.indexOf(item) === index
    })
}
let arr =[1,21,2,24,3,3,7,4,4,5,5]
console.log(ArrayToHeavy(arr))
//打印的是 1, 21, 2, 24, 3, 7, 4, 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
//思路:主要是利用indexOf()方法判断传入的数组值的是否在新数组存在,不存在就把传值push到新数组
function ArrayToHeavy(arr){
    //新建一个空数组
    let newArr = [];
    for(var i = 0; i < arr.length; i++ ){
        //遍历传入的数组,查找传入数组的值是否存在新数组中
         if(newArr.indexOf(arr[i]) === -1){
             //不存在就把值push到新数组
            newArr.push(arr[i]);
         }
    }
    //返回新的数组
    return newArr;
}
let a = [1,1,2,3,4,5,6,4,6,8,65,77];
console.log(ArrayToHeavy(a));
//打印的是 [1, 2, 3, 4, 5, 6, 8, 65, 77]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
//思路:利用双重for循环找出重复的元素,然后在使用splice()方法删除重复的一个
function ArrayToHeavy(arr) {
    //遍历数组中所有的元素
    for(var i = 0,len = arr.length; i < len; i++){
        for(var v = i + 1; v < len; v++){
            //检查是否有重复的元素
            if(arr[i] === arr[v]){
                //有,就从数组中去除
                arr.splice(v,1);
                // splice方法会改变数组长度,所以要将数组长度 len 和下标 v 减一
                len--;
                v--;
            }
        }
    }
    return arr
}
let a = [2,4,5,7,4,8,0,4,5,7,9,4,5,21];
console.log(ArrayToHeavy(a));
//打印 [2, 4, 5, 7, 8, 0, 9, 21]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
[...new Set(arr)] 
set是ES6中引入的新的数据类型。set只允许存储不重复的值,所以当你放入一个数组,它会自动去掉重复的值。

首先,我们通过原数组array创建了一个新的set,所有的重复值都被去除了。
然后,我们通过展开运算符…转换回了数组
const array = [' ', 1,  2, ' ',' ', 3];
​
// Step 1
const uniqueSet = new Set(array);
// Set { ' ', 1, 2, 3 }
​
// Step 2
const backToArray = [...uniqueSet];
// [' ', 1, 2, 3]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
const array = [' ', 1,  2, ' ',' ', 3];
​
Array.from(new Set(array));
​
// [' ', 1, 2, 3]
  • 1
  • 2
  • 3
  • 4
  • 5
 practice = (arr) => {
    return arr.reduce((prev, cur) => {
     return prev.includes(cur)?prev:prev.concat(cur)
     //return prev.includes(cur)?prev:[...prev,cur]
    }, [])
  }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

服务器端与浏览器端的Cookie交互

  • Cookie是存储在客户端上的一小段数据,浏览器(即客户端)通过HTTP协议和服务器端进行Cookie交互。
  • 浏览器将后台传递过来的cookie进行管理,并且允许开发者在JavaScript中使用document.cookie来存取cookie。
  • 针对登录过的用户,服务器端会在他登录时往 Cookie 中插入一段加密过的唯一辨识单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登录

cookies,sessionStorage 和 localStorage 的区别 ?

cookie 是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
cookie 数据始终在同源的 http 请求中携带(即使不需要),也会在浏览器和服务器间来回传递。
sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。
  • 1
  • 2
  • 3

存储大小

cookie 数据大小不能超过 4k。
sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大
  • 1
  • 2

有期时间

localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
sessionStorage 数据在当前浏览器窗口关闭后自动删除。
cookie  设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。
  • 1
  • 2
  • 3

常见兼容性问题

浏览器默认的 margin 和 padding 不同。解决方案是加一个全局的 *{margin: 0; padding: 0;} 来统一。

IE下 event 对象有 event.x,event.y 属性,而 Firefox 下没有。Firefox 下有 event.pageX,event.PageY 属性,而 IE 下没有。 解决办法:var mx = event.x?event.x:event.pageX;

Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示, 可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.

超链接访问过后 hover 样式就不出现了,被点击访问过的超链接样式不在具有 hover 和 active 了,解决方法是改变 CSS 属性的排列顺序: L-V-H-A : a:link {} a:visited {} a:hover {} a:active {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

从敲入 URL 到渲染完成的整个过程,包括 DOM 构建的过程,说的约详细越好

1、首先,在浏览器地址栏中输入url

2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。

3、若没有缓存则域名解析(DNS解析),解析获取相应的IP地址。

4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。

5、握手成功后,浏览器向服务器发送http请求,请求数据包。

6、服务器处理收到的请求,将数据返回至浏览器

7、浏览器收到HTTP响应

8、读取页面内容,浏览器渲染,解析html源码

9、生成Dom树、解析css样式、js交互
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

常见的http状态码

1**     信息,服务器收到请求,需要请求者继续执行操作
2**     成功,操作被成功接收并处理
3**     重定向,需要进一步的操作以完成请求
4**     客户端错误,请求包含语法错误或无法完成请求
5**     服务器错误,服务器在处理请求的过程中发生了错误
  • 1
  • 2
  • 3
  • 4
  • 5

301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替

302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI

304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源

400 Bad Request 客户端请求的语法错误,服务器无法理解

401 Unauthorized 请求要求用户的身份认证

403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求

404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面

JSON的了解

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于 JavaScript 的一个子集。
数据格式简单,易于读写,占用带宽小。
格式:采用键值对。例如:{ “age‟: ‟12‟, ”name‟: ‟back‟ }
  • 1
  • 2
  • 3
  • 4

怎么理解js是一门弱类型语言

在javascript中,弱类型是指数据类型可以被忽略,一个变量可以赋不同数据类型的值。javascript是一种弱类型语言,它允许变量类型的隐式转换,允许强制类型转换等,如字符串和数值可以自动转化;而强类型语言一般不允许这么做。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/339798
推荐阅读
相关标签
  

闽ICP备14008679号