Skip to content

面试题

统计信息:字数 18112 阅读37分钟

01. 作用域和值类型引用类型的传递1

  • 变量类型:全局变量,函数内部变量
  • 简单类型直接复制,复杂类型复制引用;如果改变复杂类型内容,那么其他地方也会改变

02. 作用域和值类型引用类型的传递2

  • 函数的参数如果是引用类型,改变属性时,会改变原来的对象;如果让这个参数直接指向新的对象,那么新的对象和原来的对象就不相关了。
  • 扩展:这里this指向什么(谁调用的这个方法,this就指向这个对象)?ES6中如何处理This(bind,箭头函数可以把this保留在当前类中,而不是类的实例中)

03. 封装函数进行字符串驼峰命名的转换

  • 给定一个类名使用连字符连接,然后转换成驼峰命名输出。考点是字符串的API。演示中使用一个数组,实际上完全不必要,直接一个循环即可实现。
    // code 不好的算法(0.093 ms)
    function foo(str) {
      var arr = str.split('-');
      for (let i = 1; i < arr.length; i++) {
         arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].substr(1, arr[i].length - 1);
      }
      return arr.join('');
    }
    
    // code 好的算法(0.062 ms)
    function foo(str) {
      while (str.indexOf('-') > -1) {
        let index = str.indexOf('-');
        str = str.substr(0, index) + str.charAt(index + 1).toUpperCase() + str.substr(index + 2, str.length + 1);
      }
      return str;
    }
    
    console.time(123);
    foo('hello-world-miki-mikohjk-fghdfty-rtyfghjk');
    console.timeEnd(123);
    

04. 冒泡排序

冒泡排序法;快速排序法-这两个应该熟练背写

// code 冒泡排序法
function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i; j++) {
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}

// code 快速排序法
function quickSort(arr) {
  if (arr.length < 2) {
    return arr;
  }
  let init = arr[0];
  let left = [];
  let right = [];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < init) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  let res = [];
  left = quickSort(left);
  right = quickSort(right);
  res.push(...left, init, ...right);
  return res;
}

var a = [2,1,3,4,5,2,3,4,5,5,2,4,1,2,3,4,1,0,6,8,9];
var b = quickSort(a);
console.log(b);

05. 反转数组

使用数组的原生API实现,或者循环一轮可以实现;

// code 反转数组
var arr = [1,2,3,4,5];
function reverseArr(arr) {
  for (let i = 0; i < arr.length / 2; i++) {
    var temp = arr[i];
    arr[i] = arr[arr.length - i - 1];
    arr[arr.length - i - 1] = temp;
  }
  return arr;
}

// code 好的算法
function reverse(arr) {
  arr.reverse();
}

06. 去掉数组中重复性的数据

可以使用ES6,或者使用对象存储; 这里给出的答案是双重循环,性能不好

function deleteSame (arr) {
  return Array.from(new Set(arr));
}

07. 实现1物理像素

这个考察移动端CSS的实现 可以全局更改font-size,需要把图标,文字大小使用 px 配置,把DIV的长度和宽度,使用 rem 配置。这样,图标或者文字可以实现1物理像素。

<meta name="viewport" content="width=device-width, initial-scale=1, user-scaleable=no"/>

// code 全局更改font-size
window.onload = function() {
  var dpr = window.devicePixelRatio;
  var scale = 1 / dpr;
  var width = document.documentElement.clientWidth;
  var metaNode = document.querySelector('meta[name="viewport"]');
  mataNode.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', user-scaleable=no');
  var htmlNode = document.querySelector('html');
  htmlNode.style.fontSize = width * dpr + 'px';
}

// 这样在CSS中,1px 对应的就是真实的物理像素,1rem 已经转换成 16 * dpr 尺寸。对于iPhone678,这是2。对于plus 这是3。
可以单独设置某一条线段的宽度 主要是移动端dpr是2-3,物理像素 / CSS像素的结果

/* code 单独设置某一条线段的宽度 */ 
#box {
  width: 200px;
  height: 200px;
  positon: 'relative';
}

#box:before {
  content: '';
  position: 'absolute';
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: #000;
}

@media screen and (-webkit-min-device-pixel-ratio: 2) {
  #box:before {
    transform: scaleY(0.5);
  }
}

@media screen and (-webkit-min-device-pixel-ratio: 3) {
  #box:before {
    transform: scaleY(0.333);
  }
}

08. flex元素水平垂直居中

传统方法:使用position实现水平垂直居中;

方法一

.father {
    position: relative;
}
.son {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
方法二
.father {
    position: relative;
}
.son {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -50px;
    margin-top: -50px;
}
方法三
.father {
    position: relative;
}
.son {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

flex 方法也可以实现

.father {
    display: flex;
    align-items: center;
    justify-content: center;
}
.son {
}

09. css实现三角形

原理:一个DIV,宽度是0,边框宽度很大,设置四个边不同的颜色,那么就是四个三角形。如果使用一个三角形,那么设置另外三个边是透明色即可。

#box {
    width: 0;
    height: 0;
    border: 100px solid;
    border-top-color: red;
    border-bottom-color: transparent;
    border-left-color: transparent;
    border-right-color: transparent;
}

10. rem适配

设置 meta 标签,设置初始值是 initial = 1。然后通过 JS 获取这个节点,获取屏幕的宽度,然后设置当前根节点的缩放比是 1 / 屏幕的宽度。那么设置某个元素的宽度是 1rem 就是屏幕的宽度

<meta name="viewport" content="width=device-width, initial-scale=1, user-scaleable=no"/>
// code 全局更改font-size
window.onload = function() {
  var dpr = window.devicePixelRatio;
  var scale = 1 / dpr;
  var width = document.documentElement.clientWidth;
  var metaNode = document.querySelector('meta[name="viewport"]');
  mataNode.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', user-scaleable=no');
  var htmlNode = document.querySelector('html');
  htmlNode.style.fontSize = width * dpr + 'px';
}

11. 背景图片距离

问题:给定一个CSS,计算背景图片距离左侧边框的位置。

默认的背景图片距离左侧边框是0,padding 不考虑。下面情况特殊。

.box {
    width: 100px;
    height: 200px;
    padding: 100px;
    border: 80px solid black;
    background-image: url('...png');
    background-repeat: no-repeat;
    background-origin: content-box; /* 这里设置了起始点是内容部分 */
    background-position: -50px 0;
}

结果是:边框 + 左侧 padding - 偏移量 = 80 + 100 - 50 = 130px

12. js综合面试题

变量提升,函数执行顺序,异步过程等

function Foo() {
  getName = function() {
    alert(1);
  }
  return this;
}
Foo.getName = function() {
  alert(2);
}
Foo.prototype.getName = function() {
  alert(3);
}
var getName = function () {
  alert(4);
}
function getName() {
  alert(5);
}

// 执行下面的操作
Foo.getName(); // 2
getName(); // 4 这里为什么?
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2 new (Foo.getName)(); ?
new Foo().getName(); // 3 (new Foo()).getName?
new new Foo().getName(); // new ((new Foo()).getName)(); //3?

new 一个构造函数或者类,先执行一次这个函数,然后返回这个类的实例(这个例子中没有保存或者使用新建的实例)

13. 函数节流和防抖

函数节流:函数频繁触发时,每个一定周期执行一次(执行多次)。

函数防抖:函数频繁触发时不执行,需要等最后一次触发结束后,再执行函数(执行一次)。

function throttle(fn, delay) {
  var lastTime = 0; // 上次触发函数的时间
  var nowTime = Data.now(); 
  if (nowTime - lastTime > delay) {
    fn();
    lastTime = nowTime;
  }
}
// 处理this指向问题
function throttle(fn, delay) {
  var lastTime = 0; // 上次触发函数的时间
  return function () {
    var nowTime = Data.now(); 
    if (nowTime - lastTime > delay) {
      fn.call(this);
      lastTime = nowTime;
    }
  }
}

function debounce(fn, delay) {
  var timer = null;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn();
    }, delay);
  }
}

14. 跨域

同源策略:浏览器发出请求,需要同源(协议、域名、端口号相同),如果违反同源协议会产生跨域。

解决方法:jsonp cors 服务器代理 (JSONP需要前后端协同,其他都是后端的方法)

var script = document.createElement('script');
function getData(data) {
    console.log(data);
}
script.src = 'http://localhost:3000?callback=getData';
document.body.appendChild(script);

15. nodejs事件轮询机制

setTimeout setInterval process.nextTick 三个方法

执行次序是:process.nextTick setTimeout setImmediate

nodeJS 的事件轮训机制是 libuv 实现的(C)分成几个阶段(了解)

  • timers 定时器阶段:计时并执行到点的定时器回调函数
  • pending callbacks TCP操作错误回调函数
  • idle prepare 准备工作
  • poll 轮询阶段(轮询队列)如果队列不是空,那么去除第一个回调函数执行,直到执行完毕;如果设置过setImmediate 函数,那么进入下一个check阶段。
  • Check 检查阶段:执行 setImmediate 设置的回调函数
  • close callbacks 关闭阶段:执行close事件的回调函数

16. 从url输入网址

这个题目考察:网络知识+界面解析过程,下面是具体过程

  • 域名解析(DNS解析):把域名解析成IP:这个依次从缓存中获取IP;如果一直递归不到,那么就会报错。

  • TCP三次握手

    • 1、浏览器告诉服务器:我将要发送请求了,你是否方便
    • 2、服务器告诉浏览器:我准备好了
    • 3、浏览器告诉服务器:我准备发送了,你接受吧
  • 发出请求:请求报文(HTTP)

  • 接收响应:响应报文

  • 渲染界面

    • HTML下载完毕,使用HTML解析器构建DOM树
    • style/link 标记,下载解析样式表,创建 CSSOM树
    • 遇到 script 标签,使用JS计息期,处理JS代码,更改DOM或者CSSOM树
    • 将DOM树和CSSOM树合成 render tree
    • 计算页面布局,计算节点的位置
    • 计算节点的颜色,然后渲染到屏幕上(回流,重绘)可能后几个步骤多次执行
  • TCP四次挥手

    • 浏览器发给服务器:我请求报文发完了,你准备关闭
    • 服务器发给浏览器:请求报文接受完了,我准备关闭
    • 浏览器发给服务器:响应报文法文了,你准备关闭
    • 服务器发给浏览器:响应报文接受晚了,你准备关闭

17. 闭包

闭包:闭包是一个密闭的容器,用来存储临时数据——closure

闭包形成的条件:函数嵌套;内部函数引用外部函数的局部变量。

function fn() {
    var count = 1;
    function fnInner() {
        console.log(count);
    }
}
// 这是一个简单的闭包(没有实际作用的闭包)

闭包的好处:外部函数返回内部函数。多次执行内部函数时,存放在外部函数中的局部变量不会被清除,可以临时存储数据。(延长外部函数局部变量的生命周期)

如果滥用闭包,可能造成内存泄漏。

function fun() {
    var count = 1;
    return () => {
        console.log(count);
    }
}
var fn = fun();
fn(); // 执行内部函数时。用到外部函数的局部变量
fn();

闭包的引用场景

function fun() {
    var count = 1;
    return function() {
        count++;
        console.log(count);
    }
}

var fun2 = fun();
fun2();
fun2();
// 2 3

下面是复杂的案例

function fun(n, o) {
    console.log(o); // 打印的是第二个参数
    return {
        fun: function(m) {
            return fun(m, n);
            // 第一个参数传入,第二个参数是外部参数的第一个参数
        }
    }
}

var a = fun(0); // 第二个参数没有传,打印 undefined
a.fun(1); // 执行内部函数 fun(1, 0) 打印结果是0
a.fun(2); // 执行内部函数 fun(2, 0) 打印结果是0
a.fun(3); // 执行内部函数 fun(3, 0) 打印结果是0

var b = fun(0).fun(1).fun(2).fun(3);
// undefined, 0, 1, 2 
// 第一个函数执行是 undefined 和第一个一样
// 第二个函数,返回 function(1) { fun(1, 0) } 然后打印0
// 第三个函数,返回 function(2) { fun(2, 1) } 然后打印1

var c = fun(0).fun(1); //undefined/0
c.fun(2); // 1
c.fun(3); // 1

18. 变量提升 && 执行上下文

变量定义和函数定义,变量的声明会提升到顶部,变量的赋值不会提升。

函数会全部提升到顶部

console.log(a); // undefined
var a = 10;
console.log(a); // 10

fn();
function fn() {
    console.log(1);
}

执行上下文(EC)execute context:代码执行前,根据上下文进行变量提升。确认 this 的指向;创建作用域链(全局作用域、函数作用域)

19. 宏任务和微任务-NodeJS

执行完一轮宏任务,看看是否有微任务;如果有微任务就执行。然后开始下一次轮训,执行下一个宏任务。

宏任务:setTimeout setInterval requestAnimationFrame:宏任务队列可以有多个,第一个宏任务队列只有一个任务-执行主线程的JS代码

微任务:new Promise().then(callback) process.nextTick:只有一个微任务队列。在上一个宏任务全部执行完毕,如果有微任务,就会立刻执行微任务中的全部队列。

所有,执行的流程是:第一个宏任务队列(主线程)+微任务队列(如果有就执行)+第二个宏任务队列+第三个宏任务队列。。。

console.log(1);

setTimeout(() => {
    console.log(2);
}, 0);

setTimeout(() => {
    console.log(3);
},100);

new Promise((resolve, reject) => {
    console.log(4);
    reject(); // 这个执行后,下面的resolve不会执行。
    resolve();
}).then(() => {
    console.log(5);
}).catch(() => {
    console.log(6);
});

console.log(7);
// 147(第一个宏任务队列)  6(微任务队列)2(第二个宏任务队列)3(第三个宏任务队列)

20. 小程序快速入门

  • 小程序:打包后代码不超过2M;主要依据微信官方开发者文档;开发工具也是微信开发工具(APPID)

  • 开发者工具:模拟器+调试器+编辑器。

  • project.config.json 设置项目的调试目录。
  • app.js app.json app.wxss 这里是根组件的部分
  • pages 是不同子页面;页面中结构+样式+行为一一对应。这里使用微信自定义的格式(View button text image block)。
    <view class="container">
        <view class="userinfo">
            <text class="user-metto">{{motto}}</text>
        </view>
    </view>
    
    JS 中处理事件
const app = getApp();

Page({
    data: {
        motto: 'Hello World',
        userInfo: {},
        hasUserInfo: false,
        canIUse: wx.canIUse('button.open-type.getUserInfo')
    },
    bindViewTap: function() {
        wx.navigateTo({
            url: '../logs/logs'
        })
    },
})

具体的配置在微信官方文档中有

data 可以直接在WXML中通过模板获取到的数据

微信小程序已经做了完美适配,不需要考虑移动端适配,单位是 vpx

21. 小程序和mpVue对比简介

小程序有专门的框架,wepy,mpvue (mini program Vue)

这个语法和 VUE 基本一致(需要的时候再学习)

22. 比较React与Vues

相同点: - 组件化;使用 props 进行父子组件数据传递 - 虚拟DOM,不需要直接操作DOM - 数据驱动更新 - 服务器端渲染;移动端原生渲染(React Native)

不同点: - Vue 双向绑定;React 单向绑定 - 组件写法:React 是JSX,VUE 是单文件形式,HTML+CSS+JS在一个模板文件中 - state:React需要 setState 更新状态,VUE使用data属性管理 - 虚拟DOM:VUE根据依赖关系,不需要重新渲染全部组件;React中,状态改变会渲染全部的组件,可以通过 pureComponent or shouldComponentUpdate 控制组件的更新

23. Redux管理状态的机制

  • Redux:是一个专门做状态管理的JS库,不是React的插件,可以集中管理React中多个组件共享的状态和从后台获取的数据。

  • 工作机制:Redux 分成三部分:store(包含state,存储数据,与react组件进行数据交流)、reducers(产生数据,更新store中的state)、action creator(事件驱动状态更新)。

  • 主要方法:getState 获取state;dispatch(action)派发事件;store.subscribe(listener) 订阅事件。

  • 扩展:react-redux,redux-thunk,Redux DevTools 等工具

24. 说说Vue组件间通信方式

通信对象:父子通信,隔代通信、兄弟通信

方式:props,自定义事件,消息的订阅发布,Vuex,slot 传递子元素。通常使用 Props 传递(可以传递一般属性或者回调函数),隔代传递或者兄弟传递不适合。自定义事件:订阅事件、触发事件;vuex 是VUE状态管理库,可以处理复杂的数据类型。

25. Vuex管理状态的机制

Vuex 是 VUE 的状态管理的插件;可以集中管理多个 VUE 多个组件共享的状态,以及从服务器获取的数据。

  • state 负责存储状态($store.state mapState() getters)
  • actions 负责处理事件($store.dispatch()/mapActions()),和后端交互数据(commit)
  • mutations 在数据更新后,直接更新 state

26. 说说Vue的MVVM实现原理

模板解析(解析大括号表达式——解析指令)

数据绑定(实现更新显示)

observer 观察监视data中所有层级的属性,compile 编译模板;初始化界面,watcher更新界面。

  • 注意:后面几部分是VUE或者小程序,实际不使用,所以需要的时候再说。

Last update: November 9, 2024