当前位置: 首页 > news >正文

前端笔试/面试题

文章目录

  • JS基础
    • 一、浅拷贝和深拷贝
      • 1. 基本类型值和引用类型值
      • 2.浅拷贝:
      • 3. 深拷贝:
    • 二、apply,bind,call
      • 相同点:
      • 不同点:
    • 三、new关键字和字面量创建的对象有什么不同
      • 1.new关键字
        • 原理:
        • 实现new
      • 2.字面量
    • 四、super()关键字
    • 五、static关键字
  • Vue
    • 一、V-model双向绑定原理
    • 二、画出一个页面
  • 算法
    • 一、合并两个数组
    • 二、转换对象
    • 三、版本判断
    • 四、拆分链接
    • 五、最短无序连续子数组
    • 六、翻转图像
    • 七、删除字符串中的 ' - '


在这里插入图片描述

JS基础

一、浅拷贝和深拷贝

1. 基本类型值和引用类型值

var name = "John"; // 基本类型值

var obj = new Object(); 
obj.name = "发财"; // obj 为引用类型值

我们的浅拷贝和深拷贝就是针对于引用类型值的说法

2.浅拷贝:

描述:只拷贝原值的地址,如果原地址发生改变,那么浅拷贝复制的对象也会改变

常用的数组浅拷贝方法:

  1. Array.concat()
  2. Array.slice()
  3. 使用拓展运算符
/*复杂数据类型
浅拷贝:复杂数据类型的传值
深拷贝:完全赋值一份数据一致的非同一个地址的数据
*/

var obj1 ={
  a:1,
  b:2
}
var obj2 = obj1

obj2.c = 3
console.log('obj1',obj1)
console.log('obj2',obj2)

3. 深拷贝:

描述:在计算机中开辟一块新的地址存储复制的对象
常用实现深拷贝的方法:

  1. 递归拷贝
  2. JSON.parse(JSON.stringify(obj))

JSON.parse(JSON.stringify(obj))进行深拷贝的缺点:

  1. 如果对象里有bigint类型会报错,对于bigint 类型无法转换
  2. 对于undefined,symbol,function 等类型会丢失对应的属性
  3. 对于error regexp 这些类型,属性值会变空
  4. 对于属性值是date 等类型的,属性值变成字符串,就算再次转换属性值也还是字符串

最常用的深拷贝方法就是递归进行拷贝
JSON.parse(JSON.stringify(obj))

var oo = {
  a:1,
  b:2
}
var oo2 = JSON.parse(JSON.stringify(oo))
oo2.name = '发财'
console.log(oo)
console.log(oo2)

在这里插入图片描述
手写深拷贝方法

var oneObj = {
  a:1,
  b:2,
  list:['a','b','c'],
  list1:[{name:'发财'},{name:'旺财'}],
  obj:{name:'发财'},
  c:undefined,
  d:()=>{
    alert('你好')
  }
}

function deep(obj){
  // 判断数据类型是否是数组/对象 进行新数据声明
  // 新建一个新的地址
  var targetObjet = obj instanceof  Array?[]:{}
  for (const key in obj){
    if (obj.hasOwnProperty(key)){
      const value = obj[key]
      if (value instanceof Array ||value instanceof Object){
        targetObjet[key] = deep(value)
      }else {
        targetObjet[key] = value
      }
    }
  }
  return targetObjet
}

var twoObj = deep(oneObj)

console.log(oneObj)
console.log(twoObj)

在这里插入图片描述

二、apply,bind,call

相同点:

  1. 作用都是改变函数执行时的上下文(改编this指向)
  2. 传入的第一个参数都是This要指向的对象,如果这个参数不存在或为undefined和null,则默认指向全局

不同点:

  1. 三者都可以传参,apply是数组,call是参数列表,且apply和call是一次性传参;bind可以分多次传入
  2. bind是返回绑定this之后的函数,apply、call是立即执行
function fn(...args){
  console.log(this,args);
}
let obj = {
  myName:"张三"
}
fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window

fn.call(obj,1,2); // this会变成传入的obj,传入的参数是一个参数列表;
fn(1,2) // this指向window

const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window

三、new关键字和字面量创建的对象有什么不同

1.new关键字

原理:

  1. 创建一个新对象
  2. 新对象内部的protorype 特性被赋值为构造函数的prototype属性
  3. 构造函数内部的this被赋值为这个新对象(this指向新对象)
  4. 执行构造函数内部代码(给对象添加属性)
  5. 如果构造函数返回非空对象,则返回该对象,否则返回刚创建的新对象

实现new

// 写法1
function myNew () {
	//创建一个新对象
	var obj = new Object ();
	//取出参数中的第一个参数,获得构造函数
	var constructor = Array.prototype.shift.call(arguments);
	//连接原型,新对象可以访问原型中的属性
	obj._proto_ = constructor.prototype;
	// 执行构造函数,即绑定 this,并且为这个新对象添加属性
	var result = constructor.apply(obj,arguments);
	return typeof result === "object" ? result :obj ;
}
// 写法2
function myNew1(fn) {
  const obj = Object.create(fn.prototype)
  let result = fn.apply(obj, [...arguments].slice(1))
  return typeof result === 'object' ? result : obj
}

let o1 = myNew1(Person,'ss',22)
console.log(o1)//Person { name: 'ss', age: 20, say: [Function (anonymous)] }

2.字面量

  1. 字面量创建对象,不会调用Object构造函数,简洁且性能更好;

  2. new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。

四、super()关键字

五、static关键字

在js中被Statuc 修饰的方法/变量,属于静态方法/变量,是类实例之间共享的。可以通过类名、方法/变量名直接引用

类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。这些通常是实用程序方法,例如创建或克隆对象的功能。

class Phone {
  //构造方法
  constructor(brand, price) {
    this.brand = brand
    this.price = price
  }

  call() {
    console.log('我能打电话')
  }
}

class SmartPhone extends Phone {
  constructor(brand, price, color, size) {
    super(brand, price)
    this.color = color
    this.size = size
    // 构造函数中直接调用静态方法的两种方法
    console.log(typeof SmartPhone.playGame())// typeof 判断的类型为 string
    console.log(typeof SmartPhone.playGame)// typeof 判断的类型为 function
    /**
     * 前者:加括号就代表着将会执行函数体代码,指针会指向函数体的返回值(没有返回值就算undefined)
     * 通常只有在函数调用的时候才会加上(),此时函数实际上等于函数的返回值或者执行效果
     * 有些函数没有返回值,但已经执行力函数体内的行为
     *
     * 后者:不加括号的就只是传递了函数所在的地址位置,在需要的时候好找到函数体去执行
     * 把函数名作为函数的指针,一个函数的名称就是这个函数的指针
     * 此时不是得到函数的结果,因为不会运行函数体代码
     */
  }

  message() {
    console.log('发短信')
  }

  static playGame() {
    console.log('玩游戏')
    return 'play Game'
  }
}

const xiaomi = new SmartPhone('小米', 1999, '黑色', '5.5inch')
// console.log(xiaomi)
// xiaomi.playGame()//报错,没有playGame()这个方法
// SmartPhone.playGame()//玩游戏

Vue

一、V-model双向绑定原理

这里我是手写了一个简单的Vue2的双向绑定

双向绑定原理 : MVVM/数据劫持+发布订阅者模式

  1. 通过Object.defineProperty的get方法获取值,set方法设置拦截值
  2. 定义一个更新函数update和监听函数Watcher,将来对应数据变化时Watcher会调用更新函数update
  3. 将来data中数据⼀旦发生变化,通知所有Watcher执行更新函数
// vue3.0之前使用的劫持方式是Object.defineProperty
// 3.0之后使用的是poxy
class Vue {
  constructor(obj_instance) {
    this.$data = obj_instance.data
    Observer(this.$data)
    Compile(obj_instance.el, this)
  }
}
// 数据劫持 - 监听实例里的数据
function Observer(data_instance) {
  //递归出口
  if (!data_instance || typeof data_instance !== 'object') return
  const dependency = new Dependency()
  //keys:返回其枚举自身属性的对象
  Object.keys(data_instance).forEach((key) => {
    // 存储修改之前的属性值
    let value = data_instance[key]
    Observer(value)
    //defineProperty:在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
    Object.defineProperty(data_instance, key, {
      enumerable: true,//设置可枚举
      configurable: true,//属性描述可改变
      get() {
        console.log(`访问了属性:${key}->值:${value}`)
        Dependency.temp && dependency.addSub(Dependency.temp)
        if (Dependency.temp) {
          console.log(Dependency.temp)
        }
        return value
      },
      set(newValue) {
        console.log(`属性${key}的值${value}修改为->${newValue}`)
        value = newValue
        Observer(newValue)
        dependency.notify()
      },
    })
  })
}

//HTML模板解析 - 替换DOM内
function Compile(element, vm) {
  // 获取页面元素
  vm.$el = document.querySelector(element)
  const fragment = document.createDocumentFragment()
  let child
  while ((child = vm.$el.firstChild)) {
    fragment.append(child)
  }
  fragment_compile(fragment)

  //修改文档碎片内容
  function fragment_compile(node) {
    // 正则判断是否为插值表达式
    const pattern = /\{\{\s*(\S+)\s*\}\}/
    // 第三行的节点是文本,判断如果节点等于3就是判断是否为文本
    if (node.nodeType === 3) {
      const xxx = node.nodeValue
      const result_regex = pattern.exec(node.nodeValue)
      if (result_regex) {
        const arr = result_regex[1].split('.')
        const value = arr.reduce(
          (total, current) => total[current], vm.$data,
        )
        node.nodeValue = node.nodeValue.replace(pattern, value)
        // 创建订阅者
        new Watcher(vm, result_regex[1], newValue => {
          node.nodeValue = xxx.replace(pattern, newValue)
        })
      }
      return
    }
    if (node.nodeType === 1 && node.nodeName === 'INPUT') {
      const attr = Array.from(node.attributes)
      attr.forEach(i => {
        if (i.nodeName === 'v-model') {
          const value = i.nodeValue.split('.').reduce(
            (total, current) => total[current], vm.$data,
          )
          node.value = value
          new Watcher(vm, i.nodeValue, newValue => {
            node.value = newValue
          })
          node.addEventListener('input', e => {
            const arr1 = i.nodeValue.split('.')
            const arr2 = arr1.slice(0,arr1.length-1)
            const final = arr2.reduce(
              (total,current) => total[current],vm.$data
            )
            final[arr1[arr1.length-1]] = e.target.value
          })
        }
      })
    }
    // 递归判断节点类型中所有的子节点
    node.childNodes.forEach(child => fragment_compile(child))
  }

  vm.$el.appendChild(fragment)
}

// 依赖 - 收集和通知订阅者
class Dependency {
  constructor() {
    this.subscribers = []
  }

  addSub(sub) {
    this.subscribers.push(sub)
  }

  notify() {
    this.subscribers.forEach(sub => sub.update())
  }
}

// 订阅者(监听器)
class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm
    this.key = key
    this.callback = callback
    // 临时属性
    Dependency.temp = this
    key.split('.').reduce((total, current) => total[current], this.vm.$data)
  }

  update() {
    const value = this.key.split('.')
      .reduce((total, current) => total[current], this.vm.$data)
    this.callback(value)
  }
}

二、画出一个页面

在这里插入图片描述

用react+mobx 或者 vue+vuex, scss编写界面(web界面01.png)

  1. 能包含基本操作,比如添加工资类型,标记完成
  2. 用你觉得最好的编码风格和方式来表达自己的基本功,抽象共用组件来表现自己解构能力。
  3. 界面UI不做要求,可使用各种UI库,布局尽量贴近草图即可。
  4. 可使用互联网,但不能让别人代写。

项目地址:https://gitee.com/TangShixing/basics/tree/master/Vue_Study/guoqinyixin

算法

一、合并两个数组

设计一个arrMerge方法传入两个数组,合并数据并去重

// const arr1 = [1,9,3,6,12];
// const arr2 = [0,2,10,3,12,12];
//
// merge(arr1, arr2)         // [0,1,2,3,6,9,10,12]

const arr1 = [1,9,3,6,12];
const arr2 = [0,2,10,3,12,12];

function arrMerge(arr1,arr2){
  let arr = [...arr1,...arr2].sort((a,b)=>a-b)
  let set = new Set(arr)
  return  Array.from(set)
}

console.log(arrMerge(arr1, arr2))

二、转换对象

// print({ name: 'Tom', age: 12 })
// 打印:2 keys,name is 'Tom', age is 12

let obj = { name: 'Tom', age: 12 }

function print(obj) {
  let num = 0
  let str_pro = `${num} keys,`
  for (const key in obj) {
    let str = ``
    num++
    str = `${key} is ${obj[key]}`
    str_pro += str + ','
  }
  return console.log(str_pro.substr(0,str_pro.length-1))
}

print(obj)

三、版本判断

设计一个compare方法传入两个数据,判断其版本大小

// function compare(version1, version2) {
// ...
// }
//
// compare('0.1', '1.1.1'); // 返回-1
// compare('13.37', '1.2 '); // 返回1
// compare('1.1', '1.1.0'); // 返回0
// compare('1.2', '1.12')

function compare(version1, version2) {
  if (typeof version1 === 'number' && typeof version2 === 'number') {
    if (!(version1 - version2)) return -1
    return 1
  }
  let arr1 = version1.split('.')
  let arr2 = version2.split('.')
  let sum = arr1.length - arr2.length
  if (sum>0){
    arr2.push('0')
  }else if (sum<0){
    arr1.push('0')
  }
  for (let i = 0; i < len(arr1, arr2); i++) {
    if (arr2[i].length >1 || arr1[i].length >1){
      arr1[i]=arr1[i].split('')[0]
      arr2[i]=arr2[i].split('')[0]
    }
    if (arr1[i] - arr2[i] > 0) {
      return 1
    } else if (arr1[i] - arr2[i] < 0) {
      return -1
    }
  }
  return 0
}

function len(arr1, arr2) {
  if (arr1.length - arr2) return arr1.length
  return arr2.length
}

console.log(compare('1.1', '1.1.0'))

四、拆分链接

// 输入 URL : https://google.com/search?a=b&c=d
//
//   返回结果 : { a: 'b', c: 'd' }
let url =  'https://google.com/search?a=b&c=d'

function parseQueryString(url) {
  let str = url.substring(url.indexOf('search?')+7,url.length)
  str = str.replaceAll('=','').replaceAll('&','').split('')
  let obj = {}
  for (let i = 0; i < str.length; i++) {
    if ((i%2)===0){
      obj[str[i]] = str[i+1]
    }
  }
  console.log(obj)
}
parseQueryString(url)

五、最短无序连续子数组

在这里插入图片描述

/**
 * @param {number[]} numbers
 * @return {number}
 */
function shortChildrenArr(numbers) {
  let numLen = numbers.length
  if (numbers <= 1) return 0
  let numberSort = numbers.concat().sort((a,b)=>{return a-b})
  if (numberSort === numbers) return numLen
  let sum = numberSort.length
  for (let i = 0, j = numLen-1; i < j;i++,j--) {
    if (numberSort[i]===numbers[i]){
      sum--
    }
    if (numberSort[j]===numbers[j]){
      sum--
    }
  }
  return sum
}
console.log(shortChildrenArr([2,6,4,8,10,9,15]))

六、翻转图像

在这里插入图片描述

/**
 * @param {number[][]} image
 * @return {number[][]}
 */
function reverseImg(image) {
  for (let i = 0; i < image.length; i++) {
    image[i].reverse()
    for (let j = 0; j < image[i].length; j++) {
      if (image[i][j] === 0) {
        image[i][j] = 1
      } else {
        image[i][j] = 0
      }
    }
  }
  return image
}

console.log(reverseImg([[1, 1, 0, 0], [1, 0, 0, 1], [1, 0, 1, 0]]))

七、删除字符串中的 ’ - ’

let test = 'font-size'//fontSize
let test2 = '-fo-nt-siz-e' //foNtSizE
let num

function fun(str) {
  if (!(str instanceof Array)) {
    str = str.split('')
  }
  if (!str.indexOf('-')) {
    str = str.slice(1, str.length)
    return fun(str)
  } else if ((num = str.indexOf('-')) !== -1) {
    str = fun(fun1(str))
  }
  if (str instanceof Array){
    str1 = str.join('')
  }
  return str1
}

function fun1(arr) {
  let arr1 = arr.slice(0, num)
  let arr2 = arr.slice(num + 1)
  arr2[0] = arr2[0].toUpperCase()
  return [...arr1, ...arr2]
}

console.log(fun(test))

相关文章:

  • 海纳网站建设/搜索引擎优化的英文
  • 十大深夜看黄禁用免费撒娇/谷歌排名网站优化
  • 自适应wordpress/友情链接代码
  • 多少钱日语怎么说/湖南seo网站开发
  • 仿抖音wordpress/北京推广
  • 什么是一学一做视频网站好/合肥做网站公司哪家好
  • 机器学习SVM函数
  • 李沐论文精读系列一: ResNet、Transformer、GAN、BERT
  • 写一个移动端(H5)的前端项目需要哪些插件以及常用的一些业务逻辑代码块,来look look 吧「干货满满」。
  • 传感器_高精度热敏电阻测量温度算法_有序浮点型数据使用二分法查询最接近的值
  • 【STL】vector的模拟实现
  • 公开学术报告笔记 | 上海雷达同心学术论坛之雷达图像解译技术研讨会-合成孔径雷达学习成像方法初探
  • 13-无监督学习概论
  • 在线时间戳转换工具的坑-同样的时间戳转为北京时间,转换结果受本机时区设置的影响...
  • 写在博士旅程之前|博士第一年|博士第三年|博士第四年
  • 用servlet写一个hello word
  • 【以太网硬件十五】网络变压器是什么?
  • React 路由v6 - 超全详解