2021面试题

170 阅读6分钟

js

1. 同步与异步

js是单线程,在执行本轮任务的时候,会出现同步和异步。 代码从上到下依次执行,同步代码通过一个个入栈出栈去执行,遇到异步代码时,先发起异步调用,异步中的回调函数会放到任务队列中去等待,等待本轮同步任务全部结束清空栈之后,再去消息队列里去执行异步任务。 异步回调函数中的结果是需要拿到主线程中去运行展示的。 回来的内容结果,在主线程如何接 在发起异步调用的时候,将当前代码里的一个函数传进去,在异步执行回调的时候再去执行外面传入的函数,将异步调用的结果当作实参传到外面传入的这个函数里。

回调函数就是为了解决:异步执行完的结果如何回来让我们获取并使用的

举例:想要给桌子刷油漆,找一个人去买油漆,这个油漆最后怎么回到我手里,就是我给你一个桶,你把油漆装到这个桶里,然后再给我,我拿这个桶就可以了。

函数的调用时需要回到函数声明的地方去执行的, 想要使用异步调用时返回的结果,传入一个函数,将返回结果作为传入函数的实参 通过回调函数的方式,能够让异步的结果回来 两个线程之间传值的问题,就比如我需要在请求到一个接口数据后,再去执行下一个任务,需要依赖前一个的结果的时候,再去发送下一个请求。就需要下一次的请求写在你的回调里面。多个请求形成回调地狱 调试难, 不好看

image.png

2. Promise async/await generator

为了增高代码的可阅读性 出现了promise,现在的所有的异步处理都是基于promise的,可能写法不太一样 无论异步成功失败都会告诉你 异步操作之后会有三种状态

  • 我们一起来看一下这三种状态:

    1. pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
    2. fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
    3. reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

如果多个任务的话,使用promise,会频繁的.then,链式调用过多

image.png

此时就出现了async和await

image.png

image.png async/await 是个语法糖,是generator的语法糖 给函数声明的时候,在函数的后面加个 * ,这个函数一旦调用,返回的叫生成器对象,里面的代码通通不执行。什么时候执行呢?这个生成器的对象有一个方法叫next(),当去调用next()的时候才会去执行,每调用一次代码只会执行到yields后面的代码;下一次再调用next()才会继续执行后面的代码 。 yield 代表停

![image.png](p6-juejin.byteimg.com/tos-cn-i-k3… 3u1fbpfcp-watermark.image?)

image.png 如果有五次

image.png

image.png

image.png

image.png

什么是栈和堆

栈先进后出 堆通过优先级分配

js内置对象

什么是原型

在js中是使用一个构造函数来新建一个对象的,每一个构造函数内部都有一个prototype属性,这个属性是个对象,这个对象包含了可以由构造函数的所有实例的共享的属性和方法。使用构造函数新建一个对象后,这个对象的内部包含将包含一个指针,这个指针指向构造函数prototype属性对应的值,在es5中,这个指针被称为对象的原型。

一般来说,我们是不应该能获取到这个值的,现在浏览器中都实现了--proto--属性来让我们访问这个属性。最好不要使用这个属性,es5中新增了object.getPrototypeOf方法来获取到对象的原型。

当访问一个对象的属性时,如果内部不存在这个属性,会去原型对象里去找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,这就是原型链的概念,原型链的尽头是object.prototype,所以这就是新建的对象为什么可以使用toString的原因。它们有什么特点,js对象是通过引用来传递的,我们创建的每一个新对象实体中并没有自己的原型副本,当我们修改原型时,与之相关的对象也会继承这一改变。

js作用域链

内部的函数使用变量,会在当前作用域下,没有的话会到上一层找,一直找到全局环境,如果没找到会报错

箭头函数和普通函数

function fun() {
    return 100;
}
const fun = function() {
    return 100;
}
const fun = () => {
    return 100
}
// 箭头函数 () 定义参数,如果只有一个参数,可以不写括号
// 箭头函数如果返回值只有一行代码,可以不写return,直接写返回值
const fun = () => 100

console.log(fun()) // 100 这里调用上面四个函数是没有区别的

// 区别:this指向不同,普通函数谁调用this指向谁;
//                   箭头函数在哪里定义函数,this指向谁,指向外部作用域
// 普通函数
let obj = {
    name: '小明',
    age: '12'sayName() {
        let self = this
        //2.// console.log(this) // obj对象
        //1.// console.log(`我是${this.name}`)
        setTimeout(funtion(){
            //2.//console.log(this) // window
            console.log(self) // obj对象
        })
    }
}

//1.// obj.sayName() // 我是小明

obj.sayName()

// 箭头函数
let obj = {
    name: '小明',
    age: '12'sayName() {
        setTimeout(funtion(){
            // console.log(this) // obj对象
            console.log(this.name)
        })
    }
}

obj.sayName()





this对象的理解

this是执行上下文中的一个属性,它指向的是最后一次调用这个方法的对象。 在开发中,this的指向可以通过四种模式来判断 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象; 调用模式,如果一个函数作为一个对象的方法来调用时,this指向这个对象; 构造器调用模式,如果一个函数用new调用时,函数执行前,会先创建一个对象,this指向新创建的这个对象; call、apply、bind调用时, 这三个方法都可以显式的指定调用函数的this指向, 其中apply接收两个参数,一个是this绑定的对象,一个是参数数组。 call接收的参数,一个是this绑定的对象,后面其余的参数是函数执行的参数,在使用call方法时,传递给函数的参数必须逐个列举出来。 bind方法传入一个对象,返回了一个this绑定了传入对象的新函数,这个函数的this除了使用new时会被改变,其他情况下都不会被改变

this关键字

image.png

// 1. 直接输出this指向全局对象
console.log(this)  // window

// 2. 全局函数其实是window(全局对象)的方法
function fun () {
    console.log(this)
}

fun () // window

// 3. this放在方法中,this指向调用这个方法的对象
let cat = {
    name: '喵喵'sayName() {
        console.log("我是" + this.name)
    }
}
cat.sayName() // 我是喵喵

// 4. dom事件中的对象this指向这个dom对象
<button>按钮</button>

const btn = document.querySelector("button")
btn.onclick = function () {
    console.log (this) // button
}

// 5. 构造函数中的this指向的是new创建的这个对象
 function F() {
    this.name = "小明"
}   
let f = new F()
console.log(f) // 会输出整个大的F对象


// 6. 箭头函数中没有this
// 普通函数,谁调用指向谁
// 箭头函数外指向谁就指向谁

let cat = {
    name: '喵喵',
    sayName() {
        // console.log(this) // {name : "喵喵", sayName: f}
        setTimeout(function() {
            console.log(this) // window
        }, 1000)
    }
}

let cat = {
    name: '喵喵',
    sayName() {
        // setTimeout是window的一个方法,如果改成箭头函数就可以指向外面的this
        setTimeout(() => {
            console.log(this) // {name : "喵喵", sayName: f}
        }, 1000)
    }
}

new做了什么

// 构造函数是用来创建对象的
// new 关键字会创建一个对象;将构造函数中的this指向创建出来的对象
function F() {
    this.name = "小明"
}   
let f = new F()
console.log(f) // 会输出整个大的F对象
    

call apply bind

// call是一个方法,是函数的方法
function fun () {
    // console.log('this', this) // window
    console.log(this.name) // 喵喵
}
let cat = {
    name: "喵喵"
}

// call可以调用函数
// call可以改变函数中this的指向

// fun.call() // hello

// 将函数中的this指向cat
fun.call(cat)

let dog = {
    name: "旺财",
    sayName() {
        console.log('我是' + this.name)
    },
    eat(food) {
        console.log("我喜欢吃" + food)
    }
    eats(food1, food2) {
        console.log("我喜欢吃" + food1+food2)
    }
}

let cat = {
    name: "喵喵"
}
// dog.sayName() // 我是旺财

// 让猫去用狗的sayName
// dog.sayName.call(cat) // 我是喵喵

// call第一个参数是要改变this的对象,第二个参数是要给eat传的参数,参数可以依次往后传
dog.eat.call(cat, "鱼") // 我喜欢吃鱼
dog.eats.call(cat, "鱼", "肉") // 我喜欢吃鱼肉

// apply的区别是参数二是数组
dog.eat.apply(cat, ["鱼", "肉"]) // 我喜欢吃鱼肉

// bind 传参方式和call一样
// bind不会调用这个函数,会返回一个函数
dog.eat.bind(cat, "鱼", "肉") // 什么都没输出

let fun = dog.eat.bind(cat, "鱼", "肉")
fun() // 我喜欢吃鱼肉


基于call继承

// 继承:子类可以使用父类的方法

// call实现多重继承
function Animal () {
    // 2. this也就指向小cat了
    this.eat = function () {
        console.log("吃东西")
    }
}

function Bird () {
    // 2. this也就指向小cat了
    this.fly = function () {
        console.log("飞翔")
    }
}

function Cat() {
    // 1. this指向小cat
   Animal.call(this) 
   Bird.call(this)
}


let cat = new Cat()
// 3. 小cat就可以调用eat
cat.eat() // 吃东西

cat.fly() // 飞翔


什么是闭包,为什么用它

闭包是指有权访问另一个函数作用域中变量的函数 创建闭包最常见的方式,在一个函数内,创建另一个函数,创建的函数可以访问当前函数的局部变量 闭包两个常用的用途 一是在函数外部能访问到函数内部的变量,通过使用闭包可以通过外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法创建私有变量 二是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包保留了这个对象的引用,所以这个变量对象不会被回收,

闭包的本质就是作用域链的一个特殊的引用,只要了解了作用域链的创建过程就能了解闭包的实现原理

// 闭包:函数嵌套函数,内部函数就是闭包
// 正常情况下,函数执行完之后,里面的内部变量会被销毁(释放内存空间)
function outerFun () {
    let a = 10
    function innerFun () {
    // a是可以访问外部变量的
        console.log(a)
    }
    return innerFun
}

let fun = outerFun()
// 闭包,内部函数innerFun没有执行完成,外部函数outerFun变量a不会被销毁
fun() // 10 变量不会被销毁

闭包应用:封装代码实现模块化

定义了两个全局变量,两个全局函数

image.png

使用闭包封装起来

image.png

image.png

ajax是什么,如何创建ajax

ajax就是通过js的异步通信,从服务器获取xml文档,从中提取数据,更新当前页面的对应部分,而不用刷新页面。 ajax分为以下几个步骤:

//1.获取所有要操作的元素
var btn = document.getELementById('btn');
var tb = document.getELementById('tb');
//2. 通过按钮的点击事件,发送请求内容
btn.onclick = function () {
    //2.1 使用ajax进行异步请求
	var xhr = new XMLHttpRequest();
        xhr.open('GET','/ajax/day2/table.php');
        xhr.send(null);
        xhr.onreadystatechange= function () {
            //2.2 进行条件判断,何时可以接收响应体内容	
            if (xhr.readyState === 4 && xhr.status === 200){
            //2.3 使用JSON.parse()方法将得到的响应体内容转换为js中可使用的对象形式
                var datas = JSON.parse(xhr.responseText);
                //2.4 根据响应体内容进行结构的动态创建
		// 遍历得到的二维数组结构
		for (var i = 0; i < datas.length; i++){
                    var tr = document.createElement('tr');
                    tb.appendChild('tr');
			for (var j = 0; j < datas[i].length; j++){
                              var td = document.createElement('td');
                              td.innerText = datas[i][j]; 
                              tr.appendChild(td);
                         }
                  }
                }
            }
   };	

什么是原型

每一个对象都有它的一个原型对象,它可以使用自己原型对象上的属性和方法

let cat ={
    name: '喵喵'
}
cat.__proto__.eat = function () {
    console.log("吃鱼")
}
cat.eat()

获取原型的方式

ES5:

  1. 通过对象的__proto__获取 原型对象,使用上面的方法

  2. 通过构造函数的prototype属性获取 原型对象 ES6:

  3. 通过类的prototype属性 获取原型

// ES5用构造函数创建对象
function Cat (name, age) {
    this.name = name
    this.age = age
}

// 通过构造函数创建对象
let cat = new Cat("喵喵", "2")
Cat.prototype.eat = function () {
    console.log('吃鱼')
}
cat.eat()

原型对象的用处

let date = new Date()
console.log()
// 通过原型扩展date对象,
// 构造函数的prototype可以获取原型,给原型加一个方法
Date.prototype.formate = function () {
    let year = this.getFullYear()
    let month = this.getMonth() + 1
    let date = this.getDate()
    return `${year}${month}${date}日`
}

// 目标:输出**年**月**日
console.log(date.formate())

类 ES6的语法

// es6可以用类创建对象,类可以加属性和方法
clas Cat {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}
Cat.prototype.eat = function () {
    console.log('吃鱼')
}
let cat = new Cat("喵喵", 2)
 cat.eat()
 

什么是继承,ES6通过类继承

管理员有登录、增删改用户 普通人有登录

class User {
    constructor (username, password) {
        this.username = username
        this.password = password
    }
    login () {
        console.log('登录')
    }
}
// Admin 通过extends继承 User类,可以使用里面的属性和login方法
// 自己又新增了删除方法
class Admin extends User {
    deletePerson () {
        console.log('删除一个人')
    }
}

let admin = new Admin()
admin.login()
admin.deletePerson()

继承 ES5通过构造函数基于原型继承

两个构造函数如何建立联系 Admin可以使用User上的方法, Admin可以使用自己原型对象上的方法 让Admin的原型设置为User的实例方法,User实例有User构造函数的属性和方法

 function User (username, password) {
     this.username = username
     this.password = password
     this.login = function () {
         console.log('登录')
     }
 }
 
 function Admin () {
     this.deletePerson = function () {
         console.log('删除一个人')
     }
 }
 Admin.prototype = new User()
 
 let admin = new Admin()
 admin.login()

什么是原型链

一个对象有原型对象,它的原型对象还有原型对象,一直往上最终找到Object找到原型Object.prototype,所有对象的原型都是Object.prototype

 function User (username, password) {
     this.username = username
     this.password = password
     // this.login = function () {
     //     console.log('登录')
     // }
 }
 
 function Admin () {
     this.deletePerson = function () {
         console.log('删除一个人')
     }
 }
 
 // 如果在Object.prototype加了方法,因为所有对象的最上层都是Object.prototype,那么所有的引用类型都可以调用这个方法,数组,对象,正则表达式,日期对象
 Object.prototype.login = function () {
   console.log('Object原型上的登录方法')
 }
 
 Admin.prototype = new User()
 
 let admin = new Admin()
 admin.login() // Object原型上的登录方法
 
 let arr = [1, 2, 3]
 arr.login() // Object原型上的登录方法