前端笔试/面试题
文章目录
- 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.浅拷贝:
描述:只拷贝原值的地址,如果原地址发生改变,那么浅拷贝复制的对象也会改变
常用的数组浅拷贝方法:
- Array.concat()
- Array.slice()
- 使用拓展运算符
/*复杂数据类型
浅拷贝:复杂数据类型的传值
深拷贝:完全赋值一份数据一致的非同一个地址的数据
*/
var obj1 ={
a:1,
b:2
}
var obj2 = obj1
obj2.c = 3
console.log('obj1',obj1)
console.log('obj2',obj2)
3. 深拷贝:
描述:在计算机中开辟一块新的地址存储复制的对象
常用实现深拷贝的方法:
- 递归拷贝
- JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))进行深拷贝的缺点:
- 如果对象里有bigint类型会报错,对于bigint 类型无法转换
- 对于undefined,symbol,function 等类型会丢失对应的属性
- 对于error regexp 这些类型,属性值会变空
- 对于属性值是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
相同点:
- 作用都是改变函数执行时的上下文(改编this指向)
- 传入的第一个参数都是This要指向的对象,如果这个参数不存在或为undefined和null,则默认指向全局
不同点:
- 三者都可以传参,apply是数组,call是参数列表,且apply和call是一次性传参;bind可以分多次传入
- 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关键字
原理:
- 创建一个新对象
- 新对象内部的protorype 特性被赋值为构造函数的prototype属性
- 构造函数内部的this被赋值为这个新对象(this指向新对象)
- 执行构造函数内部代码(给对象添加属性)
- 如果构造函数返回非空对象,则返回该对象,否则返回刚创建的新对象
实现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.字面量
-
字面量创建对象,不会调用Object构造函数,简洁且性能更好;
-
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/数据劫持+发布订阅者模式
- 通过Object.defineProperty的get方法获取值,set方法设置拦截值
- 定义一个更新函数update和监听函数Watcher,将来对应数据变化时Watcher会调用更新函数update
- 将来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)
- 能包含基本操作,比如添加工资类型,标记完成
- 用你觉得最好的编码风格和方式来表达自己的基本功,抽象共用组件来表现自己解构能力。
- 界面UI不做要求,可使用各种UI库,布局尽量贴近草图即可。
- 可使用互联网,但不能让别人代写。
项目地址: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))