又金九银十了,把今年的面试记录翻了翻,发现还不少,趁着这个机会总结一下
这是24年春季招聘的面试题
1、dispaly设置none会引起什么,渲染的时候,dom树和渲染树是一一对应的么
2、手写几种元素水平垂直居中方法
3、transform相较于margin的好处
4、position定位
5、移动端适配
6、vw、vh、rem、em有什么区别
7、手写准确判断数据类型方法以及返回的示例
8、上面slice中的参数表示什么
9、其他的类型判断方式与区别
10、手写instanceof方法
11、手写一个JS实现继承方式
12、节流防抖的区别,手写防抖
13、如何封装组件
14、如何提高前端页面性能
15、xss攻击了解吗?
16、csrf呢?localstorage存了token,怎么解决?
17、vue源码了解吗,模板编译,render函数,响应式原理,watcher和dep,patch,diff算法,computed的实现,nexttick的实现知道吗?
18、项目中做过rbac权限控制token吗,简单说说?
19、算法题:反转链表
元素不会在页面上被显示,它将从文档流中移除,并且不会占据任何空间。其他元素将会填补其位置。元素的事件监听器将被禁用,不再与页面交互。
DOM 树和渲染树并不是一一对应的,DOM 树是由浏览器解析 HTML 文档而生成的,它表示了整个文档的结构和内容,包括 HTML 元素、文本节点和属性等。DOM 树中的每个节点都对应着 HTML 中的一个元素或文本。
渲染树是在 DOM 树和 CSSOM 树结合之后生成的,用于表示页面上需要渲染的元素及其样式信息。渲染树中的节点称为渲染对象(Render Object),它们与 DOM 树中的节点相对应,但并不是一一对应的关系。
在构建渲染树时,一些不需要显示的节点(如 <script>
、<head>
、display: none
和一些不可见的元素)会被省略,而只有需要显示的元素才会被包含在渲染树中
详细的实现方式:我已经说了5种css居中实现的方式了,面试官竟然说还不够
<body>
元素进行定位。可以使用 top、right、bottom、left 属性进行定位。如果没有已定位的祖先元素,则相对于视口进行定位。<meta>
标签中的 viewport 属性来控制网页在移动设备上的显示方式。设置 viewport 的宽度、缩放比例、初始缩放等属性,以确保页面在不同设备上正确缩放和显示。vw、vh、rem 和 em 都是用于响应式布局和移动端适配的相对单位,它们有一些区别:
主要区别如下:
使用Object.prototype.toString方法可以获取一个值的具体类型
function getType(obj) { return Object.prototype.toString.call(obj).slice(8, -1); } console.log(Object.prototype.toString.call(42)); // 输出 "[object Number]" console.log(Object.prototype.toString.call('Hello')); // 输出 "[object String]" console.log(Object.prototype.toString.call(true)); // 输出 "[object Boolean]" console.log(Object.prototype.toString.call(undefined)); // 输出 "[object Undefined]" console.log(Object.prototype.toString.call(null)); // 输出 "[object Null]" console.log(Object.prototype.toString.call([])); // 输出 "[object Array]"
.slice(8, -1)
的含义是截取字符串中从第 8 个字符到倒数第 1 个字符之间的部分。也就是说它会去掉字符串开头的 "[object " 和结尾的 "]",只返回中间的类型部分。
.slice()
方法可以接受两个参数,分别是起始位置和结束位置。如果省略结束位置,则表示截取到字符串末尾。如果参数是负数,则表示倒数第几个字符。因此 -1
表示倒数第一个字符
typeof
操作符:typeof
操作符可以返回一个表示操作数类型的字符串。typeof只能判断基础类型,typeof null
返回 "object" 是 JavaScript 的历史遗留问题。实际上,null 是一个空对象指针,应该返回 "null" 才更准确。
console.log(typeof 42); // 输出 "number" console.log(typeof 'Hello'); // 输出 "string" console.log(typeof true); // 输出 "boolean" console.log(typeof undefined); // 输出 "undefined" console.log(typeof null); // 输出 "object"(注意这是个历史遗留问题) console.log(typeof []); // 输出 "object"
instanceof
操作符:instanceof
操作符用于检查对象是否属于某个类或构造函数的实例。let arr = []; let date = new Date(); console.log(arr instanceof Array); // 输出 true console.log(date instanceof Date); // 输出 true
Array.isArray()
方法:Array.isArray()
方法用于判断一个值是否为数组。function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left); let prototype = right.prototype; while(true) { if(proto === null) { // 到达原型链顶端仍未找到目标原型,则返回 false return false; } if(proto === prototype) { // 找到目标原型,则返回 true return true; } proto = Object.getPrototypeOf(proto); // 继续查找 } } function Person() {} let p = new Person(); console.log(myInstanceof(p, Person)); // true
首先通过 Object.getPrototypeOf
方法获取对象 left
的原型,并将其保存在变量 proto
中。然后,获取目标构造函数 right
的原型,并将其保存在变量 prototype
中。
接下来,使用一个无限循环来遍历 left
对象的原型链。while(true)
会导致无限循环,如果没有找到目标原型将一直循环下去,因此需要在循环内部做出判断避免死循环。
但是,使用原型链继承时,父类中引用类型的属性会被所有子类实例共享,容易出现修改一个子类实例中的引用类型属性,导致其他子类实例中的该属性也被修改的问题。
function Parent() { this.name = 'Parent'; this.colors = ['red', 'green', 'blue']; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() { this.name = 'Child'; } Child.prototype = new Parent(); let child1 = new Child(); child1.sayName(); // "Child"
借用构造函数继承的实现比较简单,只需要在子类的构造函数中调用父类的构造函数,并将 this
指向子类实例即可。这样可以避免引用类型属性被所有子类实例共享的问题。但是,使用该种方式继承时,子类无法访问到父类原型对象中定义的方法和属性。
function Parent() { this.name = 'Parent'; this.colors = ['red', 'green', 'blue']; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() { Parent.call(this); this.name = 'Child'; } let child1 = new Child(); child1.sayName(); // 报错:child1.sayName is not a function
防抖:n 秒后再执行回调,若在 n 秒内被重复触发,则重新计时;防抖的基本思想是在函数被连续调用时,只执行最后一次调用,并在指定的时间间隔内没有新的调用才执行函数。如果在时间间隔内有新的调用,则重新计时。
function debounce(func, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, delay); }; }
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效;节流的基本思想是限制函数在一定时间间隔内的执行次数,例如每隔一段时间执行一次,并在该时间间隔内忽略其他的函数调用。
function throttle(func, delay) { let timer; return function (...args) { if (!timer) { timer = setTimeout(() => { func.apply(this, args); timer = null; }, delay); } }; }
首先进行需求分析,确定组件功能和实现方式。再定义组件 API,包括传入的 props 和事件。最后编写组件测试用例,确保组件功能正确。
封装组件时要注意组件的可复用性、易维护性和可测试性。在编写组件时,应该遵循单一职责原则,尽量避免组件之间的耦合,提供清晰简洁的 API,确保组件功能正确,并编写充分的测试用例,以便于其他开发者和用户使用和维护组件。
XSS(跨站脚本攻击)是一种常见的网络安全漏洞,攻击者通过在网页中注入恶意脚本代码,使得用户在浏览网页时执行该恶意脚本,从而达到攻击的目的。XSS 攻击可以分为三种类型:
为了防止 XSS 攻击,可以采取以下措施:
CSRF(跨站请求伪造)是一种利用受信任用户的身份执行非意愿操作的攻击方式。
在 CSRF 攻击中,攻击者通过诱使受害者在登录状态下访问恶意网站或点击恶意链接,来执行受害者在其他网站上的非意愿操作,如修改密码、发表言论等。
对于使用 localStorage 存储 token 的情况,可以采取以下措施来防止 CSRF 攻击:
当 Vue 编译模板时,会将模板字符串转换为渲染函数,这个过程称为模板编译。模板编译的目的是将模板转换为可执行的 JavaScript 代码,用于生成 Virtual DOM(虚拟 DOM)树并最终渲染到真实的 DOM 上。
Vue 的模板编译分为以下几个阶段:
需要注意的是,Vue 的模板编译只在开发阶段进行,生产环境中通常会使用预编译的方式,将模板编译为 render 函数,并在运行时直接使用 render 函数进行渲染,以提高性能。
render 函数是用来生成 Virtual DOM 树的函数,在 Vue 中,模板字符串会被编译成一个 render 函数,然后在组件实例化时执行这个 render 函数,生成对应的虚拟 DOM 树
在 render 函数中,以使用 h
函数创建不同类型的虚拟 DOM 元素,除了使用 h
函数外,render 函数还可以使用 JavaScript 的标准语法,包括变量、表达式、条件语句、循环语句等。通过这些语法结构,可以根据组件的状态和属性动态地生成 Virtual DOM 树,从而实现组件的渲染和更新。
Vue 的响应式原理基于以下几个核心概念:
data
函数或者 props
中的数据进行递归地劫持,即将数据转换为可监听的对象,并在对象上添加一个名为 __ob__
的 Observer 实例,用于监听数据的变化。Watcher、Dep、Patch和Diff算法是Vue响应式原理中的重要概念和机制。Watcher负责跟踪数据的变化并调度更新操作,Dep用于收集依赖于数据属性的Watcher实例,Patch将虚拟DOM转换为实际的DOM操作,Diff算法用于比较新旧虚拟DOM树的差异并生成补丁操作。这些机制协同工作,实现了Vue的数据驱动视图更新的核心功能。
Watcher是连接依赖收集器(Dep)和更新操作之间的桥梁。每个组件实例都会创建一个Watcher实例,当组件渲染时,Watcher会在渲染过程中跟踪所有被使用的数据属性,并将自身添加到这些数据属性的依赖收集器中。当数据发生变化时,Watcher会被通知并调度更新操作。
Dep是一个用于存储Watcher实例的容器。每个被劫持的数据属性(如对象的属性或数组的元素)都会有一个对应的Dep实例,用于收集依赖于该属性的Watcher实例。当属性被读取时,Watcher会被添加到Dep实例中;当属性发生变化时,Dep实例会通知其中的所有Watcher实例进行更新操作。
Patch是指将虚拟DOM(Virtual DOM)转换为实际DOM操作的过程。在Vue的更新机制中,Vue会先将模板编译成虚拟DOM树,然后通过比较新旧虚拟DOM树的差异(Diff算法),找到需要更新的部分,并生成一系列的补丁操作,最后将这些补丁操作应用到实际的DOM上,从而完成视图的更新。
Diff算法是比较两个树结构差异的算法。在Vue中,当数据发生变化时,Vue会通过比较新旧虚拟DOM树的差异,找出需要更新的部分,而不是直接重新渲染整个组件。Diff算法通过遍历新旧虚拟DOM树的节点,逐个比较它们的差异,并生成一系列的补丁操作(Patch)来描述这些差异。这样可以减少了不必要的DOM操作,提高了性能。
RBAC权限控制 RBAC(Role-Based Access Control)是基于角色的访问控制,是一种常用的权限管理模型。它将用户分配到不同的角色中,每个角色被赋予不同的权限,而用户只能使用与其角色相匹配的权限。在实际项目中,可以通过RBAC来实现对系统中各种资源(如API接口、页面元素等)的权限控制。
RBAC权限控制和Token认证的使用,流程大致如下:
在实现RBAC权限控制和Token认证时,通常需要使用一些框架和工具来简化开发。例如,可以使用Spring Security框架来实现RBAC权限控制,使用JWT(JSON Web Token)来实现Token认证。同时,为了保证安全性,还需要注意一些细节问题,如Token的有效期、Token的加密方式、token的存储方式等。
使用迭代法反转链表的过程如下:
function reverseList(head) { let prev = null; let curr = head; while (curr) { let nextNode = curr.next; curr.next = prev; prev = curr; curr = nextNode; } return prev; } let reversedHead = reverseList(head);#机械制造笔面经##24届软开秋招面试经验大赏##我发现了面试通关密码##快手求职进展汇总#