js面试知识点合集

js面试知识点合集

  • 1. 大纲
  • 2. JS基础 变量类型和计算
    • 2.1 typeof能判断哪些类型?
    • 2.2 何时使用=== 何时使用== ?
    • 2.3 值类型和引用类型的区别?
    • 2.4 手写深拷贝
    • 2.5 类型转换的坑
  • 3. 原型和原型链
    • 3.1 如何判断一个变量是不是数组?
    • 3.2 手写一个简易的jquery,考虑插件和扩展性。
    • 3.3 class的原型本质
  • 4. 作用域和闭包
    • 4.1 this的不同应用场景,如何取值?
    • 4.2 手写bind函数
    • 4.3 作用域
    • 4.4 闭包及自由变量
  • 5. 异步
    • 5.1 同步与异步的区别
    • 5.2 promise
    • 5.3 使用异步的场景
  • 6. 异步进阶
    • 6.1 promise
    • 6.2 event loop
    • 6.3 async/await
    • 6.4 宏任务和微任务

1. 大纲

js面试知识点合集_第1张图片

2. JS基础 变量类型和计算

2.1 typeof能判断哪些类型?

基础类型:string number boolean object symbol function
对象类型:Object Array Date
不包含任何值类型:null undefined
js面试知识点合集_第2张图片

2.2 何时使用=== 何时使用== ?

除了判断== null 以外,其他都使用===

2.3 值类型和引用类型的区别?

值类型:值一般都存放在栈中,当一个变量赋值给另一个变量时,两个变量都各有一个值存放在栈中;
引用类型:声明一个变量,该变量存放在栈中,声明一个对象,存放在堆中,将对象赋值给变量后,变量指向的是对象在堆中的地址。
js面试知识点合集_第3张图片

2.4 手写深拷贝

什么是深拷贝?
上个知识点提到引用类型,引用类型是指变量指向的是对象的地址,所以当对对象操作后,变量的值也将会改变,那么如何让变量的值不发生改变就要用到深拷贝,其实就是将对象复制一份出来,重新开辟一块堆空间,让变量指向属于自己的对象。

function deepClone(obj = {}){
    if (typeof(obj) !== 'object' || obj == null){
        return obj;
    }

    let result;
    if (obj instanceof Array){
        result = [];
    }else {
        result = {};
    }

    for (let key in obj){
        if (obj.hasOwnProperty(key)){
            result[key] = deepClone(obj[key]);
        }
    }
    return result;
}

2.5 类型转换的坑

1)if 语句和逻辑运算
truly变量: !!a === true 的变量
falsely变量: !!a === false 的变量
2)字符串拼接
字符串拼接,只要加了字符串,就是字符串

3. 原型和原型链

3.1 如何判断一个变量是不是数组?

使用instanceOf
js面试知识点合集_第4张图片

3.2 手写一个简易的jquery,考虑插件和扩展性。

class JQuery {
    constructor(selector){
        const result = document.querySelectorAll(selector);
        const length = result.length;

        for (let i = 0; i < length; i++){
            this[i] = result[i];
        }

        this.length = length;
        this.selector = selector;
    }

    get(index){
        return this[index];
    }

    each(fn){
        for (let i = 0; i < this.length; i++){
            const elem = this[i];
            fn(elem);
        }
    }

    on(type,fn){
        return this.each((elem)=>{
            elem.addEventListener(type,fn,false);
        })
    }
}

const $p = new JQuery('p');
console.log($p.get(1));
$p.on('click',(index)=>{
    alert('a')
})

JQuery.prototype.dialog = function(info){
    alert(info);
}

class MyJQuery extends JQuery {
    add(){};
    sub(){}
}

3.3 class的原型本质

class Person {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }

    sayHi(){
        console.log( "say hi");
    }
}

class Student extends Person{
    constructor(name,age,subject,num){
        super(name,age);
        this.name = name;
        this.num = num;
        this.subject = subject;
        this.age = age;
    }

    study(){
        console.log(this.name + ' study '+ this.subject + ',score is ' + this.num);
    }
}

const xialuo = new Student('xialuo',18,'English' ,99);
console.log(xialuo);
xialuo.sayHi();
xialuo.study();

上面代码定义了一个父类Person,一个子类Student,一个实例xialuo。Student通过关键字extends继承了父类Person的属性。那么三者之间有什么关系呢?
js面试知识点合集_第5张图片
通过例子可以看出来:
xiaoluo.proto === Student.prototype
Student.prototype.proto === Person.prototype
Person.prototype.proto === Object.prototype
Object.prototype.proto = null
js面试知识点合集_第6张图片
总结:

  • 每个class都有显示原型
  • 每个实例都有隐式原型
  • 实例的__proto__都指向对应的class的显示原型prototype.
    基于原型的执行规则:
  • 先在自身属性和方法寻找,如果找不到则自动去__proto__中寻找。

4. 作用域和闭包

4.1 this的不同应用场景,如何取值?

最重要的一点是:this取值是在函数执行的时候决定,不是在函数定义的时候决定。

function fn1(){
    console.log(this) //this指向window
}
fn1(); //普通函数调用
fn1.call({x:100}); //call调用返回对象
let fn2 = fn1.bind({x:200});//bind调用返回对象
fn2();

const zhangsan = {
    name: '张三',
    sayHi: function(){
        console.log(this); //返回对象本身
    },
    wait(){
        setTimeout(function(){
            console.log(this) //window
        },0)
    }
}

const lisi = {
    name: 'lisi',
    sayHi(){
        console.log(this);//返回对象本身
    },
    waitAgain(){
        setTimeout(()=>{
            console.log(this);//返回对象本身
        },0)
    }
}

class People{
    constructor(name){
        this.name = name;
        this.age = 30;
    }
    sayHi(){
        console.log(this);//class中调用返回对象本身
    }
}

const wangwu = new People('wangwu');
wangwu.sayHi();

使用场景有:

  • 1)作为普通函数。
  • 2)使用apply, call ,bind函数。
  • 3)作为对象方法被调用。
  • 4)在class方法中调用
  • 5)箭头函数 ,找他上级作用域的值

4.2 手写bind函数

Function.prototype.bind1 = function(){
    //获取参数列表
    const args = Array.prototype.slice.call(arguments);
    //获取this指针
    const t = args.shift();
    //this指向函数 fn1
    const self = this;
    //返回函数
    return function(){
       return self.apply(t,args);
    }
}

function fn1(a,b,c){
    console.log(this);
    console.log(a,b,c);
    console.log("this is fn1");
}

const fn2 = fn1.bind1({x:100},200,300,400);
const res = fn2();
console.log(res);

结果:
js面试知识点合集_第7张图片

4.3 作用域

作用域包括:全局作用域、函数作用域、块级作用域。
全局作用域和函数作用域比较好理解,一个是在全局定义,一个在函数定义。
块级作用域一般是指在if(){}、for(){}、while(){}等中。

4.4 闭包及自由变量

闭包的使用场景:

  • 函数作为返回值被返回。
  • 函数作为参数被传递。
//函数作为返回值
function creat(){
    let a = 100;
    return function(){
        console.log(a);
    }
}

let a = 200;
let fn = creat();
fn();

//函数作为参数被传递
function print(fn){
    let b = 200;
    fn(b);
}

let b = 100;
function fn1(){
    console.log(b);
}
print(fn1);

上面例子是闭包的使用场景。结果为:
js面试知识点合集_第8张图片
输出的自由变量的值都是其在定义地方的值。

需要注意的是自由变量的值是在它定义的地方,不是在执行的地方。

5. 异步

5.1 同步与异步的区别

背景
js是单线程语言;
浏览器和node.js已支持启动进程,如webWorker;
js和dom渲染共用同一个线程,js可修改dom结构,js执行dom停止渲染,dom渲染js停止执行。

异步的作用:
异步就是用来解决网络请求、定时任务等任务执行期间不让浏览器卡住不动的作用。

同步和异步的区别
基于js是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行。

5.2 promise

js面试知识点合集_第9张图片
上面代码一个函数调用另一个函数,函数中再嵌套函数,当函数嵌套层更多的时候,代码的可读性很差,所以promise就是用来解决地狱回调。

使用promise来加载一张图片的例子看一下。

function loadImg(src){
    return new Promise((resolve,reject)=>{
        const img = document.createElement('img');
        img.onload = function(){
            resolve(img);
        }
        img.onerror = function(){
            const error = new Error(`加载图片失败 + ${src}`)
            reject(error);
        }
        img.src = src;
    })
}

let url1 = 'https://s.cn.bing.net/th?id=ODL.8a0e02e3f689b7f4f93b7bf17be4ca40&w=197&h=110&c=7&rs=1&qlt=80&o=6&dpr=1.12&pid=RichNav';
let url2 = 'https://s.cn.bing.net/th?id=ODL.1fda16309b56777cfcc70452accb321c&w=197&h=110&c=7&rs=1&qlt=80&o=6&dpr=1.12&pid=RichNav';

loadImg(url1).then(img =>{
        console.log(img.width);
        return img;
}).then(img =>{
    console.log(img.height);
    return loadImg(url2);
}).then(img1 =>{
    console.log(img1.width);
}).catch(error =>{
    console.log(error);
})

5.3 使用异步的场景

  • 网络请求,如加载图片
  • 定时任务,如setTimeout

6. 异步进阶

6.1 promise

  1. promise的三种状态
  • pending 初始状态
  • fulfilled 成功状态
  • rejected 失败状态

resolve() 方法 使得状态 pending —>fulfilled
reject() 方法 使得状态 pending —> rejected

2)promise的then和catch
then 方法返回一个 Promise 对象,其允许方法链。

Promise.resolve("foo")
  // 1. 接收 "foo" 并与 "bar" 拼接,并将其结果做为下一个 resolve 返回。
  .then(function(string) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        string += 'bar';
        resolve(string);
      }, 1);
    });
  })
  // 2. 接收 "foobar", 放入一个异步函数中处理该字符串
  // 并将其打印到控制台中,但是不将处理后的字符串返回到下一个。
  .then(function(string) {
    setTimeout(function() {
      string += 'baz';
      console.log(string);
    }, 1)
    return string;
  })
  // 3. 打印本节中代码将如何运行的帮助消息,
  // 字符串实际上是由上一个回调函数之前的那块异步代码处理的。
  .then(function(string) {
    console.log("Last Then:  oops... didn't bother to instantiate and return " +
                "a promise in the prior then so the sequence may be a bit " +
                "surprising");

    // 注意 `string` 这时不会存在 'baz'。
    // 因为这是发生在我们通过 setTimeout 模拟的异步函数中。
    console.log(string);
  });

// logs, in order:
// Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising
// foobar
// foobarbaz

catch方法返回一个Promise (en-US),并且处理拒绝的情况.

// 抛出一个错误,大多数时候将调用 catch 方法
var p1 = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

p1.catch(function(e) {
  console.log(e); // "Uh-oh!"
});

// 在异步函数中抛出的错误不会被 catch 捕获到
var p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    throw 'Uncaught Exception!';
  }, 1000);
});

p2.catch(function(e) {
  console.log(e); // 不会执行
});

// 在 resolve() 后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {
  resolve();
  throw 'Silenced Exception!';
});

p3.catch(function(e) {
   console.log(e); // 不会执行
});

3)promise的API
resolve:返回一个以给定值解析后的 Promise 对象
reject:方法返回一个带有拒绝原因的 Promise 对象、
all:返回一个promise实例,回调结果是一个数组,所有函数执行完成后返回结果,如果reject回调立即抛出错误。
race:返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
any:返回一个promise,有一个成功返回promise,全部失败返回失败的promise。
4)手写promise(构造函数、then的链式调用、all、race)

/**
 * @description myPromise
 * @author liuer
 * 
 */

class MyPromise{
    state = 'pending'; //pending fulfilled rejected
    value = undefined; //存储成功的值
    reason = undefined; //存储失败的值
    resolveCallback = []; //存储pending状态 回调resolve的函数
    rejectedCallback = []; //存储pending状态 回调rejected的函数、
    constructor(fn){
        const resolveHandler = (value) => {
            if (this.state === 'pending'){
                this.state = 'fulfilled';
                this.value = value;
                this.resolveCallback.forEach(fn => fn(this.value));
            }
        }

        const rejectHandler = (reason) =>{
            if (this.state === 'pending'){
                this.state = 'rejected';
                this.reason = reason;
                this.rejectedCallback.forEach(fn => fn(this.reason));
            }
        }

        try {
            fn(resolveHandler,rejectHandler);
        } catch (error) {
            rejectHandler(error);
        }
        
    }

    then(fn1,fn2){
        fn1 = typeof fn1 === 'function' ? fn1 : (v)=>{v};
        fn2 = typeof fn2 === 'function' ? fn2 : (e)=>{e};
        //当状态为fulfilled 和 rejected时会直接执行,如果状态为pending 需要把 fn1和fn2存储起来
        if (this.state === 'pending'){
            return new MyPromise((resolve,reject)=>{

                this.resolveCallback.push(()=>{
                    try {
                        const newValue = fn1(this.value);
                        resolve(newValue);
                    } catch (e) {
                        reject(e);
                    }
                });

                this.rejectedCallback.push(()=>{
                    try {
                        const newReason = fn2(this.reason);
                        reject(newReason);
                    } catch (error) {
                        reject(error);
                    }
                })
            })
        }
        //当状态为 fulfilled 
        if (this.state === 'fulfilled'){
            return new MyPromise((resolve,reject)=>{
                try {
                    const newValue = fn1(this.value);
                    resolve(newValue);
                } catch (error) {
                    reject(error);
                }
                
            })
        }

        //当状态为 rejected
        if (this.state === 'rejected'){
            return new MyPromise((resolve,reject)=>{
                try {
                    const newReason = fn2(this.reason);
                    reject(newReason);
                } catch (error) {
                    reject(error);
                }
            })
        }
    }

    //catch是then的一个语法糖
    catch(fn){
        this.then(null,fn);
    }
}


MyPromise.resolve = function(value){
    return new MyPromise((resolve,reject)=>{resolve(value)});
}

MyPromise.reject = function(reason){
    return new MyPromise((resolve,reject) =>{ reject(reason)});
}

MyPromise.all = function(paramList = []){
    return new MyPromise ((resolve,reject) =>{
        let result = [];
        let length = paramList.length;
        let count = 0;
        paramList.forEach(p => {
            p.then(data=>{
                result.push(data);
                count++;
                if (count === length){
                    resolve(result);
                }
            }).catch((error)=>{
                reject(error);
            })
        })
    })
}

MyPromise.race = function(paramList = []){
    return new MyPromise((resolve,reject)=>{
        let flag = false;
        paramList.forEach(p =>{
            p.then(data =>{
                if (!flag){
                    resolve(data);
                    flag = true;
                }
            }).catch(e =>{
                reject(e);
            })
        })
    })
}

练习代码:

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Documenttitle>
  head>
  <body>
    <script src="./22-promise.js">script>

    <script>
      const p1 = new MyPromise((resolve, reject) => {
        //resolve(100);

        setTimeout(() => {
          resolve(100);
        }, 500);
      });
      console.log(p1);

      const p11 = p1.then((data) => {
        return data + 1;
      });

      console.log(p11);


      const p2 = MyPromise.resolve(100);
      const p3 = MyPromise.resolve(200);
      const p4 = MyPromise.reject('出错了');
      const p5 = MyPromise.all([p2,p3]);
      p5.then(data=>{console.log('p5: ' + data)});

      const p6 = MyPromise.race([p2,p3,p4]);
      p6.then(data=>{console.log('p6: ' + data)});
    script>
  body>
html>

结果如下图:
js面试知识点合集_第10张图片

6.2 event loop

event loop 事件循环/事件轮询机制。

为什么会有event loop ? 或者说event loop 和 javascript 有什么关联?
首先 javascript 是单线程语言,单线程是指在同一时间内只能执行一个事件。当有多个任务执行时,需要排队等待,如果有任务所需的时间比较长,如IO操作或网络请求等,页面就需要长时间等待会出现“假死”现象。为了解决这种情况,将任务分为了同步任务和异步任务。同步任务是指在主线程上执行的任务,异步任务是指不进入主线程,进入任务队列。当主线程空了,就会读取任务队列,这个过程会不断重复。这就是event loop.
js面试知识点合集_第11张图片

如上图所示执行过程:
1)代码一行一行执行
2)遇到异步先记录下来,如定时器、网络请求
3)时机一到移动到callback Queue
4) 如果同步代码执行完,即call stack为空,触发eventloop机制
5)event loop 执行轮询机制,一遍一遍的去callback Queue查询,如果有则放到call stack 执行。

dom事件和event loop的关系?
1)js是单线程的
2)异步(setTimeOut、ajax)使用回调,基于event loop,setTimeout则是等待时间,时间一到就移动到Queue中
3) dom事件使用回调,基于event loop,回调的时机是点击触发事件时,会将存储在webApis中的事件放到callback Queue中。

** dom渲染和event loop 的关系?**
每次call stack清空(即每次轮训结束),即同步任务执行完,都是dom重新渲染的机会,dom结构如有改变则重新渲染,然后再去触发下一次Event loop。
js面试知识点合集_第12张图片

6.3 async/await

async/await 是同步语法,彻底消灭回调函数。

async/await 和 promise的关系?

  • 消灭异步回调的终极武器和Promise相辅相成。
  • 执行async函数,返回的是promise对象
  • await相当于promise的then
  • try…catch可捕获异常,代替了Promise的catch
async function fn1(){
    return 400; //async函数执行返回的是promise对象
    //return Promise.resolve(400);
}

const res = fn1();
console.log(res);

!(async function fn2(){
    const p1 = Promise.resolve(200);
    const res1 = await p1;
    console.log(res1);
})()

!(async function fn2(){
    try {
        const p1 = Promise.reject('error');//rejected状态需要trycatch捕获
        const res1 = await p1;
    } catch (error) {
        console.log(error);
    }
})()

!(async function fn2(){
    const res1 = await 500; //await相当于promise的then
    console.log(res1);
})()

async function fn3(){
    return 100;
}

!(async function(){
    const a = fn3();
    console.log(a); //a是一个promise对象
    const b = await fn3()
    console.log(b);//100
})()

执行结果的一个面试题:
js面试知识点合集_第13张图片
这里要注意的是c出错了所以不会打印。

6.4 宏任务和微任务

  • 宏任务macroTask 微任务 microTask
  • 宏任务:setTimeout setInterval ajax Dom事件
  • 微任务:promise async/await
  • 微任务执行时机比宏任务时机要早
  • js是单线程的,而且和Dom渲染共用一个线程,js执行时得留一些时机供dom渲染
<body>
    

    <div id="container">div>
    <script>
        // console.log(100);
        // setTimeout(()=>{
        //     console.log(200);
        // })
        // Promise.resolve().then(()=>{
        //     console.log(300);
        // });
        // console.log(400);

        const div1 = document.getElementById('container');
        const p1 = document.createElement('p');
        p1.innerHTML = '这是第一段文字' + '
'
; const p2 = document.createElement('p'); p2.innerHTML ='这是第二段文字' + '
'
; const p3 = document.createElement('p'); p3.innerHTML = '这是第三段文字' + '
'
; div1.appendChild(p1).appendChild(p2).appendChild(p3); // alert会阻断js执行和dom渲染 Promise.resolve().then(()=>{ console.log('length1: ' + div1.children.length); alert('promise') }) setTimeout(()=>{ console.log('length2: ' + div1.children.length); alert('setTimeout') })
script>

上述代码证明微任务是在dom渲染之前执行,宏任务是在dom渲染之后执行。

下面是执行顺序的面试题

async function async1(){
    console.log('async1 start'); //2
    await async2();
    //await后面的都作为回调内容 --微任务
    console.log('async1 end'); //微任务 6
}

async function async2(){
    console.log('async2'); //3
}

console.log('script start'); //1

setTimeout(function(){
    console.log('setTimeout'); //宏任务 8
},0)

async1();

//初始化promise时,传入的函数会立刻被执行
new Promise(function(resolve){
    console.log('promise1'); //4
    resolve()
}).then(function(){
    console.log('promise2') //微任务 7
})

console.log('script end'); //5

结果如下图所示:
js面试知识点合集_第14张图片

你可能感兴趣的:(前端面试知识点合集,javascript,面试,前端)