面试题-js

new关键字

new关键字的作用

通过new关键字实例化构造函数,获取对象

new在执行时会做的4件事

1在内存中创建一个新的空对象

2让this指向这个新对象(将构造函数的作用域赋给新对象,就是给这个新对象构造原型链,链接到构造函数的原型对象上,从而新对象就可以访问构造函数中的属性和方法)

3执行构造函数里面的代码,给这个对象添加属性和方法

4返回这个新对象(所以构造函数里面不需要return)

 

为什么子类的构造函数,一定要调用super()

原因就在于 ES6 的继承机制,与 ES5 完全不同。

ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。

ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。

这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。

ES5的继承和ES6的继承有什么区别

ES5的继承:通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this

function Father(uname,age) {
    this.uname = uname;
    this.age = age;
}
Father.prototype.money = function() {
    console.log(this);
}

function Son(uname, age, sex) {
    Father.call(this, uname, age); // 3 实现继承
    this.sex = sex;;
}

// Father的实例对象中有__proto_ ,指向Father的原型对象prototype,Father的prototype中就有Father的方法
Son.prototype = new Father(); // 或者Object.create(person.prototype);
// 如果利用实例对象的形式修改了原型对象,要把constructor 指回构造函数 
Son.prototype.constructor = Son;
Son.prototype.exam
= function() {
console.log(
'孩子的方法');
}

ES6的继承先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。

ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。

这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。

如果不调用super()方法,子类就得不到自己的this对象。

ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。
 
继承多个类
 
 

 

 

为什么 JS 阻塞页面加载 ?

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系。

 

什么是单线程,和异步的关系?

”JS是单线程的”指的是JS 引擎线程。

在浏览器环境中,有JS 引擎线程和渲染线程,且两个线程互斥。
Node环境中,只有JS 线程。

单线程 :只有一个线程,只能做一件事
原因 : 避免 DOM 渲染的冲突
浏览器需要渲染 DOM
JS 可以修改 DOM 结构
JS 执行的时候,浏览器 DOM 渲染会暂停
两段 JS 也不能同时执行(都修改 DOM 就冲突了)
webworker 支持多线程,但是不能访问 DOM
解决方案 :异步

 

css 加载会造成阻塞吗 ?

由浏览器渲染流程我们可以看出 :

DOM 和 CSSOM 通常是并行构建的,所以 CSS 加载不会阻塞 DOM 的解析

然而,由于 Render Tree 是依赖于 DOM Tree 和 CSSOM Tree 的,

所以他必须等待到 CSSOM Tree 构建完成,也就是 CSS 资源加载完成(或者 CSS 资源加载失败)后,才能开始渲染。

因此,CSS 加载会阻塞 Dom 的渲染

DOMContentLoaded 与 load 的区别 ?

当 DOMContentLoaded 事件触发时,仅当 DOM 解析完成后,不包括样式表,图片。

当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件。

如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等 CSSOM 构建完成才能执行。

在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。

当 onload 事件触发时,页面上所有的 DOM,样式表,脚本,图片等资源已经加载完毕。

DOMContentLoaded -> load。

 

 

js 执行机制:事件循环(宏任务微任务)

 

JavaScript 语言的一大特点就是单线程,同一个时间只能做一件事。

为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,Event Loop 的方案应用而生

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。

所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)

在所有同步任务执行完之前,任何的异步任务是不会执行的。

例子:网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。

         而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务

导图要表达的内容用文字来表述的话:

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

事件循环

事件循环的顺序,决定js代码的执行顺序。

进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。

然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

 

 

DOM 事件有哪些阶段?谈谈对事件代理的理解

分为三大阶段:捕获阶段--目标阶段--冒泡阶段

事件代理:事件不直接绑定到某元素上,而是绑定到该元素的父元素上,进行触发事件操作时(例如'click'),再通过条件判断,执行事件触发后的语句(例如'alert(e.target.innerHTML)')

好处:(1)使代码更简洁;(2)节省内存开销

 

构造函数

一般情况下,公共属性定义到this上,公共方法放到原型对象上

如果方法是放this上的,每生成一个实例就会开辟一个新的内存空间,造成浪费

 

原型,原型链

每一个构造函数都有一个prototype对象,就是原型对象。在prototype对象上定义方法和属性,所有的实例对象都可以共享,节约内存。

每个实例对象上都有一个__proto__属性,它指向父类构造函数的原型,这个原型的__proto__属性也指向父类的原型,以次类推,直到指向Object对象为止,这就形成了原型链

当访问实例对象上的属性和方法时,就是通过原型链一层一层去查找的

(构造函数的)原型对象 prototype

每一个构造函数都有一个prototype属性,是一个对象,叫原型对象-prototype

作用

       共享方法:把方法定义在prototype上,所有实例都可以共享这个方法

       节约内存:不需要再开辟内存空间

对象(的)原型 __proto__

每个对象上都有一个属性__proto__ 指向构造函数的原型对象

(对象可以使用构造函数prototype原型对象上的属性和方法,就是因为对象有__proto__存在)

__proto__对象原型和原型对象prototype是等价的

constructor构造函数

对象原型(__proto__)和构造函数的原型对象(prototype)里面都有一个constructor属性,指回构造函数本身

constructor属性主要用来记录该对象引用于哪个构造函数

 

原型链

在JavaScript中万物都是对象,对象和对象之间的关系是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链

 

 

js的成员查找机制

1 访问一个对象的属性或方法时,先查找对象自身有没有该属性

2 没有找到 就查找他的原型(也就是__proto__指向的prototype原型)

3 还是没有找到,就查找原型对象的原型(object的原型对象)

4 以此类推一直找到Object(null)为止

 

作用域,作用域链

1 作用域 

作用域是在运行时代码中变量和对象的可访问性

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

ES5中作用域有:全局作用域、函数作用域。没有块作用域的概念。
ES6中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。

2 作用域链

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。

但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

 

this指向

1 函数内this指向 由谁调用就指向谁

btn.onclick = function(){ 
  this.disable = true; // 指向btn 
   setTimeout(function(){ 
      this.disable = false; // 指向window 
   },1000) 
} 

btn.onclick = function(){ 
  this.disable = true; 
  setTimeout(function(){ 
    this.disable = false; 
  }.bind(this),1000) // 改变this指向 this指向btn 
}

 

调用方式 this指向
普通函数调用 window  (严格模式下undefined)
构造函数调用 实例对象(原型对象里面的方法也指向实例对象-obj.prototype)
对象方法调用 该方法所属对象
事件绑定方法 绑定事件对象 btn.onclick = function() {}
定时器函数 window
立即执行函数 window

2 使用call()  apply() bind()

相同点:改变this指向

不同点: 

 1) call,apply会调用函数,并且改变this指向

 2)传参方式不同  call(指向, 参数1, 参数2)  apply(指向,[参数1, 参数2])

 3)bind不会调用函数,只改变this指向

应用场景

1)call经常做继承

2)apply经常和数组有关,

3)改变定时器内部的this指向

3 箭头函数-es6的

不绑定this关键字,箭头函数中的this,指向函数定义位置的上下文this,只会从自己的作用域链的上一层继承this。

bind,call,apply只能调用传递参数,不可修改this指向

箭头函数内部没有arguments,不能使用new实例化

function函数也是一个对象,箭头函数不是对象

// 箭头函数
var age = 1;
var obj = {
  age: 2,
  say: () => {
    console.log(this.age); // 对象不能产生作用域  指向window
  }
}

obj.say(); // 1


// 普通函数
var age = 1;
var obj = {
  age: 2,
  say: function() {
    console.log(this.age); // 指向调用者
  }
}

obj.say(); // 2

 

var obj = {
            age: 2,
            init: function () {
                document.addEventListener('click', () => {
                    this.say(); // 箭头函数 向上一级作用域查找  2
                })
                // 相当于
                document.addEventListener('click', function () {
                    this.say(); // 2
                }.bind(this));
                // 
                document.addEventListener('click', function () {
                    this.say(); // this指向document   报错 say is not a function
                })
            },
            say: function () {
                console.log(this.age); // 指向调用者
            }
        }
        obj.init();

 

高价函数

高阶函数是至少满足下列一个条件的:

  • 接受一个或多个函数作为输入
  • 输出一个函数

数组自带的map、filter和reduce就是高阶函数,因为它们接受一个函数作为参数

 

什么是闭包?闭包作用?优缺点

闭包(closure)指有权访问另一个函数作用域中的变量的函数

function fn() { // fn就是闭包函数
  var num = 10;
  return function () {
    console.log(num)
  }
}
var f = fn();
f()

作用:延伸变量的作用范围

优点:

  1. 避免全局变量的污染
  2. 可以读取函数内部的变量

缺点:

        1 导致变量不会被垃圾回收机制回收,造成内存消耗

        2 常驻内存,增加内存使用量

案例1:利用闭包的方式得到当前小li的索引号

var lis = document.querySelector('.nav').querySelectorAll('li')
for(var i = 0; i< lis.length; i++) {
    // 利用for循环创建4个立即执行函数
    (
        function(i) {
            lis[i].onclick = function () { // 点击事件是异步的
                console.log(i);  // 0 1 2 3
            }
        }
    )(i);
}

不用闭包:动态添加属性

var lis = document.querySelector('.nav').querySelectorAll('li')
for(var i = 0; i< lis.length; i++) {
    lis[i].i = i
    lis[i].onclick = function() {
       console.log(this.i)
    }
}

案例2:利用闭包 3秒后打印所有li元素的内容

var lis = document.querySelector('.nav').querySelectorAll('li')
for(var i = 0; i< lis.length; i++) {
    // 利用for循环创建4个立即执行函数
    (
        function(i) {
            setTimeout(() => {
                console.log(lis[i].innerHtml);
            }, 3000);
        }
    )(i);
}

 

javascript 的垃圾回收机制

JS规定在一个函数作用域内,程序执行完以后变量就会被销毁,这样可节省内存

(C 语言,低级内存管理,需显式地对操作系统的内存进行分配和释放)

JavaScript 在创建对象(对象、字符串等)时会为它们分配内存,不再使用对时会“自动”释放内存,这个过程称为垃圾回收。

内存生命周期中的每一个阶段:

分配内存 —  内存是由操作系统分配的,它允许您的程序使用它。

在低级语言(例如 C 语言)中,这是一个开发人员需要自己处理的显式执行的操作。然而,在高级语言中,系统会自动为你分配内在。

使用内存 — 这是程序实际使用之前分配的内存,在代码中使用分配的变量时,就会发生读和写操作。

释放内存 — 释放所有不再使用的内存,使之成为自由内存,并可以被重利用。

与分配内存操作一样,这一操作在低级语言中也是需要显式地执行。

四种常见的内存泄漏:全局变量,未清除的定时器,闭包,以及 dom 的引用

  1. 全局变量 不用 var 声明的变量,相当于挂载到 window 对象上。如:b=1; 解决:使用严格模式
  2. 被遗忘的定时器和回调函数
  3. 闭包
  4. 没有清理的 DOM 元素引用

 

 

前端性能优化

减少 HTTP 请求数
减少 DNS 查询
使用 CDN
避免重定向
图片懒加载
减少 DOM 元素数量
减少 DOM 操作
使用外部 JavaScript 和 CSS
压缩 JavaScript、CSS、字体、图片等
优化 CSS Sprite
使用 iconfont
多域名分发划分内容到不同域名
尽量减少 iframe 使用
避免图片 src 为空
把样式表放在 link 中
把 JavaScript 放在页面底部

 

减少请求数量

减小资源大小

优化网络连接

优化资源加载

减少重绘回流

使用性能更好的API

构建优化

 

负载均衡

单台服务器共同协作,不让其中某一台或几台超额工作,发挥服务器的最大作用
http 重定向负载均衡:调度者根据策略选择服务器以 302 响应请求,缺点只有第一次有效果,后续操作维持在该服务器 dns 负载均衡:解析域名时,访问多个 ip 服务器中的一个(可监控性较弱)原因 - 避免 DOM 渲染的冲突
反向代理负载均衡:访问统一的服务器,由服务器进行调度访问实际的某个服务器,对统一的服务器要求大,性能受到 服务器群的数量

 

函数柯里化

 函数柯里化其实就是在函数调用时只传递一部分参数进行调用,函数会返回一个新函数去处理剩下的参数

//函数柯里化
function fn(x) {
    return function (y) {
        console.log(x + y);
    };
};
var fn_ = fn(1);
fn_(1); //2

fn(1)(1) //2

作用:

参数复用

利用闭包的原理,让我们前面传输过来的参数不要被释放掉

提前确认

这一特性经常是用来对浏览器的兼容性做出一些判断并初始化api,比如说我们目前用来监听事件大部分情况是使用addEventListener来实现的,但是一些较久的浏览器并不支持该方法,所以在使用之前,我们可以先做一次判断,之后便可以省略这个步骤了

延迟运行

js中的bind这个方法,用到的就是柯里化的这个特征

案例

    function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

 

异步编程的实现方式?

回调函数
优点:简单、容易理解
缺点:不利于维护、代码耦合高


事件监听
优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
缺点:事件驱动型,流程不够清晰


发布/订阅(观察者模式)
类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者


Promise 对象
优点:可以利用 then 方法,进行链式写法;可以书写错误时的回调函数
缺点:编写和理解,相对比较难


Generator 函数
优点:函数体内外的数据交换、错误处理机制
缺点:流程管理不方便


async 函数
优点:内置执行器、更好的语义、更广的适用性、返回的是 Promise、结构清晰
缺点:错误处理机制

 

web安全问题

posted @ 2021-10-09 00:00  litiyi  阅读(41)  评论(0编辑  收藏  举报