javascript面试题(1)

箭头函数和普通函数区别

https://www.pxcodes.com/Codes/1593069883119880.html

1.如何理解执行上下文

JavaScript执行上下文(context)主要指代码执行环境的抽象概念。执行上下文分为三种:

  • 全局执行上下文

  • 函数执行上下文

  • eval执行上下文

每一段js代码执行,都会先创建一个上下文环境。

eval是做什么的?

eval的功能是把对应的字符串解析成JS代码并运行

应该避免使用eval,不安全,非常耗性能(先解析成js语句,再执行)

由JSON字符串转换为JSON对象的时候可以用 eval(’(’+ str +’)’);

2.如何理解作用域链

前沿知识:js代码执行前会创建上下文环境,这个上下文环境包含了变量、作用域链和this.

简单理解就是从当前环境向父级一层一层查找变量的过程称之为作用域链。

var name = '前端未来';
function hello(){
  console.log(name);
}

解释:当我们在函数hello里面打印name的时候,会先在hello作用域中查找,如果没有找到就去hello的父级作用域中查找。

3.解释JavaScript中的作用域与变量声明提升?

JavaScript作用域:

在Java、C等语言中,作用域为for语句、if语句或{}内的一块区域,称为作用域;

而在 JavaScript 中,作用域为function(){}内的区域,称为函数作用域。

Javascript作用链域?

全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节

如果当前作用域没有找到属性或方法,会向上层作用域查找,直至全局函数,这种形式就是作用域链

JavaScript变量声明提升:

在JavaScript中,函数声明与变量声明经常被JavaScript引擎隐式地提升到当前作用域的顶部。

声明语句中的赋值部分并不会被提升,只有名称被提升

函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明

如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数

var、let 及 const 区别?

var 存在提升,我们能在声明之前使用。let、const 因为暂时性死区的原因,不能在声明前使用

var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会

let 和 const 作用基本一致,但是后者声明的变量不能再次赋值

var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。

let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。

const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。

块级作用域 {}

ES5 中作用域有:全局作用域、函数作用域。没有块作用域的概念。

ES6 中新增了块级作用域。块作用域由 { } 包括,if语句和 for语句里面的{ }也属于块作用域。

4.如何理解原型链

每个函数都拥有一个prototype属性,每个函数实例对象都拥有一个__proto__属性,而这个属性指向了函数的prototype,当我们访问实例对象的属性或者方法时,会先从自身构造函数中查找,如果没有就通过__proto__去原型中查找,这个查找的过程我们称之为原型链。(跟作用域链有点像)

// 定义动物 - 父类
function Animal(){
    this.age = 10;
    this.say = function(){
        return 'hello tom';
    }
}
// 定义猫 - 子类
function Cat(){
    this.name = 'tom';
}
// 通过原型继承动物类
Cat.prototype = new Animal()
// 实例化一个cat对象
var cat = new Cat();
// 打印返回true
cat.__proto__ === Cat.prototype
// 打印age,会先查找cat,再查找Animal
console.log(cat.age)

通过截图,我们可以看到cat实例对象__proto__指向了Animal,当cat没有age的时候,会通过__proto__到原型上查找,如果原型上依然没有,会继续向Object上查找。

00

https://blog.csdn.net/weixin_42312074/article/details/106995609

5.什么是闭包

简单理解就是函数中嵌套函数。我们都知道局部变量我们是无法访问的,但是通过闭包可以做到。

// 正常访问
var lan = 'zh';
function hello(){
  var name = '前端未来';
}
console.log(name)//很明显'undefined'

// 换成闭包
function hello(){
    var name = '前端未来';
    function demo(){
      console.log(name)//打印:前端未来
    }
}

以上几个基础面试题都是密切关联的,理解其中一个,基本就能理解剩下几个

6.说说你对闭包的理解

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

闭包有三个特性:

1.函数嵌套函数

2.函数内部可以引用外部的参数和变量

3.参数和变量不会被垃圾回收机制回收

7.继承有哪些方法

  • 原型继承

  • 构造继承

  • 实例继承

  • call/apply继承(组合继承)

  • ES6 使用class extends继承

8.类的定义和继承的几种方式

类的定义:

方式一:用构造函数模拟类(传统写法)

方式二:用 class 声明(ES6的写法)

继承方式:

方式一:借助构造函数//这种方式,虽然改变了 this 的指向,但是,Child1 无法继承 Parent1 的原型。也就是说,如果我给 Parent1 的原型增加一个方法:这个方法是无法被 Child1 继承的。如下:

方法二:通过原型链实现继承//缺点是:如果修改 child1实例的name属性,child2实例中的name属性也会跟着改变。造成这种缺点的原因是:child1和child2共用原型。

方式三:组合的方式:构造函数 + 原型链//这种方式,能解决之前两种方式的问题:既可以继承父类原型的内容,也不会造成原型里属性的修改。这种方式的缺点是:让父亲Parent的构造方法执行了两次。

9.javascript创建对象的几种方式?

对象字面量的方式

用function来模拟无参的构造函数

用function来模拟参构造函数来实现(用this关键字定义构造的上下文属性)

用工厂方式来创建(内置对象)

用原型方式来创建

用混合方式来创建

10.谈谈This对象的理解

this总是指向函数的直接调用者(而非间接调用者)

如果有new关键字,this指向new出来的那个对象

在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window

11.null,undefined 的区别?

null是一个表示"无"的对象,null 表示一个对象被定义了,值为“空值”,转为数值时为0;undefined 表示不存在这个值,undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined,转为数值时为NaN。

undefined:

  (1)变量被声明了,但没有赋值时,就等于undefined。

  (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

  (3)对象没有赋值的属性,该属性的值为undefined。

  (4)函数没有返回值时,默认返回undefined。

null:

  (1) 作为函数的参数,表示该函数的参数不是对象。

  (2) 作为对象原型链的终点。

       (3)在验证null时,一定要使用 === ,因为 == 无法分别 null 和 undefined

12.什么是深/浅拷贝,有哪些实现方式

JS数据类型分别基本数据类型和引用数据类型,基本数据类型保存的是值,引用类型保存的是引用地址(this指针)。浅拷贝共用一个引用地址,深拷贝会创建新的内存地址。

浅拷贝方法

  • 直接对象复制

  • Object.assign

深拷贝

  • JSON.stringify转为字符串再JSON.parse

  • 深度递归遍历

13.如何准确判断一个对象是数组

面试官希望的答案:Object.prototype.toString.call([]) 返回 "[object Array]"

扩展答案

  • [].slice (能力判断 )

  • [] instanceof Array(类型判断)

  • [].proto === Array.prototype

  • Array.isArray([]) 存在兼容问题

数组也是引用类型,通过typeof依然返回object

14.数组有哪些常用方法

这个非常多,说起来也很快,我主要考察你会多少,另外也为了引出下一个问题,slice和splice区别

  • push 末尾添加

  • pop 末尾删除

  • shift 首部删除

  • unshift 首部添加

  • concat 数组合并

  • join 数组元素 通过连接符 连接

  • reverse 数组反转

  • sort 数组排序

  • map/forEach/filter/indexOf/includes/slice/splice

slice表示截取,slice(start,end)不改变原数组,返回新数组。

splice表示删除,splice(start,length,item),会改变原数组,从某个位置开始删除多个元素,并可以插入新的元素。

15.call/apply/bind作用和区别

他们都可以改变函数的作用域。

  • call/apply可以直接执行该函数,而bind不会立刻执行

  • call/apply作用类似,都可以改变指针和执行函数,区别在于传参不同,call需要单个传参,apply通过数组传参

call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。
除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组

let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])


bind 和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind 实现柯里化
 

16.观察者和发布订阅者区别

他们都属于观察者模式,只不过有不同的实现方法。发布订阅相比于观察者多了一个调度中心,发布者通过调度中心向订阅者发布消息。观察者模式中目标和观察者相互依赖,观察者订阅目标主题,当目标发生变化后,会通知对应观察者。

大家可参考我另一篇公众号文章。

17.谈一下EventLoop

这其中大家需要了解几个概念:调用栈、同步/异步任务、宏任务/微任务

JavaScript本身是单线程,也就是同一时刻只能干一件事,JS任务包含了同步任务和异步任务,遇到执行函数会将其放入调用栈(先进后出)中,遇到setTimeout/setInterval等异步任务时,会把它放入到消息队列中,等主线程的任务执行完成以后,再回过头执行消息队列中的异步任务,如果异步任务中仍然有异步任务,会继续放入消息队列,以此类推,便形成了一个事件循环。

异步任务:

  • setTimeout

  • setInterval

异步任务又分为宏任务和微任务,promise就属于微任务.

https://blog.csdn.net/weixin_42312074/article/details/106541507

18.JavaScript有几种类型的值?,你能画一下他们的内存图吗?

  • 栈:原始数据类型(Undefined,Null,Boolean,Number、String)
  • 堆:引用数据类型(对象、数组和函数)

两种类型的区别是:存储位置不同;
1.原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
2.引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其
3.在栈中的地址,取得地址后从堆中获得实体

19.new 操作符具体干了什么?

创建实例对象,this 变量引用该对象,同时还继承了构造函数的原型
属性和方法被加入到 this 引用的对象中
新创建的对象由 this 所引用,并且最后隐式的返回 this

20.事件的代理/委托

事件委托是指将事件绑定目标元素的到父元素上,利用冒泡机制触发该事件

优点:

可以减少事件注册,节省大量内存占用

可以将事件应用于动态添加的子元素上

缺点:
使用不当会造成事件在不应该触发时触发

21.阻止事件冒泡和默认行为

阻止事件冒泡:stopPropagation(),cancelBuble=true;

阻止默认行为:preventDefault,returnValue=false

22.Ajax 是什么? 如何创建一个Ajax?

1、创建XMLHttpRequest对象

2,创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.

3,设置响应HTTP请求状态变化的函数.

4,发送HTTP请求.

5,获取异步调用返回的数据.

6,使用JavaScript和DOM实现局部刷新.

23.JavaScript 对象生命周期的理解?

当创建一个对象时,JavaScript 会自动为该对象分配适当的内存
垃圾回收器定期扫描对象,并计算引用了该对象的其他对象的数量
如果被引用数量为 0,或惟一引用是循环的,那么该对象的内存即可回收
 

24.async函数是什么,有什么作用?

async函数可以理解为内置自动执行器的Generator函数语法糖,它配合ES6的Promise近乎完美的实现了异步编程解决方案

25.addEventListener()和attachEvent()的区别


addEventListener()是符合W3C规范的标准方法; attachEvent()是IE低版本的非标准方法
addEventListener()支持事件冒泡和事件捕获; - 而attachEvent()只支持事件冒泡
addEventListener()的第一个参数中,事件类型不需要添加on; attachEvent()需要添加'on'
如果为同一个元素绑定多个事件, addEventListener()会按照事件绑定的顺序依次执行,attachEvent()会按照事件绑定的顺序倒序执行

26.谈一下防抖和节流

防抖和节流都是希望在同一时间内,不要重复触发请求。一般场景用在搜索和网页滚动事件中。

区别:

防抖主要是在规定时间内只触发一次,如果再次调用,时间从新计算。

节流主要是在固定时间内只触发一次。比如每间隔1秒触发一次。

函数节流用于 onresize, onscroll 等短时间内会多次触发的事件

函数节流的原理:使用定时器做时间节流。
当触发一个事件时,先用 setTimout 让这个事件延迟一小段时间再执行。
如果在这个时间间隔内又触发了事件,就 clearTimeout 原来的定时器,
再 setTimeout 一个新的定时器重复以上流程。

27.数组如何去重

  • ES6 Set去重

  • 利用Object key去重

  • 两层循环逐一对比,生成新数组

  • indexOf去重

  • sort排序,再单层循环前后对比

28.数组如何排序

  • 数组sort排序

  • 冒泡排序(两层循环,两两互换)

//冒泡排序
function bubbleSort ( data ) {
    var temp = 0;
    for ( var i = data.length ; i > 0 ; i -- ){
        for( var j = 0 ; j < i - 1 ; j++){
           if( data[j] > data[j + 1] ){
               temp = data[j];
               data[j] = data [j+1];
               data[j+1] = temp;
           }
        }
    }
    return data;
}
  • 选择排序

//选择排序
function selectionSort( data ) {
    for( var i = 0; i< data.length ; i++){
        var min = data[i];
        var temp;
        var index = i;
        for( var j = i + 1; j< data.length; j++){
            if( data[j] < min ){
                min = data[j];
                index = j;
            }
        }


        temp = data[i];
        data[i] = min;
        data[index]= temp;
    }
    return data;
}
  • 插入排序

//插入排序
function insertionSort( data ) {
    var len = data.length;
    for (var i = 1; i < len; i++) {
        var key = data[i];
        var j = i - 1;
        while ( j >= 0 && data[j] > key) {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = key;
    }
    return data;
}

29.JSON 的了解?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小。

格式:采用键值对,例如:{'age':'12', 'name':'back'}

 JSON方法:

(1)JSON.stringify();  把js对象序列化为json字符串

(2)JSON.parse(); 把json字符串解析为原生js值

30.对象合并(数组合并)

var obj = Object.assign(a,b)

31.babel是什么,有什么作用?

babel是一个 ES6 转码器,可以将 ES6 代码转为 ES5 代码,以便兼容那些还没支持ES6的平台

32.JavaScript 由以下三部分组成:

ECMAScript(核心):JavaScript 语言基础

DOM(文档对象模型):规定了访问HTML和XML的接口

BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法

33.介绍JS有哪些内置对象?

数据封装类对象:Object、Array、Boolean、Number、String

其他对象:Function、Arguments、Math、Date、RegExp、Error

ES6新增对象:Symbol、Map、Set、Promises、Proxy、Reflect

34.javascript 代码中的"use strict";是什么意思 ? 使用它区别是什么?

use strict是一种ECMAscript 5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为

35.documen.write和 innerHTML的区别

document.write只能重绘整个页面

innerHTML可以重绘页面的一部分

 

36.模块化


涉及面试题:为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?

使用一个技术肯定是有原因的,那么使用模块化可以给我们带来以下好处

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性

立即执行函数

在早期,使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题

(function(globalVariable){
   globalVariable.test = function() {}
   // ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable)


AMD 和 CMD

鉴于目前这两种实现方式已经很少见到,所以不再对具体特性细聊,只需要了解这两者是如何使用的。

// AMD
define(['./a', './b'], function(a, b) {
  // 加载模块完毕可以使用
  a.do()
  b.do()
})
// CMD
define(function(require, exports, module) {
  // 加载模块
  // 可以把 require 写在函数体的任意地方实现延迟加载
  var a = require('./a')
  a.doSomething()
})


CommonJS

CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它,当然目前在 Node 中的模块管理已经和 CommonJS有一些区别了

// a.js
module.exports = {
    a: 1
}
// or
exports.a = 1
 
// b.js
var module = require('./a.js')
module.a // -> log 1
ar module = require('./a.js')
module.a
// 这里其实就是包装了一层立即执行函数,这样就不会污染全局变量了,
// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {
    a: 1
}
// module 基本实现
var module = {
  id: 'xxxx', // 我总得知道怎么去找到他吧
  exports: {} // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {
    // 导出的东西
    var a = 1
    module.exports = a
    return module.exports
};
// 然后当我 require 的时候去找到独特的
// id,然后将要使用的东西用立即执行函数包装下,over


另外虽然 exports 和 module.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exports 和 module.exports享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效

ES Module

ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别

1.CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
2.CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
3.CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
4.ES Module 会编译成 require/exports来执行的
 

// 引入模块 API
import XXX from './a.js'
import { XXX } from './a.js'
// 导出模块 API
export function a() {}
export default function() {}
 

37.谈一下for...of

for...of 是ES2015版本引入的语法,它可以遍历数组、类数组、以及Map/set/字符串等

  • 数组迭代

for (const number of [1,2,5]) { }
  • 类数组迭代

for (const number of arguments) { }
  • 字符串迭代

const message = 'hello';

for (const character of message) { }
  • map迭代

const map = new Map();
map.set("name", '前端未来');
map.set("author", '河畔一角');
for(const item of map){ }
// 或者
for(const [key,val] of map){ }
  • Set迭代

const names = new Set(['Tom', 'Jack', 'Lily']);
for (let name of names) { }

for...of非常灵活,还有其它例子,我这儿不再列出。

38.怎么判断两个对象相等?

obj={
    a:1,
    b:2
}
obj2={
    a:1,
    b:2
}
obj3={
    a:1,
    b:'2'
}


可以转换为字符串来判断

JSON.stringify(obj)==JSON.stringify(obj2);//true
JSON.stringify(obj)==JSON.stringify(obj3);//false

快速的让一个数组乱序
 

var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
    return Math.random() - 0.5;
})
console.log(arr);

 

39.math.random

返回介于 0(包含) ~ 1(不包含) 之间的一个随机数

在本例中,我们将取得介于 1 到 10 之间的一个随机数:

Math.floor((Math.random()*10)+1);

在本例中,我们将取得介于 1 到 100 之间的一个随机数:

Math.floor((Math.random()*100)+1);

随机数Math.random()公式

1.  0-x之间的随机数:

Math.round(Math.random()*x);

2.  x至y之间的随机数

Math.round(Math.random()*(y-x)+x);

3.  1-x之间的随机数:

Math.ceil(Math.random()*

40.前端常见攻击方式

  • XSS攻击

  • CSRF攻击

  • Sql注入

  • html脚本注入

攻击原理和方案这儿不再阐述

41.前端有哪些跨域方案

  • JSONP跨域(本质是JS调用)

  • CORS(后台设置)

  • Nginx反向代理(运维配置)

跨域是浏览器做出的安全限制,必须同协议、同域名、同端口否则会被浏览器block

不常用跨域方案,我不进行列出

 

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值