前端面试题分享001

前言

总结面试中的一些常规的面试题 ,js方向的,包括基础知识、设计模式、代码技巧等。

面试题汇总

定义方法实现1+++n

  • 递归
function sum(n){
    if(n>1){
    return sum(n-1)+1
    }else{
    return n
    }
}
  • 数学公式
function sum(n){
return (1+n)*n/2
}
  • for循环(略)

得到url参数中的键值对,’dsewf?a=1&b=2′

备注 :前提 参数中其他位置无? – qs模块

let str = urlStr.split('?')[1]
qs.parse(str)
  • split分组
let str = urlStr.split('?')[1]
let query = str.split("=") 
  • replace去除 let str = urlStr.replace(/^\w+\?/,'')

方法定义时this指向与运行环境有关

解释 :要注意的是函数中的this与运行环境强相关,与定义环境不相关。所以下面的代码段中,当直接通过对象属性方法中去调用时,其都可以访问到对象的属性,但是当其变为一个函数单独调用时,就访问不到对象的属性了,而是从全局环境中找,所以变成未定义。

let temp = {
  baz :function(){
    return this.bar
  },
  bar :1
}
console.log(temp.baz());//1
var baz = temp.baz ;
console.log(baz());//undefined

(function(fn){
  console.log(fn())//undefined
})(temp.baz)

参考地址:0621对象方法的this指向

函数参数为对象时,传入的为引用

解析:因为对象不是基本类型,所以当其作为参数传入时,使用的是引用地址,所以当其进行值变更的时候,原始内存值也会变化。再之后通过等号或者赋值引用相同地址时,都会发生关联性的改变哦。

var a ={
  b :1
}
function demo(obj){
  obj.b = 3
  return obj
}

console.log(a.b)//1
var aa = demo(a)
console.log(a.b)//3
console.log(aa.b)//3
aa.b = 4 
console.log(a.b)//4
console.log(aa.b)//4

参考地址:0621对象方法的this指向

对象的属性不是全部可枚举的

对象中的某些属性时通过for in无法遍历得到的,由enumerable属性判断,如果定义属性为不可枚举的那么就无法得到,propertyIsEnumerable可以判断属性是否是可枚举属性。如果你希望得到不可枚举属性,需要Object.getOwnPropertyNames(obj)或者Reflect.ownKeys(obj)。详细的参考下面的六种对象属性的查询方法。

1.for … in 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性). 2.Obejct.keys(obj),返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性). 3.Object.getOwnPropertyNames(obj),返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性). 4.Object.getOwnPropertySymbols(obj),返回一个数组,包含对象自身的所有Symbol属性. 5.Reflect.ownKeys(obj),返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举. 6.Reflect.enumerate(obj),返回一个Iterator对象,遍历对象自身的和继承的所有可枚举属性(不含Symbol属性),与for … in 循环相同.

参考地址:0621练习

原型链对对象实例化的影响

我们都知道子类可以得到父类的属性以及属性方法,那么实例化的过程与实际运行时是否会依赖这个呢 ?请看下面的代码执行结果.我们假设具有人类的父原型,再假设男人的子原型。

let person = function(){
  this.name = 'person'
}

let man = function(){
  this.call = function(){
    console.log(this.name)
  }
}
let xiaoli = new man();
xiaoli.call()//undefined
man.prototype = new person();
xiaoli.call()//undefined
let xiaoming = new man();
xiaoming.call()//'man'

总结:从上述的结果中可以分析得出对象在实例化的时候建立好了其对应的属性以及原型的引用关系,所以虽然man类原型有改变原型指向,但是并没有影响其输出。而xiaoming的实例在创建时原型的关系已经确定好,所以能输出得到其父属性。

js array:map方法是否需要返回

一般情况下,我们处理数组中map方法时知道其入参有三个,分别是元素,index,数组本身,而当我们使用箭头函数直接处理元素时,并且只有一句时,那么会默认的将元素处理完然后自己会默认的返回这个元素,但是当用{}包围之后,需要显性的声明返回值才可以返回对应的变化元素.

let arr = [1,6,67,90,'34']
let newArr = arr.map((ele,index,arr)=>
  parseInt(ele)
)
console.log(newArr)//正确数组返回

let newArr2 = arr.map((ele,index,arr)=>{
  parseInt(ele)
  }
)
console.log(newArr2)//返回undefined数组,此时需要语句中追加return parseInt(ele)

parseInt(str,numberArg)

一般情况下,我们都将数字或者含有字符串的数字用这个方法进行转换,而且默认是按照十进制转换,在不含数字的字符串或者转化失败时会返回NaN.那么如果针对一些数据我们第二个参数如果传入 0 1 2 3 8 10 16会返回什么呢?

let arr = [1,6,67,90,'34','sdc']
let newArr2 = arr.map((ele,index,arr)=>
  parseInt(ele,0)

)
console.log(newArr2)//正确
传入 0 [1, 6, 67, 90, 34, NaN]
传入1 [1, NaN, NaN, NaN, NaN, NaN]
传入2 [1, NaN, NaN, NaN, NaN, NaN]
传入9 [1, 6, 61, NaN, 31, NaN]

总结:从上述的分析可以得出其首先会过滤出数字的部分,然后针对数字的部分进行进制的转换,如果返回其进制那么进行返回,如果不是其用对应的进制可以转换的,就会返回NaN

代码实现promise

如果你要应付面试,如果不想或者不保证代码写出来,那么可以分别对关键部分进行伪代码的了解与说明。 – 说明其状态status的字段 ,以及其对应的返回值,错误原因,定义到构造器中 – 核心函数,resolve以及reject实现,进入每个函数时修改其状态,进行结果或者原因的赋值 – 执行resolve,以及reject函数 – 支持then链式操作 – 支持异步的关键,定义传入的回调函数,并修改then函数

class Promise{
  constructor(executor){
    //控制状态,使用了一次之后,接下来的都不被使用
    this.status = 'pendding'
    this.value = undefined
    this.reason = undefined

    //定义resolve函数
    let resolve = (data)=>{
      //这里pendding,主要是为了防止executor中调用了两次resovle或reject方法,而我们只调用一次
      if(this.status==='pendding'){
        this.status = 'resolve'
        this.value = data
      } 
    }

    //定义reject函数
    let reject = (data)=>{
      if(this.status==='pendding'){
        this.status = 'reject'        
        this.reason = data
      } 
    }

    //executor方法可能会抛出异常,需要捕获
    try{
      //将resolve和reject函数给使用者      
      executor(resolve,reject)      
    }catch(e){
      //如果在函数中抛出异常则将它注入reject中
      reject(e)
    }
  }
}

//then 函数主要是传入其resove或者reject的结果

then(onFufilled,onRejected){  
  if(this.status === 'resolve'){
    onFufilled(this.value)
  }
  if(this.status === 'reject'){
    onRejected(this.reason)
  }
}

//异步函数 callback
//存放成功回调的函数
this.onResolvedCallbacks = []
//存放失败回调的函数
this.onRejectedCallbacks = []

let resolve = (data)=>{
  if(this.status==='pendding'){
    this.status = 'resolve'
    this.value = data
    //监听回调函数
    this.onResolvedCallbacks.forEach(fn=>fn())
  } 
}
let reject = (data)=>{
  if(this.status==='pendding'){
    this.status = 'reject'        
    this.reason = data
    this.onRejectedCallbacks.forEach(fn=>fn())
  } 
}
//resolvePromise 重点源码的参考
function resolvePromise(promise2,x,resolve,reject){
  //判断x和promise2之间的关系
  //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
  if(x === promise2){//相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
    return reject(new TypeError('循环引用'))
  }
  //如果x不是null,是对象或者方法
  if(x !== null && (typeof x === 'object' || typeof x === 'function')){
    //为了判断resolve过的就不用再reject了,(比如有reject和resolve的时候)
    let called
    try{//防止then出现异常,Object.defineProperty
      let then = x.then//取x的then方法可能会取到{then:{}},并没有执行
      if(typeof then === 'function'){
        //我们就认为他是promise,call他,因为then方法中的this来自自己的promise对象
        then.call(x,y=>{//第一个参数是将x这个promise方法作为this指向,后两个参数分别为成功失败回调
          if(called) return;
          called = true
          //因为可能promise中还有promise,所以需要递归
          resolvePromise(promise2,y,resolve,reject)
        },err=>{
          if(called) return;
          called = true
          //一次错误就直接返回
          reject(err)
        })
      }else{
        //如果是个普通对象就直接返回resolve作为结果
        resolve(x)
      }
    }catch(e){
      if(called) return;
      called = true
      reject(e)
    }
  }else{
    //这里返回的是非函数,非对象的值,就直接放在promise2的resolve中作为结果
    resolve(x)
  }
}

掘金promise函数分析:推荐 廖雪峰promise了解与使用 promise原理的了解

函数防抖和节流的区别分析

函数防抖与节流的区别,首先防抖最明显的场景使用是避免重复点击,尤其像登录注册时;而节流最明显的场景是屏幕高频的滚动。

那么在代码具体实现上其差别会是什么呢?这里我们用伪代码解释下,首先两个都是用计时函数实现的,区别在于:

防抖函数会清除掉之前的计时器如果之前有,所以其触发频率会更低;而节流函数是判断其控制的相应时间,如果还在其时间内,那么会继续执行原来的计时器,不会销毁原来的。

然后我们在高频操作的滚动函数触发中分别进行了防抖以及节流的函数绑定,时间间隔均为300ms,最终发现防抖函数只执行了一次,而节流函数进行了若干次。

防抖与节流函数代码分析

js实现瀑布流布局

这里同样伪代码跟大家说明:我们常见的一般是横向流布局以及竖向流布局。 比如我们常见的百度图片就是横向瀑布流布局。

其主要的伪代码逻辑: 1 制定出比较合适的行高度,以及每行适合存放的图片数量 2 根据1得到的高度,计算出每个图片所得到的渲染宽度,渲染高度。其中最后一张图片,需要渲染为剩下的宽度+固定的高度(可能会有一定的变形)。 3 当剩余图片的数量不符合规格的时候,需要灵活的让其符合多种规格的布局 4 当只有一个图片时,可以自由发挥,需要限制一些边界情况即可。

特殊说明:对于瀑布流中的图片固定的放4列然后其瀑布流布局的,只要保证四列的布局,然后图片计算出图片对应的渲染高度,然后放入对应的列即可,比横向的瀑布流布局更简单。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏老司机的技术博客

golang学习笔记8:控制结构

关键字 if 和 else 之后的左大括号 { 必须和关键字在同一行,如果你使用了 else-if 结构,则前段代码块的右大括号 } 必须和 else-if 关...

7730
来自专栏静晴轩

JavaScript 之 this 详解

JavaScript作为一种脚本语言身份的存在,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript支持函数式编程、闭包、基于原型的继承等高级功...

46850
来自专栏Ryan Miao

String.split()用法以及特殊分隔符注意,ps:|

转载:http://www.cnblogs.com/mingforyou/archive/2013/09/03/3299569.html 在java.lang包...

33290
来自专栏十月梦想

JavaScript中类的创建以及类的传参

在之前(ES2015)以前我们常用构造函数来搞定一个事物类,通过new 这个构造函数实现类的功能!在ES6(ES2015)中已经可以使用类,下面我们看一下类如何...

13320
来自专栏小詹同学

Python系列之——字符串格式化(xiaozhan is a boy of 22 years old.)

不知道小伙伴有没有遇到过字符串输出有格式要求的情况呢?今天小詹学习分享一波python的字符串格式化的方法。学以致用,首先我们得明确为什么要格式化字符串输出,以...

7420
来自专栏马涛涛的专栏

JS面向对象二:this/原型链/new原理

也可以看看这篇文章周大侠啊 进击的 JavaScript(六) 之 this先了解一下`this的四种绑定规则和箭头函数的this绑定

19930
来自专栏偏前端工程师的驿站

(cljs/run-at (JSVM. :all) "Metadata就这样哦")

前言  动态类型语言,少了静态类型语言必须声明变量类型的累赘,但也缺失了编译时类型检查和编译时优化的好处。cljs虽然作为动态类型语言,但其提供Metadata...

21980
来自专栏开发与安全

从零开始学C++之模板(二):类模板、Stack的类模板实现(自定义链栈方式,自定义数组方式)

一、类模板 类模板:将类定义中的数据类型参数化 类模板实际上是函数模板的推广,可以用相同的类模板来组建任意类型的对象集合 (一)、类模板的定义 templ...

24400
来自专栏coder修行路

python---基础之模块,列表,元组,字典

1、 模块 写模块的时候尽量不要和系统自带的模块的名字相同 调用模块的时候,会先在当前目录下查找是否有这个模块,然后再会如python的环境变量中查找 a.模块...

21360
来自专栏Golang语言社区

Golang 语言--map 用range遍历不能保证顺序输出

按照之前我对map的理解,map中的数据应该是有序二叉树的存储顺序,正常的遍历也应该是有序的遍历和输出,但实际试了一下,却发现并非如此,网上查了下,发现从Go1...

44380

扫码关注云+社区

领取腾讯云代金券