欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

2019前端面试题(js篇)

程序员文章站 2022-06-09 11:19:08
...

在此分享、整理前端面试题,如有解答错误的地方,烦请各位大佬指正,感谢!!

javascript原型、原型链?有什么特点

每个函数都有一个 prototype 属性,函数的 prototype属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型

那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。

原型链解决的主要是继承问题。

每个对象拥有一个原型对象,通过 proto 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.proto 指向的是null)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。

juejin.im/post/5cf336…

function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
复制代码
function Person() {

}
console.log(Person === Person.prototype.constructor); // true
复制代码
function Person() {

}

var person = new Person();

console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
复制代码

参考:github.com/mqyqingfeng…

解释javascript中的作用域和变量声明提升

作用域是指程序源代码中定义变量的区域。

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

变量声明提升:

foo;  // undefined
var foo = function () {
    console.log('foo1');
}

foo();  // foo1,foo赋值
复制代码

可以想象成:所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端

javascript有几种类型的值?你能画一下他们的内存图吗

JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object)。

基本类型有六种: null,undefined,boolean,number,string,symbol。

  • 基本类型:--> 内存(不包含闭包中的变量)
  • 引用类型:--> 内存

引用类型有:Object、Array、Date、RegExp、Function

如何实现数组的随机排序

var arr = [1,2,3,4,5,6,7,8,9,10];
var newArr = [];
let length=arr.length
for(let i=0;i<length;i++){
  let randomIndex = Math.floor(Math.random()*arr.length);
  newArr[i]=arr[randomIndex]
  arr.splice(randomIndex,1)
}
console.log(newArr)
复制代码

谈谈this对象的理解,call()和apply()的区别

call和apply的区别在于传入参数的不同; 第一个参数都是,指定函数体内this的指向;

第二个参数开始不同,apply是传入带下标的集合,数组或者类数组,apply把它传给函数作为参数,call从第二个开始传入的参数是不固定的,都会传给函数作为参数。

call比apply的性能要好,平常可以多用call,

call传入参数的格式正是内部所需要的格式,

什么是闭包?为什么要用它?

闭包:函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。

作用有:

封装私有变量 模仿块级作用域(ES5中没有块级作用域) 实现JS的模块

new操作符到底干了什么?

  • 创建一个新对象
  • 这个对象会链接到它的原型:obj.proto = Con.prototype
  • 绑定this(apply),属性和方法被加入到 this 引用的对象中。并执行了构造函数中的方法.
  • 函数没有返回其他对象,那么this指向这个新对象,否则this指向构造函数中返回的对象。

如何实现一个 new

function _new(fn, …arg) {
    const obj = Object.create(fn.prototype);
    const ret = fn.apply(obj, arg);
    return ret instanceof Object ? ret : obj;
}
复制代码

谈谈你对ECMAScript6的理解

ECMAScript6是ES2015标准;

  • 新增了块级作用域(let,const)
  • 提供了定义类的语法糖(class)
  • 新增了一种基本数据类型(Symbol)
  • 新增了变量的解构赋值
  • 函数参数允许设置默认值,引入了rest参数,新增了箭头函数
  • 数组新增了一些API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法
  • 对象和数组新增了扩展运算符
  • ES6 新增了模块化(import/export)
  • ES6 新增了 Set 和 Map 数据结构
  • ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
  • ES6 新增了生成器(Generator)和遍历器(Iterator)

AMD、CMD规范区别

AMD规范:是 RequireJS在推广过程中对模块定义的规范化产出的,

CMD规范:是SeaJS 在推广过程中对模块定义的规范化产出的。 区别

CMD 推崇依赖就近;AMD 推崇依赖前置

CMD 是延迟执行;AMD 是提前执行

CMD性能好,因为只有用户需要的时候才执行;AMD用户体验好,因为没有延迟,依赖模块提前执行了

垃圾回收

垃圾回收是一种内存管理机制。我们声明一个变量和函数的时候都会占用内存,但是内存容量有限,当一个变量离开执行环境的时候。考虑到它不会再使用的,就会被回收,释放内存

方法:

  • 引用计数
  • 标记清除

内存泄漏

内存泄漏是指计算机可用的内存越来越少,主要是因为程序不能释放那些不再使用的内存。

无意的全局变量、循环、定时器、回调

浏览器缓存

  • 强缓存
  • 协商缓存

强缓存

强缓存表示在缓存期间不需要请求,state code 为 200 expires(本地过期时间)和cache-control(单位是秒,多少秒后过期),cache-control优于expires

协商缓存

如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304。

  • Etag和if-none-match对比
  • last-modified和if-modified-since对比

算法

  • 冒泡排序

    重复遍历所有的元素,两个元素比较,如果大小顺序不对,就交换它们的位置。重复遍历直到没有再需要交换,排序完成。

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        //相邻元素两两对比
                var temp = arr[j+1];        //元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}
复制代码
  • 选择排序 在待排序序列中找到最小(大)元素,放在序列的起始位置,然后,再从剩余元素中寻找最小(大)元素,然后放到已排序序列的末尾。重复,直到所有元素均排序完毕。

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     //寻找最小的数
                minIndex = j;                 //将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}
复制代码
  • 插入排序 原理:通过构建有序序列,对于未排序元素,在已排序序列中从后向前扫描,找到相应位置并插入。一般可以将第一个元素作为有序序列,用未排序的元素与之相比,插入,直到排序完毕。像打扑克牌整理自己的牌一样。
function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}
复制代码
  • 快速排序的思想: 数组中指定一个元素作为标尺,比它大的放到该元素后面,比它小的放到该元素前面,如此重复直至全部正序排列。 。

 function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ? 0 : left,
        right = typeof right != 'number' ? len - 1 : right;

    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}

function partition(arr, left ,right) {     //分区操作
    var pivot = left,                      //设定基准值(pivot)
        index = pivot + 1;
    for (var i = index; i <= right; i++) {
        if (arr[i] < arr[pivot]) {
            swap(arr, i, index);
            index++;
        }       
    }
    swap(arr, pivot, index - 1);
    return index-1;
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
复制代码

继承的方法

实现继承 主要是依靠原型链来实现的.

  • 构造函数
    • 保证了原型链中引用类型值的独立,不再被所有实例共享;
    • 子类型创建时也能够向父类型传递参数.
  • 组合继承 组合继承, 有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者之长的一种继承模式.
    • 组合继承其实调用了两次父类构造函数,
  • 寄生式继承
  • 寄生组合继承

TypeScript的类型

  • 布尔值 let isDone: boolean = false;

  • 数字

    所有数字类型都是浮点数

    let decLiteral: number = 6;

  • 字符串

    模版字符串也是这个类型

    let name: string = "bob";

  • 数组

    • 数字数组:number[]、Array
    • 对象数组:Object[]
  • 元组

    元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组。

    let x: [string, number];

  • 枚举enum

    enum Color {Red, Green, Blue}
    let c: Color = Color.Green;
    复制代码
  • nerver

  • Object

  • Null 和 Undefined

  • Void

  • Any

var、let 和 const 区别的实现原理是什么

var 和 let 用以声明变量,const 用于声明只读的常量;

var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const 声明的,只在它所在的代码块内有效;

let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变量可以先使用,后声明,而 let 和 const 只可先声明,后使用;

let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它所声明的变量就绑定了这个区域,不再受外部的影响。

let 不允许在相同作用域内,重复声明同一个变量;

const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,更不允许重复声明;

如 const 声明了一个复合类型的常量,其存储的是一个引用地址,不允许改变的是这个地址,而对象本身是可变的。

变量与内存之间的关系,主要由三个部分组成:

变量名

内存地址

内存空间

JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引擎会在合适的时机进行 GC,回收旧的内存空间。

const 定义变量(常量)后,变量名与内存地址之间建立了一种不可变的绑定关系,阻隔变量地址被改变,当 const 定义的变量进行重新赋值时,根据前面的论述,JS 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。

github.com/Advanced-Fr…

input 搜索如何防抖,如何处理中文输入

处理中文输入,在input框绑定compositionsend,就可以监听中文输入结束

promise、promise.all、promose.race

手写一个ajax

简化版

var request = new XMLHttpRequest();
request.open('GET', '/xxx');
request.onload = () => {console.log('请求成功'};
request.send();
复制代码

详细版

var request = new XMLHttpRequest();
request.open('GET', 'xxx');
request.onreadystatechange = function() {
  if(request.readyState === 4 ) {
    console.log('请求完成');
    if(request.respondse.status >=200 && request.respondse.status) {
      console.log('请求成功')
    }else{
      
    }
  }
}

request.send();
复制代码

如何实现深拷贝

常用:使用JSON.parse(JSON.stringify(obj))

原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象

缺点是: 会忽略undefined、symbol、funciton

实现:递归+判断类型

一个简单的代码

// 数字 字符串 function是不需要拷贝的
function deepClone(value) {  
    if (value == null) return value;  
    if (typeof value !== 'object') return value;
    if (value instanceof RegExp) return new RegExp(value);  
    if (value instanceof Date) return new Date(value);  
    // 我要判断 value 是对象还是数组 如果是对象 就产生对象 是数组就产生数组  
    let obj = new value.constructor;  
    for(let key in value){    
        obj[key] = deepClone(value[key]); // 看一看当前的值是不是一个对象  
    }  
    return obj;
}
复制代码

juejin.im/post/5c400a…

手写一个promise

function Promise(executor) {
    let self = this;
    self.status = 'pending'; //等待态
    self.value = undefined;  //成功的返回值
    self.reason = undefined; //失败的原因

    function resolve(value){
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(reason) {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    try{
        executor(resolve, reject);
    }catch(e){
        reject(e);// 捕获时发生异常,就直接失败
    }
}
//onFufiled 成功的回调
//onRejected 失败的回调
Promise.prototype.then = function (onFufiled, onRejected) {
    let self = this;
    if(self.status === 'resolved'){
        onFufiled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise;
复制代码

juejin.im/post/5aafe3…

参考:参考链接 JS家的排序算法

转载于:https://juejin.im/post/5ce8eebbf265da1bb0039a9a