2019前端面试题(js篇)

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值