【22届】【秋招】前端向复习指北(下)

849 阅读11分钟

https://pic1.zhimg.com/80/v2-cb0e4f8cfc2ba10cdb1ae881b005d1c7_720w.jpg?source=1940ef5c

JavaScript篇

ch1 - 理论

数据类型相关

数据类型分类

JS的数据类型可以分为两大类:基本数据类型引用数据类型

  • 基本数据类型:Number、 String、 Boolean、 Undefined、 Null、 (symbol、 bigint)
  • 引用数据类型:Function、Object、Array

数据类型区别

  • 两种类型的区别是:
    基本类型存储在内存栈中;
    引用类型将数据地址存储在内存栈中,实际的数据内容存储在堆中。
  • 典型的例子如下:
let a = [1,2], b = a
b.push(3)
console.log(a===b) // true

由于a,b保存的地址相同,所以对于数据内容的更改不会影响到a与b的相等性。

判断数据类型的方式

  1. typeof | 类型 | 结果 | | :---------------------------- | :------------ | | Undefined | "undefined" | | Null | "object" | | Boolean | "boolean" | | Number | "number" | | BigInt | "bigint" | | String | "string" | | Symbol | "symbol" | | Functin | "function" | | 其他任何对象(Object,Array) | "object" |

typeof能够精准的识别基本类型,但对于引用类型无法进行区分。

  1. instanceof 一般用于判断引用类型。
[1,2] instanceof Array //true

判断简单类型时会出现如下错误:

var str = 'hello world'
str instanceof String // false

var str1 = new String('hello world')
str1 instanceof String // true
  1. Object.prototype.toString 可以做到精准识别。
Object.prototype.toString.call(99) //"[object Number]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call({}) //"[object Object]"

原型与原型链

先来一张图,给个直观理解(个人认为比高程之类的书上给的要简单易懂哈哈哈) 莫嫌字丑哈哈哈,用平板画的。 原型链

原型

可以看到图上的 #605 #809 就是原型。

原型在哪里?

window对象下面挂载了许多内置函数:Object,Array,Function等等,我们习惯称他们为构造函数。而每个构造函数都有一个prototype属性,prototype的指向就是我们一直说的原型

原型是什么?

原型的本质其实就是一个对象。它提供了对应类型的共同方法和属性。
如:Array.prototype原型,提供了数组独有的push,pop方法;
Function.prototype原型,提供了函数类型特有的call方法。

如何找到原型?
  • 利用 __proto__ 属性(不推荐)
[1,2,3].__proto__ === Array.prototype  //true
  • 利用 Object.getPrototypeof(instance)
Object.getPrototypeOf([1,2]) === Array.prototype  //true

原型链

为什么我们从没有未数组声明过方法,但却可以调用push,pop等方法呢?

const arr = []
arr.push(1) // [1]
逐级查找

js在当前对象上找不到对应的方法后,会沿着原型链逐层查找
例如:

const arr = []
arr.push(1)	// [1]
arr.hasOwnProperty("keys")	//true
arr.sayHi()	//Uncaught TypeError: arr.sayHi is not a function
  • arr上没有push方法;沿原型链找到数组原型Array.prototype上存在push方法,则调用。
  • arr上没有hasOwnProperty;沿原型链找到数组原型Array.prototype,也没有hasOwnProperty方法;继续沿原型链查找,由于"数组原型"本质上也是一个对象实例,所以"数组原型"的原型是Object.prototype,发现存在hasOwnProperty,则调用。
  • arr上、数组原型、对象原型上都没有这个方法;而对象原型是原型链的尽头 Object.getPrototypeof(Object.prototype) === null,所以这个方法不存在,直接报错。

JavaScript中的OOP

new做了什么

function Person(name){ this.name = name }
Person.prototype.say = function() { console.log( this.name )}
let p = new Person("Lee")
  1. 创建一个空对象 let obj = {}
  2. obj的__proto__指向构造函数的ptototype
    obj.__proto__ = Person.prototype
    
  3. 执行Person.call(obj)
  4. 若构造函数显式return一个对象则返回该对象;否则返回obj。

寄生组合模式

function Person(name){ this.name = name }
Person.prototype.say = function() { console.log( this.name )}
function Children(age) { 
	Person.call(this); 
    this.age = age
}
Children.prototype = Object.create(Parent.prototype,{constructor: Children})
/*	
	等价于 
	Child.prototype.__proto__ = Person.prototype;	
	Child.prototype.constructor = Child;
*/

class模式

class Person{
	constructor(name){
    	this.name = name
    }
    say(){ console.log(this.name) }
}

/*	
	extends 等价于 
	Child.prototype.__proto__ = Person.prototype;	
	Child.prototype.constructor = Child;
*/
class Children extends Person{
	constructor(name, age){
    	super(name)	//等价于 Parent.call(this,name);
    	this.age = age
    }
}

AJAX

  • 全称:async JavaScript and xml
    js通过异步通讯获得服务端的xml更新页面

原生ajax:

const xhr = XMLHttpRequest()
xhr.open('GET','/')
xhr.onreadystatechange = ()=>{
	if(xhr.readyState===4 && xhr.status===200){
    	console.log(xhr.responseText)
    }
}
xhr.send()

简化版ajax:

const xhr = XMLHttpRequest()
xhr.open('GET','/path?query=x')
xhr.onload(()=>{console.log(xhr.responseText)})
xhr.onerrer(()=>{console.log('error')})
xhr.send()

事件机制

事件捕获:先父后子,先外后里。
事件冒泡:先子后父,先里后外。

  • 阻止冒泡:event.stopPropagation()
  • addEventListener("click", fn , false):第三个参数为是否在捕获阶段执行。默认false,即在冒泡阶段执行。

正则相关

参考:juejin.cn/post/699389…

ch2 - 运行机制

变量提升

  • 之所以需要实现变量提升,是因为JS机制:先编译,再运行
  • 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值被设为undefined;在代码执⾏阶 段,JavaScript引擎会从变量环境中去查找定义的变量和函数。
  • 如果在编译阶段,存在两个函数名相同的函数定义,那么最终存放在变量环境中的是最后定义的那个。
  • 函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。
showName()
var showName = function() { console.log(2) } 
function showName() { console.log(1) }
showName() // 1 , 2

/ *** 等价于 *** /

function showName() { console.log(1) }
var showName	//函数提升不会被变量声明覆盖
showName()
showName = function() { console.log(2) }

调用栈和执行上下文

  • 每调用一个函数,JS引擎则编译对应的函数代码并创建执行上下文(包括变量环境对象、词法环境对象、this引用),压入调用栈。随后由JS引擎执行代码
  • 函数执行完毕后,清理函数内变量占用的内存,将该执行上下文弹栈。
  • 当调用栈中的执行上下文个数达到一定数量后会发生堆栈溢出

作用域

定义:作用域是指在程序中定义变量的区域。

全局作用域

js任何位置都可以访问到全局作用域中的变量

函数作用域

函数内部定义的变量或者函数只能在函数内部被访问。函数执⾏结束之后被销毁。

块级作用域
  1. 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境⾥⾯了。
  2. 通过 let 声明的变量,在编译阶段会被存放到词法环境中。
  3. 函数块级作用域中,通过let声明的变量被集中存储在一个区域内,并被压入词法环境对象的顶部。(词法环境内部,维护了⼀个⼩型栈结构。)

IMG_2052.PNG 4. 暂时性死区:在块级作用域中,若变量使用 let / const 声明,则在声明之前使用该变量会报错。

作用域链

词法作用域:

作用域是由代码中函数声明的位置决定的,是静态的。 作用域链由词法作用域决定。

  • 每个函数声明时会创建一个函数声明对象,该对象的 [[scope]] 属性指向声明该函数的执行上下文。

  • 每个变量环境包含一个 outer 属性,指向外部执行上下文(该函数的声明对象中的 [[scope]] 属性对应的上下文)。

  • 全局上下文中变量环境的outer属性为 null,即全局上下文是作用域链的终点。

作用域链定义:

  1. 定义:本质上是一个执行上下文的指针列表。
  • 作用:保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链可以访问到外层环境的变量和函数。

变量查找方式:

  • 词法环境对象栈顶
  • ==> 词法环境对象栈底
  • ==> 变量环境对象
  • ==> outer对应的执行上下文
  • ==> 按作用域链进行查找,直到全局执行上下文:outernull

闭包

  • 定义闭包 = 函数 + 对其词法环境的引用。(MDN官方定义)

由于函数声明的函数对象[[scope]]属性会记录外层词法环境的特点,可以通过return一个函数来保留当前执行上下文中的变量。

  • 用途:
    1. 闭包的第一个用途是:使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量
    2. 闭包的另一个用途是:使已经运行结束的函数上下文中的函数对象继续留在内存中,因为函数对象的[scope]属性保留了对词法环境的引用,所以这个词法环境不会被回收。
    3. setTimeout 等回调函数。

this用法

image.png

  • 箭头函数:不会创建执行上下文,没有自己的this。

    let obj = {
        num: 1,
        prin: function(){
            //父级作用域 此处保存着父级的this
            setTimeout(()=>{
                console.log(this.num);
            },1000)
        }
    }
    
    obj.prin()	// 1
    //实际上就是闭包的应用。
    
  • 绑定丢失:隐式绑定作为回调函数时,会出现绑定丢失的情况。

    let user = {
      firstName: "John",
      sayHi() {
        alert(`Hello, ${this.firstName}!`);
      }
    };
    
    setTimeout(user.sayHi, 1000); // Hello, undefined!
    

EventLoop

ch3 - ES6

let和var的区别

  1. let块级作用域
  2. let不存在变量提升(function和var都存在变量提升,function优先)
  3. 存在暂时性死区,如果在变量声名前使用会报错
  4. let不允许重复声名
  5. 在全局上下文中声明的var变量会成为window的属性;let不会
  6. var变量在执行上下文的变量环境对象中注册,let const变量在执行上下文的词法环境对象中注册。

promise

我的博客:juejin.cn/post/692429…

解构赋值

  • 交换变量 [a,b] = [b,a]

  • 参数处理

    function (args){
    	const {num,sum} = args
    }
    

String

  • 模板字符串

    `名字:${name}`
    
  • includes,startsWith,endsWith

Array

  • Array.from

  • arr.fill

  • arr.includes

  • arr.flat

    arr.flat(n)	//n为想拉平的层数
    [1, [2, [3]]].flat(Infinity)	//拉平无限层,则拉平为一维数组
    

Object

  • Object.assign( target, obj1, obj2 ) 浅复制
  • Object.getPrototypeof( obj )

ch4 - 编程

数组相关

数组的变更方法:在原数组上进行更改

  • sort():可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。
  • reverse():交换次序
  • splice():插入/删除元素方法
  • push,pop,shift,unshift

数组的替换方法:根据原数组返回新数组

  • concat() :数组连接 arr.concat([2,3])
  • slice():用于截取数组中的一部分返回 arr.slice(0,5)
  • indexOf() 和 lastIndexOf() 迭代方法
  • every()、some()、filter()、map() 和 forEach() 方法:对数组项处理,返回结果
  • reduce方法:将数组中的值整合为一个值。
    let arr = [1,2,3]
    arr.reduce((sum,v,idx,a) => sum+v*v, 0)	//14
    

数组去重

  • indexOf + reduce
let fn = arr => arr.reduce((account,v)=>{
	if(account.indexOf(v)===-1) account.push(v)
},[])
  • indexOf + filter
let fn = arr => arr.filter((v,index)=>arr.indexOf(v)===index)
  • Set
let fn = arr => [...new Set(arr)]
  • Map (key的唯一性)
let fn = arr =>{
	let map = new Map()
    arr.forEach(v=>map.set(v,true))
    return [...map.keys()]
}

对象相关

对象的原生方法

  • Object.create( obj ):以obj为原型,创建一个对象
  • Object.assign( target, obj1, obj2 ):将obj1,obj2合并到target中
  • Object.getPrototypeof(obj):获得原型
  • Object.keys( obj ):获取对象的属性列表
  • Object.values( obj ):获取对象的值列表

深浅拷贝

浅拷贝

只进行浅层拷贝,若对象上的属性仍是引用类型则无法实现完全拷贝。

//method 1
let a = { id:1 }
let new_A = Object.assign( {}, a )
//method 2
let a = { id:1 }
let b = {...a}

let a = 1
let b = a
深拷贝

递归进行完全拷贝

  1. JSON.parse(JSON.stringify( obj ))
  2. 通过递归函数
    let deepClone = (obj)=>{
    	if (typeof obj !== "object" && typeof obj !== "function") throw new Error();
    	else {
    		let newObj;
    		if (Array.isArray(obj)) newObj = [...obj];
    		else newObj ={...obj};
    		for (let i in newObj) {
      			if (typeof newObj[i] !== "object") continue;
      			else newObj[i] = deepClone(newObj[i]);
    		}
    	return newObj;
    }
    

真题总结

Object.defineProperty

  • configurable(可改变):
    当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false

  • enumerable(可枚举):
    当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。 数据描述符还具有以下可选键值:

  • value(值):
    该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined

  • writable(可写):

    当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。默认为 false

存取描述符还具有以下可选键值:

  • get
    属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined

  • set
    属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为undefined

对象属性遍历

  1. Object.keys(obj).forEach(v=>{ console.log(obj[v]) })
  2. 使用 for in 方式。注意:for in 方式会同时输出Object.prototype上的属性
    for (let key in obj) {
        console.log(obj[key])
    }
    

写在最后

上篇写过后,公司的项目突然忙了起来,耽误了许久。前段时间的复习热情有些凉了下来。
说实话对于前路还是非常迷茫的。我不知道是否每个前端er都心里清楚且坚定自己要什么。
但我现在知道的是:我是真实地对前端热情充沛,所以我会告诉自己坚定下去,努力下去。

希望和大家共勉!一起加油!