飞道的博客

JS从看懂到看开(前端面试题整合)

272人阅读  评论(0)

解释一下为何[ ] == ![ ]   // ---> true

首先看一张图


![ ] 是 false
原式:[ ] == false
根据第八条,false通过tonumber()转换为0
原式:[ ] == 0
根据第十条,[ ]通过ToPrimitive()转换为'  '
原式:' ' == 0
根据第六条
原式:0 == 0

尝试实现new


  
  1. function ObjectClass() { //对象
  2. console.log( arguments[ 0])
  3. }
  4. ObjectClass.prototype.constructor = ObjectClass
  5. function create() {
  6. // 创建一个空的对象
  7. var obj = {}
  8. // 获得构造函数
  9. var _constructor = this
  10. // 链接到原型
  11. obj.__proto__ = _constructor.prototype
  12. // 绑定 this,执行构造函数
  13. var result = _constructor.apply(obj, arguments)
  14. // 确保 new 出来的是个对象
  15. return typeof result === 'object' ? result : obj
  16. }
  17. create.call(ObjectClass, 'hello world') //实例化

拓展typeof功能使其支持更多类型(array,object,null区分),并解释一下typeof null为何是object


  
  1. function myTypeOf(target) {
  2. var _type = typeof (target)
  3. var temp = {
  4. "[object Object]": 'object',
  5. "[object Array]": 'array',
  6. "[object Number]": 'number',
  7. "[object String]": 'string',
  8. "[object Boolean]": 'boolean'
  9. }
  10. if (target === null) {
  11. return 'null'
  12. } else if (_type == 'object') {
  13. var str = Object.prototype.toString.call(target) //根据toString区分
  14. return temp[str]
  15. } else {
  16. return _type
  17. }
  18. }
  19. console.log(myTypeOf( 'hello')) //string
  20. console.log(myTypeOf( 111)) // number
  21. console.log(myTypeOf( true)) // boolean
  22. console.log(myTypeOf({})) // object
  23. console.log(myTypeOf([])) // array
  24. console.log(myTypeOf( null)) // null
  25. console.log(myTypeOf( undefined)) // undefined
  26. console.log(myTypeOf( Symbol())) // symbol

typeof null为何是object

因为在早期js初版本中,操作系统使用的是32位,出于性能考虑,使用低位存储变量类型,object的类型前三位是000,而null是全0,从而系统将null误判为object

instanceof是什么?尝试实现一下

用官话来讲:instanceof用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

通俗来讲,a instanceof b也就是判断a是否是由b实例化得来的

实现:


  
  1. function ObjectClass() {}
  2. ObjectClass.prototype.constructor = ObjectClass
  3. var _objectClass = new ObjectClass()
  4. function myInstanceof(orgProto, tag) { //org前者,实例化对象, tag后者,类
  5. var tagProto = tag.prototype
  6. orgProto = orgProto.__proto__
  7. for (;;) { //死循环查询原型链上是否有类的原型
  8. if (orgProto === null) {
  9. return false
  10. }
  11. if (orgProto === tagProto) {
  12. return true
  13. }
  14. orgProto = orgProto.__proto__
  15. }
  16. }
  17. console.log(myInstanceof( Object, Function)) // true
  18. console.log(myInstanceof( Object, Object)) // true
  19. console.log(myInstanceof( String, Object)) // true
  20. console.log(myInstanceof(_objectClass, Object)) // true
  21. console.log(myInstanceof( String, String)) // false
  22. console.log(myInstanceof( Boolean, Boolean)) // false

解释以下代码分别在控制台显示什么,并简单说明

有一个对象Car,分别对以下四种情况进行作答


  
  1. Car.prototype.name = 'BMW'
  2. function Car() {}

1.实例化对象时打印BMW,因为Car.prototype.name = 'BMW',实例化的car本身没有name属性,于是会在Car的原型上找。此时将Car.prototype.name = 'Benz',实例化后的car.name也会等于Benz,因为name是基本数据类型(原始值),当值发送变化,实例化后的对象也会改变


  
  1. var car = new Car()
  2. console.log(car.name) //BMW
  3. Car.prototype.name = 'Benz'
  4. console.log(car.name) //Benz

2.实例化对象时打印Benz,因为在实例化之前就已经改变构造函数原型上的name值


  
  1. Car.prototype.name = 'Benz'
  2. var car = new Car()
  3. console.log(car.name) //Benz

3.第一个log的BMW与上述一样,第二个log依然打印BMW的原因是,这里将Car.prototype直接改变成另一个对象,由于对象是引用数据类型(引用值),指向的是内存地址而不是值,new之前和new之后的实例对象引用的name地址不同


  
  1. var car = new Car()
  2. console.log(car.name) //BMW
  3. Car.prototype = {
  4. name: 'Benz'
  5. }
  6. console.log(car.name) //BMW

4.和上述相同,原因是修改了prototype,改变的是引用地址,new之前和new之后的实例对象引用的name地址不同


  
  1. Car.prototype = {
  2. name: 'Benz'
  3. }
  4. var car = new Car()
  5. console.log(car.name) //Benz

写一个函数,计算字符串Unicode总长度(例如:abcd,打印4,qwerdf,打印6)

需要注意的是,英文字符占1个字节,中文字符占两个字节


  
  1. function unicodeLength(str) {
  2. for ( var i = 0, count = 0; i < str.length; i++) {
  3. console.log(str.charCodeAt(i))
  4. if (str.charCodeAt(i) > 255) { //中文字符
  5. count += 2
  6. } else { //英文字符
  7. count++
  8. }
  9. }
  10. return count
  11. }
  12. console.log(unicodeLength( 'hello,1024,你好')) //17

实现一下js中window自带的isNaN()函数

注意点:如果直接使用NaN==NaN来判断,会返回false,需要将NaN转换成字符串,再来判断


  
  1. isNaN( 'asda') //window下的原函数
  2. console.log( isNaN( 13)) //false
  3. console.log( isNaN( 'aaa')) //true
  4. function myIsNaN(number) {
  5. return "" + Number(number) == "NaN" ? true : false
  6. }
  7. console.log(myIsNaN( 32323)) //false
  8. console.log(myIsNaN( 'aaa')) //true

实现数组push()方法


  
  1. function myPush() {
  2. for ( var i = 0; i < arguments.length; i++) {
  3. this[ this.length] = arguments[i]
  4. }
  5. return this.length
  6. }
  7. Array.prototype.myPush = myPush
  8. var list = [ 1, 2, 3, 4, 5]
  9. var item = 6
  10. console.log(list.myPush(item)) //6
  11. console.log(list) //[1, 2, 3, 4, 5, 6]

实现数组乱序(提示:使用Array.sort)

Array.sort((a,b)=>{})中a-b升序,b-a降序


  
  1. Array.prototype.random = random
  2. function random() {
  3. this.sort( function () {
  4. return Math.random() - 0.5
  5. })
  6. return this
  7. }
  8. var list = [ 1, 2, 3, 4, 5, 6, 7, 8, 9]
  9. console.log(list.random()) //[3, 2, 6, 4, 9, 8, 1, 5, 7] 结果每次都不同

以下代码在控制台显示什么?说明原因


  
  1. var obj = {
  2. "0": 'a',
  3. "1": 'b',
  4. "2": 'c',
  5. "length": 3,
  6. "push": Array.prototype.push
  7. }
  8. obj.push( 1, 2, 3)
  9. console.log(obj)

打印结果是


  
  1. {
  2. 0: "a"
  3. 1: "b"
  4. 2: "c"
  5. 3: 1
  6. 4: 2
  7. 5: 3
  8. length: 6
  9. }

原因:说明原因之前先看一段Array.prototype.push的源码:


  
  1. function ArrayPush () {
  2. var n = TO_UNIT32( this.length);
  3. var m = %_ArgumentsLength();
  4. for ( var i = 0; i < m; i++) {
  5. this[i + n ] = %_Arguments(i);
  6. } this.length = n + m;
  7. return this.length;
  8. }

push的原理是在原对象后面将push的内容遍历进去,获取this.length并且在此基础上加上push的个数,这就不难解释为何push了三个数后length为6

解释以下代码打印为undefined的原因


  
  1. var num = 123;
  2. num.item = 'abc'
  3. console.log(num.item) //undefined

第一步:var num = 123

第二步:num.item = 'abc'//隐式转换,相当于new Number(num).item = 'abc'(包装类生成引用类型数据),此时底层会判定此时的num是原始值,不存在属性值,所以执行delete(num.item)

第三步:打印undefined

使用JS原生实现function中的call,apply,bind函数

call:


  
  1. Function.prototype.myCall = function () {
  2. var _this = arguments[ 0] || window; //第一项是需要this指向的对象
  3. _this._function = this //this是要执行的函数,改变指向为_this
  4. var args = [] //把除this之外的所有参数放在args中
  5. for ( var i = 1; i < arguments.length; i++) { //i = 1,第二项到最后一项是参数
  6. args[i - 1] = arguments[i]
  7. }
  8. return eval( "_this._function(" + args + ")") //eval能将数组隐式拆分,效果与join相似,但二者区别很大,return将函数执行结果返回
  9. delete _this._function //执行完成后删除当前_function,这个_function用来放this
  10. }
  11. var a = 'window'
  12. var obj1 = {
  13. a: 'obj1',
  14. fn: function () {
  15. console.log( this.a)
  16. console.log( arguments)
  17. }
  18. }
  19. var obj2 = {
  20. a: 'obj2'
  21. }
  22. obj1.fn.myCall(obj2, 1, 2, 3, 4) //obj2 arguments[1, 2, 3, 4]
  23. obj1.fn.myCall( this, 3, 2, 1) //window arguments[3, 2, 1]

apply(调用上面的myCall实现即可):


  
  1. Function.prototype.myApply = function () {
  2. var _this = arguments[ 0] || window; //第一项是需要this指向的对象
  3. _this._function = this //this是要执行的函数,改变指向为_this
  4. return eval( "_this._function.myCall(_this, " + arguments[ 1] + ")") //eval能将数组隐式拆分,效果与join相似,但二者区别很大,return将函数执行结果返回
  5. delete _this._function //执行完成后删除当前_function,这个_function用来放this
  6. }
  7. var a = 'window'
  8. var obj1 = {
  9. a: 'obj1',
  10. fn: function () {
  11. console.log( this.a)
  12. console.log( arguments)
  13. }
  14. }
  15. var obj2 = {
  16. a: 'obj2'
  17. }
  18. obj1.fn.myApply(obj2, [ 1, 2, 3, 4]) //obj2 arguments[1, 2, 3, 4]
  19. obj1.fn.myApply( this, [ 3, 2, 1]) //window arguments[3, 2, 1]

bind(继续调用上面myApply):


  
  1. Function.prototype.myBind = function () {
  2. var t = this;
  3. var _this = arguments[ 0] || window; //第一项是需要this指向的对象
  4. var args = Array.prototype.slice.myApply( arguments, [
  5. 1], ) //这项的目的是为了去除第一项arguments[0],就与上面的myCall中的遍历作用相同,Array.prototype.slice传一个参数,slice(start,end)表示删除第start到end项并返回删除后的数组,这里我们只用截取,不用删除,这里是删除第一项(由于用的是myApply,第二个参数是数组所以用[1])并返回删除后的数组
  6. return function () {
  7. return t.myApply(_this, args)
  8. }
  9. }
  10. var a = 'window'
  11. var obj1 = {
  12. a: 'obj1',
  13. fn: function () {
  14. console.log( this.a)
  15. console.log( arguments)
  16. }
  17. }
  18. var obj2 = {
  19. a: 'obj2'
  20. }
  21. obj1.fn.myBind(obj2, 1, 2, 3, 4)() //obj2 arguments[1, 2, 3, 4]
  22. obj1.fn.myBind( this, 3, 2, 1)() //window arguments[3, 2, 1]

对mvvm,mvp和mvc的理解

Model–View–ViewModel(MVVM),Model-View-Presenter(MVP)和Model–View-Controller(MVC) 都是软件架构设计模式

相同的地方

  • Model 是指任何一个领域模型(domain model),一般做数据处理,可以理解为数据库,用来存放应用的所有数据对象。模型不必知晓视图和控制器的细节,模型只需包含数据及直接和这些数据相关的逻辑。任何事件处理代码、视图模版,以及那些和模型无关的逻辑都应当隔离在模型之外,它代表了真实情况的内容(一个面向对象的方法),或表示内容(以数据为中心的方法)的数据访问层
  • View就是用户界面(UI),视图层是呈现给用户的,用户与之产生交互。在javaScript应用中,视图大都是由html、css和JavaScript模版组成的。除了模版中简单的条件语句之外,视图不应当包含任何其他逻辑。事实上和模型类似,视图也应该从应用的其他部分中解耦出来

不同的地方

  • MVC的Controller控制器是模型和视图的纽带。控制器从视图获得事件和输入,对它们进行处理,并相应地更新视图。当页面加载时,控制器会给视图添加事件监听,比如监听表单提交和按钮单击。然后当用户和应用产生交互时,控制器中的事件触发器就开始工作。
  • MVVM的ViewModel是一个公开公共属性和命令的抽象的view。取代了 MVC 模式的 controller,或 MVP 模式的任命者(presenter),MVVM 有一个驱动。 在 viewmodel 中,这种驱动传达视图和数据绑定的通信。此 viewmodel 已被描述为该 model 中的数据的状态。
  • MVP的Presenter负责逻辑的处理,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter来进行的,所有的交互都发生在Presenter内部,而 在MVC中View会直接从Model中读取数据而不是通过Controller。

谈谈对前端页面渲染的理解(过程,原理,性能,重绘和回流)

页面渲染分为以下步骤
1. 处理HTML语句标签并构建 DOM 树
2. 处理CSS语句并构建CSSOM树
3. 将处理好的DOM与CSSOM合并成一个渲染树
4. 根据渲染树来布局,计算每个节点的位置样式等等
5. 调 GPU(显卡)绘制页面,合成图层,最后显示在浏览器

在处理CSSOM时,会暂时堵塞DOM渲染,并且扁平层级关系有利于渲染速度,越详细的样式选择器,会导致页面渲染越慢
CSS加载会影响JS文件或语句加载,JS需要等待CSS解析完毕后运行

document中的DOMContentLoaded和Load的区别​​:前者只需HTML加载完成后,就会触发,后者需要等HTML,CSS,JS都加载完成才会触发​​​​​

图层概念:普通文档流就是一个图层,特定的属性可以生成一个新的图层。 不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用
以下CSS属性可以生成新图层:

  • 3D 变换:translate3d、translateZ
  • will-change
  • video、iframe 标签
  • 通过动画实现的 opacity 动画转换
  • position: fixed

重绘(Repaint)和回流(Reflow)
重绘是当节点需要更改外观而不会影响布局的,比如改变color就叫称为重绘回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。
回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。

所以以下几个动作可能会导致性能问题:

  • 改变 window 大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

如何减少重绘和回流

  • 使用 translate 替代 top
  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发 回流(改变了布局)
  • 把DOM离线后修改,比如:先把DOM给display:none(回流),然后你修改100次,然后再把它显示出来
  • 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame
  • CSS 选择符从右往左匹配查找,避免 DOM 深度过深
  • 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对 于 video 标签,浏览器会自动将该节点变为图层。

谈谈对前端继承的理解

原型链继承,子类实例继承的属性有,子类构造函数的属性,父类构造函数的属性,父类原型上的属性
缺点:无法向父类传参,当父类原型上的属性改变时,所以子类实例相对应的属性都会对应改变


  
  1. function Father() {
  2. this.name = "father";
  3. this.sex = "man"
  4. }
  5. Father.prototype.hobby = 'fish'
  6. function Son() {
  7. this.name = "son";
  8. }
  9. // 原型链继承
  10. Son.prototype = new Father()
  11. var son1 = new Son()
  12. var son2 = new Son()
  13. Father.prototype.hobby = 'dog' //缺点,修改父类prototype上的属性时,所有子类都会随之修改
  14. console.log(son1.hobby) // dog
  15. console.log(son2.hobby) // dog
  16. console.log(son1 instanceof Father) // true

构造函数继承(通过call,apply),子类可继承多个父类,可传参给父类
缺点:每个实例都有父类的构造函数,父类prototype上的属性无法继承


  
  1. // 构造函数继承(通过call,apply)
  2. function Father() {
  3. this.name = "father";
  4. this.sex = "man"
  5. }
  6. Father.prototype.hobby = 'fish'
  7. function Son(sex) {
  8. Father.call( this, sex) //可继承多个父类,但是每个实例都有父类的构造函数
  9. this.name = "son";
  10. }
  11. var son = new Son( 'woman')
  12. console.log(son.sex) //woman,可传参给父类
  13. console.log(son.hobby) //undefined,缺点,父类prototype上的属性无法继承
  14. console.log(son instanceof Father) // false

组合继承,上述两者的结合,解决了上面的缺点和问题(常用)
缺点:Father.call()和new Father()执行了两次父类构造函数,增加了性能损耗,父类的原型上的constructor指向了子类,此时需要在实例化父类(new Father)后在实例化子类(new Son)之前添加一句话:Father.prototype.constructor = Father


  
  1. // 组合继承
  2. function Father(sex) {
  3. this.name = "father";
  4. this.sex = sex
  5. }
  6. Father.prototype.hobby = 'fish'
  7. function Son(sex) {
  8. Father.call( this, sex) //可继承多个父类
  9. this.name = "son";
  10. }
  11. Son.prototype = new Father()
  12. Father.prototype.constructor = Father //解决父类的原型上的constructor指向了子类
  13. var son = new Son( 'woman')
  14. console.log(son.sex) //woman,可传参给父类
  15. console.log(son.hobby) //fish
  16. console.log(son instanceof Father) // true

原型式继承,和Object.create相似,通过函数进行继承,会继承父类所有属性
缺点:父类原型上的属性发生变化时,所有子类对应属性都会改变,子类无法直接修改属性,复用性较差


  
  1. // 原型式继承
  2. function Father() {
  3. this.name = "father";
  4. this.sex = 'man'
  5. }
  6. Father.prototype.hobby = 'fish'
  7. function Son() {
  8. this.name = "son";
  9. }
  10. function inherit(father) {
  11. function Fn() {}
  12. Fn.prototype = father;
  13. return new Fn() //类似于复制了father这个对象
  14. }
  15. var father = new Father()
  16. var son1 = inherit(father)
  17. Father.prototype.hobby = 'dog' //缺点,修改父类prototype上的属性时,所有子类都会随之修改
  18. var son2 = inherit(father)
  19. console.log(son1.sex) //man
  20. console.log(son1.hobby) //dog
  21. console.log(son2.hobby) //dog
  22. console.log(son1 instanceof Father) // true

寄生式继承,继承父类所有属性,并且可以添加子类自己的属性方法
缺点:代码复用率低


  
  1. function Father(sex) {
  2. this.name = "father";
  3. this.sex = sex //实例传参
  4. }
  5. Father.prototype.hobby = 'fish'
  6. function Son() {
  7. this.name = "son";
  8. }
  9. Object.prototype.myCreate = function (obj) { //实现Object.create
  10. function Fn() {}
  11. Fn.prototype = obj;
  12. return new Fn()
  13. }
  14. function inherit(father) {
  15. var _father = Object.myCreate(father) //克隆对象
  16. _father.getInfo = function () { //增强子类,修改属性,产生子类独有的方法和属性,但是耦合高,复用性差,不同子类的写法各不同
  17. console.log(_father.name)
  18. console.log(_father.hobby)
  19. console.log(_father.sex)
  20. }
  21. return _father;
  22. }
  23. var father = new Father( 'woman')
  24. var son = inherit(father)
  25. son.getInfo() //father,fish,woman

寄生式组合继承,继承父类所有属性,解决调用两次父类构造函数问题:一次是在创建子类型原型,一次在子类内部(理论上是最理想的继承)


  
  1. // 寄生式组合继承
  2. function Father(sex) {
  3. this.name = "father";
  4. this.sex = sex //实例传参
  5. }
  6. Father.prototype.hobby = 'fish'
  7. Father.prototype.getName = function () {
  8. console.log( this.name)
  9. }
  10. function Son(sex) {
  11. console.log( this.superClass) //Father
  12. Father.call( this, sex); //构造函数继承传递参数
  13. this.name = "son";
  14. this.hobby = "dog";
  15. }
  16. Son.prototype.getName = function () {
  17. console.log( this.name)
  18. }
  19. function Grandson(sex) {
  20. console.log( this.superClass) //Son
  21. Son.call( this, sex); //构造函数继承传递参数
  22. this.name = "grandson";
  23. this.hobby = "cat";
  24. }
  25. var inherit = ( function () {
  26. function F() {} //使用闭包产生私有函数,使每个子类继承的父类属性无引用关系
  27. return function (father, son) {
  28. F.prototype = father.prototype; //私有函数取出父类的原型
  29. son.prototype = new F();
  30. son.prototype.superClass = father; //子类的超类指向父类,子类通过this.superClass调用Father
  31. son.prototype.constructor = son;
  32. }
  33. }())
  34. inherit(Father, Son)
  35. inherit(Son, Grandson)
  36. var father = new Father( 'fatherMan')
  37. var son = new Son( 'sonMan')
  38. var grandson = new Grandson( 'grandsonMan')
  39. console.log(son instanceof Father) //true
  40. console.log(grandson instanceof Son) //true
  41. console.log(grandson instanceof Father) //true
  42. console.log(father.sex) //fatherMan
  43. console.log(son.sex) //sonMan
  44. console.log(grandson.sex) //grandsonMan
  45. console.log(father.hobby) //fish
  46. console.log(son.hobby) //dog
  47. console.log(grandson.hobby) //cat
  48. father.getName() //father
  49. son.getName() //son
  50. grandson.getName() //grandson

 


转载:https://blog.csdn.net/time_____/article/details/109525973
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场