0


【直接收藏】前端JavaScript面试100问(终)

87、事件代理

事件代理 也就是 事件委托

不是直接给标签添加事件 是给标签的父级添加事件 通过 事件对象 判断触发事件的标签对象是谁 执行不同的函数程序的语法形式

委托的优点

减少内存消耗
试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件

如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;

因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

所以事件委托可以减少大量的内存消耗,节约效率。

动态绑定事件
比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件;

在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。

88、不卡顿

如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条 都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来 每 16 ms 刷新一次。

<ul>控件</ul> <script>    setTimeout(() => {       // 插入十万条数据      const total = 100000      // 一次插入 20 条,如果觉得性能不好就减少      const once = 20      // 渲染数据总共需要几次     const loopCount = total / once      let countOfRender = 0      let ul = document.querySelector("ul");      function add() {      // 优化性能,插入不会造成回流       const fragment = document.createDocumentFragment();      for (let i = 0; i < once; i++) {        const li = document.createElement("li");        li.innerText = Math.floor(Math.random() * total);        fragment.appendChild(li);      }     ul.appendChild(fragment);     countOfRender += 1;     loop();  }  function loop() {      if (countOfRender < loopCount) {       window.requestAnimationFrame(add);   }  }  loop();  }, 0);

89、JavaScript中的instanceof

JavaScript中变量的类型判断常常使用typeof运算符,但使用typeof时存在一个缺陷,就是判断引用类型存储值时,无论引用的是什么类型的对象,它都返回 object。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。

1.instanceof运算符用法

var strObj = new String("字符串");
console.log(strObj instanceof String);// true

该段代码判断的是变量strObj是否为String对象的实例,strObj 是 String 对象的实例,因此是”true”。尽管不像 typeof 方法那样灵活,但是在 typeof 方法返回 “object” 的情况下,instanceof 方法就很有用。

// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();

console.log(foo instanceof Foo)

2.instanceof在继承关系中使用

// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo(); //JavaScript 原型继承

var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true
foo作为构造函数Foo的实例,因为构造函数Foo原型继承了构造函数Aoo,因此返回true。该代码中是判断了一层继承关系中的父类,在多层继承关系中,instanceof 运算符同样适用。

3.instanceof运算符代码
function instance_of(L, R) { //L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
  if (L === null)
    return false;
  if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true
    return true;
  L = L.__proto__;
}
}

90、forEach中的await

不知道你是否写过类似的代码:

 function test() {     let arr = [3, 2, 1]     arr.forEach(async item => {      const res = await fetch(item)      console.log(res)     })     console.log('end')    } function fetch(x) { return new Promise((resolve, reject) => {  setTimeout(() => {   resolve(x)  }, 500 * x) })}test()

我当时期望的打印顺序是

3
2
1
end
结果现实与我开了个玩笑,打印顺序居然是

end
1
2
3
为什么?

其实原因很简单,那就是 forEach 只支持同步代码。

我们可以参考下 Polyfill 版本的 forEach,简化以后类似就是这样的伪代码

while (index < arr.length) {  callback(item, index)   //也就是我们传入的回调函数}

从上述代码中我们可以发现,forEach 只是简单的执行了下回调函数而已,并不会去处理异步的情况。并且你在 callback 中即使使用 break 也并不能结束遍历。

怎么解决?

一般来说解决的办法有2种,for...of和for循环。

使用 Promise.all 的方式行不行,答案是:不行

​​​​​​​

从上述代码中我们可以发现,forEach 只是简单的执行了下回调函数而已,并不会去处理异步的情况。并且你在 callback 中即使使用 break 也并不能结束遍历。怎么解决?一般来说解决的办法有2种,for...of和for循环。使用 Promise.all 的方式行不行,答案是:不行

可以看到并没有按照我们期望的输出。

这样可以生效的原因是 async 函数肯定会返回一个 Promise 对象,调用 map 以后返回值就是一个存放了 Promise 的数组了,这样我们把数组传入 Promise.all 中就可以解决问题了。但是这种方式其实并不能达成我们要的效果,如果你希望内部的 fetch 是顺序完成的,可以选择第二种方式。

第1种方法是使用 for...of

​​​​​​​

   async function test() {     let arr = [3, 2, 1]     for (const item of arr) {      const res = await fetch(item)      console.log(res)     }     console.log('end')    }

这种方式相比 Promise.all 要简洁的多,并且也可以实现开头我想要的输出顺序。

但是这时候你是否又多了一个疑问?为啥 for...of 内部就能让 await 生效呢。

因为 for...of 内部处理的机制和 forEach 不同,forEach 是直接调用回调函数,for...of 是通过迭代器的方式去遍历。

​​​​​​​

async function test() { let arr = [3, 2, 1] const iterator = arr[Symbol.iterator]() let res = iterator.next() while (!res.done) {  const value = res.value  const res1 = await fetch(value)  console.log(res1)  res = iterator.next() } console.log('end')}

第2种方法是使用 for循环

​​​​​​​

async function test() {  let arr = [3, 2, 1]  for (var i=0;i<arr.length;i++) {    const res = await fetch(arr[i])    console.log(res)  }  console.log('end')}function fetch(x) { return new Promise((resolve, reject) => {  setTimeout(() => {   resolve(x)  }, 500 * x) })}test()

第3种方法是使用 while循环

​​​​​​​​​​​​​​

async function test() {  let arr = [3, 2, 1]  var i=0;  while(i!==arr.length){    const res = await fetch(arr[i])    console.log(res)    i++;  }  console.log('end')}function fetch(x) { return new Promise((resolve, reject) => {  setTimeout(() => {   resolve(x)  }, 500 * x) })}test()

要想在循环中使用async await,请使用for...of 或者 for 循环, while循环

forEach支持async awaitforEach 在正常情况像下面这么写肯定是做不到同步的,程序不会等一个循环中的异步完成再进行下一个循环。原因很明显,在上面的模拟中,while 循环只是简单执行了 callback,所以尽管 callback 内使用了 await ,也只是影响到 callback 内部。

​​​​​​​

arr.myforeach(async v => {    await fetch(v);});

要支持上面这种写法,只要稍微改一下就好

​​​​​​​

Array.prototype.myforeach = async function (fn, context = null) {    let index = 0;    let arr = this;    if (typeof fn !== 'function') {        throw new TypeError(fn + ' is not a function');    }    while (index < arr.length) {        if (index in arr) {            try {                await fn.call(context, arr[index], index, arr);            } catch (e) {                console.log(e);            }        }        index ++;    }};

91、src和href

src和href都是用在外部资源的引入上,比如图像,CSS文件,HTML文件,以及其他的web页面等等,那么src和href的区别都有哪些呢?

1、请求资源类型不同
(1) href是Hypertext Reference的缩写,表示超文本引用。用来建立当前元素和文档之间的链接。常用的有:link、a。
(2)在请求 src 资源时会将其指向的资源下载并应用到文档中,常用的有script,img 、iframe;

2、作用结果不同
(1)href 用于在当前文档和引用资源之间确立联系;

(2)src 用于替换当前内容;

3、 浏览器解析方式不同
(1)若在文档中添加href ,浏览器会识别该文档为 CSS 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式。

(2)当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

92、JavaScript中事件绑定的方法

在JavaScript的学习中,我们经常会遇到JavaScript的事件机制,例如,事件绑定、事件监听、事件委托(事件代理)等。这些名词是什么意思呢,有什么作用呢?

一、事件绑定要想让 JavaScript 对用户的操作作出响应,首先要对 DOM 元素绑定事件处理函数。所谓事件处理函数,就是处理用户操作的函数,不同的操作对应不同的名称。

在JavaScript中,有三种常用的绑定事件的方法:

在DOM元素中直接绑定;在JavaScript代码中绑定;绑定事件监听函数。

1、在DOM中直接绑定事件

​​​​​​​

我们可以在DOM元素上绑定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等。好多不一一列出了。如果想知道更多事件类型请查看, DOM事件 。<input type="button" value="click me" onclick="hello()"><script>function hello(){ alert("hello world!");}

2、在JavaScript代码中绑定事件

​​​​​​​

在 JS 代码中(即 script 标签内)绑定事件可以使 JS 代码与HTML标签分离,文档结构清晰,便于管理和开发。<input type="button" value="click me" id="btn"><script>document.getElementById("btn").onclick = function(){ alert("hello world!");}

3、使用事件监听绑定事件

绑定事件的另一种方法是用 addEventListener() 或 attachEvent() 来绑定事件监听函数。下面详细介绍,事件监听。

1)事件监听

​​​​​​​

关于事件监听,W3C规范中定义了3个事件阶段,依次是捕获阶段、目标阶段、冒泡阶段。起初Netscape制定了JavaScript的一套事件驱动机制(即事件捕获)。随即IE也推出了自己的一套事件驱动机制(即事件冒泡)。最后W3C规范了两种事件机制,分为捕获阶段、目标阶段、冒泡阶段。IE8以前IE一直坚持自己的事件机制(前端人员一直头痛的兼容性问题),IE9以后IE也支持了W3C规范。W3C规范element.addEventListener(event, function, useCapture)event : (必需)事件名,支持所有 DOM事件 。function:(必需)指定要事件触发时执行的函数。useCapture:(可选)指定事件是否在捕获或冒泡阶段执行。true,捕获。false,冒泡。默认false。注:IE8以下不支持。<input type="button" value="click me" id="btn1"><script>document.getElementById("btn1").addEventListener("click",hello);function hello(){ alert("hello world!");}IE标准element.attachEvent(event, function)event:(必需)事件类型。需加“on“,例如:onclick。function:(必需)指定要事件触发时执行的函数。<input type="button" value="click me" id="btn2"><script>document.getElementById("btn2").attachEvent("onclick",hello);function hello(){ alert("hello world!");}

​​​​​​​

<script>document.getElementById("btn2").attachEvent("onclick",hello);function hello(){ alert("hello world!");}

2)事件监听的优点

1、可以绑定多个事件;常规的事件绑定只执行最后绑定的事件。

​​​​​​​

<input type="button" value="click me" id="btn3">  <input type="button" value="click me" id="btn4">var btn3 = document.getElementById("btn3");btn3.onclick = function(){                  //动态绑定事件 alert("hello 1");           //不执行}btn3.onclick = function(){ alert("hello 2");           //执行}var btn4 = document.getElementById("btn4");btn4.addEventListener("click",hello1);      //添加事件监听器btn4.addEventListener("click",hello2);function hello1(){ alert("hello 1");        //执行}function hello2(){ alert("hello 2");        //执行 (顺序执行)}

2、可以解除相应的绑定

​​​​​​​

<input type="button" value="click me" id="btn5"><script>var btn5 = document.getElementById("btn5");btn5.addEventListener("click",hello1);//执行了btn5.addEventListener("click",hello2);//不执行btn5.removeEventListener("click",hello2);function hello1(){ alert("hello 1");}function hello2(){ alert("hello 2");}

3)封装事件监听

​​​​​​​

<input type="button" value="click me" id="btn5">//绑定监听事件function addEventHandler(target,type,fn){ if(target.addEventListener){ target.addEventListener(type,fn); }else{ target.attachEvent("on"+type,fn); }}//移除监听事件function removeEventHandler(target,type,fn){ if(target.removeEventListener){ target.removeEventListener(type,fn); }else{ target.detachEvent("on"+type,fn); }}

93、git常见分支

git 分支命名规范
为规范开发,保持代码提交记录以及 git 分支结构清晰,方便后续维护,现规范 git 的相关操作。

主要规范两点:

git 分支命名规范

git 提交记录规范

1. git 分支命名规范
  git 分支分为集成分支、功能分支和修复分支,分别命名为 develop、feature 和 hotfix,均为单数。不可使用 features、future、hotfixes、hotfixs 等错误名称。

master(主分支,永远是可用的稳定版本,不能直接在该分支上开发)
develop(开发主分支,所有新功能以这个分支来创建自己的开发分支,该分支只做只合并操作,不能直接在该分支上开发)
feature-xxx(功能开发分支,在develop上创建分支,以自己开发功能模块命名,功能测试正常后合并到develop分支)
feature-xxx-fix(功能bug修复分支,feature分支合并之后发现bug,在develop上创建分支修复,之后合并回develop分支。PS:feature分支在申请合并之后,未合并之前还是可以提交代码的,所以feature在合并之前还可以在原分支上继续修复bug)
hotfix-xxx(紧急bug修改分支,在master分支上创建,修复完成后合并到 master)
注意事项:

一个分支尽量开发一个功能模块,不要多个功能模块在一个分支上开发。
feature 分支在申请合并之前,最好是先 pull 一下 develop 主分支下来,看一下有没有冲突,如果有就先解决冲突后再申请合并。

94、前端引擎模板

JavaScript随着各种神奇的实用功能库日渐丰富,而越来越受到Web开发者与设计师的追捧,例如jQuery,MooTools,Prototype等。

  1. Jade

Jade是一个有着完善API和惊艳特性的JavaScript模板引擎。使用空白与缩进敏感的代码格式编写HTML页面。基于Node.js,运行在服务器端。

  1. Mustache

Mustache是一个logic-less(无逻辑或轻逻辑)语法模板。可以用于组织HTML、配置文件、源代码在内的任何东西。Mustache使用JavaScript对象的值,用来扩展模板代码中的大括号标签。

  1. Transparency

Transparency是一个强大的客户端模板引擎,用来将数据绑定到Web页面的BOM结构中。其模板无需特殊格式,直接完全符合HTML。直接使用JavaScript逻辑,无需新学特殊的“模板语言”。兼容IE9+、Chrome、Fx、iOS、安卓等浏览器。

  1. Underscore.js

Underscore.js是一个JavaScript库,提供一系列实用的工具函数(helper)。Underscore.js仅作为额外的工具函数独立工作,不扩充(污染)任何JavaScript内建对象的本身。

  1. Embeddedjs

EJS以类似PHP的JS/HTML通过标签混排的形式,帮助开发者将JavaScript和HTML部分有效分离。

  1. DoTjs

最快和简洁的JavaScript模板引擎,同时用于Node.js和浏览器。

  1. Handlebarsjs

一套语义化模板引擎。兼容Mustache。

  1. T.js

一个用简单的JavaScript数据结构去渲染表现html/xml内容的模板引擎。

  1. Dustjs

一套同时可用于浏览器或Node.js的异步模板引擎。

  1. Nunjucks

Nunjucks是一套富功能的模板引擎。模板语言功能强大,支持块继承、自动转义、宏、异步控制等功能。

怎么样的模板引擎是适合前端的

前端模板引擎需要有开发时的透明性我认为前端任何框架和工具都要有对开发的透明性,模板引擎也不例外。所谓透明性即指我在搭建好开发环境后,随手写代码随手刷新浏览器就能看到最新的效果,而不需要额外地执行任何命令或有任何的等待过程所以一切依赖编译过程的模板引擎并不适合前端使用,编译只能是模板引擎的一个特性,而不能是使用的前提更严格地说,使用FileWatch等手段进行文件变更检测并自动编译也不在我的考虑范围之内,因为这会造成额外的等待,像我这种手速极快的人可能编译速度跟不上由此可以推出,前端的模板引擎应该是具备可在纯前端环境中解析使用的能力的

前端模板引擎要有良好的运行时调试能力

前端并不像后端,任何错误都可以有严格的日志记录和调用堆栈以供分析。由于用户行为的不确定性、执行环境的不确定性、各种第三方脚本的影响等,前端很难做到完全的错误处理和跟踪,这也导致前端必然存在需要直接在线上排查问题的情况而当问题出现在模板引擎这一层时,就需要模板引擎提供良好的调试能力一般来说,编译后生成的函数的调试能力是弱于原先手动编写的模板片断的,因为自动生成的函数基本不具备可读性和可断点跟踪性因此在这一点上,一个供前端使用的模板引擎应该具备在特定情况下从“执行编译后函数获取HTML”换回“解析原模板再执行函数获取HTML”的模式,即应该支持在两种模式间切换或者更好地,一个强大的前端模板引擎编译生成的函数,可以使用Source Map或其它自定义的手段直接映射回原模板片段,不过现在并没有什么模板引擎实现了这一功能

前端模板引擎要对文件合并友好

在HTTP/2普及之前,文件合并依旧是前端性能优化中的一个重要手段,模板作为文件的一部分,依旧是需要合并的

在提供编译功能的模板引擎中,我们可以使用编译的手段将模板变为JavaScript源码,再在JavaScript的基础上做文件合并

但是如果我们出于上文所说的调试能力等原因希望保留原模板片段,那就需要模板引擎本身支持模板片段合并为一个文件了

大部分仅支持将一段输入的字符串作为模板解析的引擎并不具备这一能力,他们天生并不能将一整个字符串切分为多个模板片段,因而无法支持模板片段层面上的文件合并

需要实现对文件合并的支持,最好的办法就是让模板的语法是基于“片段”的

前端模板引擎要担负XSS的防范

从安全性上来说,前端对XSS的控制是有严格要求的

我在 单页面(SPA)开发会不会比多页面有更多的安全问题?- 张立理的回答 中有提到过,前端对XSS的防范比较合适的方法是使用“默认转义”的白名单式策略

基于此,一个合理的模板引擎是必须支持默认转义的,即所有数据的输出都默认经过escape的逻辑处理,将关键符号转为对应的HTML实体符号,以从根源上杜绝XSS的入侵路径

当然并不是所有的内容都必须经过转义的,在系统中免不了有对用户输入富文本的需求,因此需要支持特定的语法来产生无转义的输出,但时刻注意无转义输出才是特例,默认情况下必须是转义输出的

前端模板引擎要支持片段的复用

这并不是前端模板引擎的需求,事实上任何模板引擎都应该支持片段的复用,后端如Velocity、Smarty等无不拥有此功能

所谓片段复用,应该有以下几个层次的应用:

一个片段可以被引入到另一处,相当于一个变量到处用的效果

一个片段被引入时,可以向其传递不同的数据,相当于一个函数到处用的效果

一个片段可以被外部替换,但外部不提供此片段的话保持一个默认的内容,类似设计模式中的策略模式

满足第1和第2点的模板引擎并不少,而满足第3点的前端模板引擎却不多见,而后端的Razor、Smarty等都具备这一功能

话说我当时设计我们自己的模板引擎的第3个版本时,就想出了block这一个概念来实现第3点,在做完交付将近半年之后,有人告诉我说Smarty上就有这概念,顿时有种不知应该高兴还是悲伤的不知所措感。还好他并没有怀疑我直接抄了别人的功能,不然真是冤枉

前端模板引擎要支持数据输出时的处理

所谓数据输出时处理,指一个数据要在输出时做额外的转换,最常见的如字符串的trim操作,比较技术性的如markdown的转换等

诚然数据的转换完全可以在将数据交给模板引擎前就通过JavaScript的逻辑处理完,但这会导致不少有些丑陋又有些冗余的代码,对逻辑本身的复用性也会造成负面的影响

通常模板引擎对数据做额外处理会使用filter的形式实现,类似bash中的管道的逻辑。filter的实现和注册也会有不同的设计,如mustache其实注册的是fitler工厂,而另一些模板引擎则会直接注册filter本身,不同设计有不同的考量点,我们很难说谁好谁坏

但是,模板引擎支持数据的输出处理后,会另我们在编码过程中产生一个新的纠结,即哪些数据处理应该交由模板引擎的filter实现,哪些应该在交给模板引擎前由自己的逻辑逻辑实现。这个话题展开来又是一篇长长的论述,于当前的话题无关就略过吧

前端模板引擎要支持动态数据

在开发过程中,其实有不少数据并不是静态的,如EmberJS就提供了Computed Property这样的概念,Angular也有类似的东西,Backbone则可以通过重写Model的get方法来变相实现

虽然ES5在语言层面上直接提供了getter的支持,但我们在前端开发的大部分场景下依旧不会使用这一语言特性,而会选择将动态的数据封装为某种对象的get等方法而模板引擎在将数据转为HTML片段的过程中,同样应该关注这一点,对这些动态计算的数据有良好的支持

说得更明白一些,模板引擎不应该仅仅接受纯对象(Plain Object)作为输入,而应该更开放地接受类似带有get方法的动态的数据

一个比较合理的逻辑是,如果一个对象有一个get方法(模板引擎决定这个接口),则数据通过该方法获取,其它情况下视输入的对象为纯对象(Plain Object),使用标准的属性获取逻辑

前端模板引擎要与异步流程严密结合

前端有一个很大的特点,就是到处充斥着异步的流程。由于JavaScript在浏览器提供的引擎中单线程执行的特性、大部分与IO相关的API都暴露为异步的事实,以及多数模块定义规范中模板的动态获取是异步的这一现象,注定我们无法将这个世界当作完全同步来看

一个很常见的例子是,我们有一个AMD模块存放了全局使用的常量,模板引擎需要使用这些常量。当然我们可以在使用模板引擎之前让JavaScript去异步获取这一模块,随后将常量作为数据传递给模板引擎,但这是一种业务与视图相对耦合的玩法,出于强迫症我并不觉得这是一个漂亮的设计,所以我们希望直接在模板中这么写:

{{$globals.ICP_SERIAL}}

这是我假想的一个语法,通过$globals可以使用AMD Loader获取globals这一模块,随后获取其中的ICP_SERIAL属性输出

模板引擎支持异步是一个比较具有挑战性的话题,我的计划是在我们自己的模板引擎的下一个版本中尝试实现。这其中涉及很多的技术点,比如:

模板的输出本身成了异步的方法,而不再像现在一样直接返回字符串

分析模板对异步操作的依赖,整个字符串的拼接逻辑被打断成多个异步

异步是需要等待的,且等待是未知的,从性能上考虑,是否需要考虑Stream式的输出,以便完成一段提供一段

是提供内置的固定几种异步逻辑,还是基于Promise支持任何自定义的异步逻辑,在复杂度和实用性上作出平衡

至今我还没有完全明确模板与异步结合的方式和接口,这个话题也没办法继续深入探讨了

前端模板引擎要支持不同的开发模式

前端发展至今,有很多不同的开发模式,比如:

最普通的HTML页面,使用DOMContentLoaded等事件添加逻辑,特定交互下局部刷新页面

采用传统的MVC模型进行单页式开发

使用MVVM方式以数据为核心,数据与视图方向绑定进行开发

基于Immutable Data进行数据比对Diff转DOM更新的开发(其中可能有Virtual DOM的引入)

一个模板引擎要能支持这么多种不同的的模式是一个非常大的挑战,特别是对双向绑定的支持尤为突出。至今为止几乎所有的支持双向绑定的开发框架都自带了专用的模板引擎,这是因为双向绑定对模板有两大要求:

能够从模板中提取“这一模板对哪些数据有依赖”的元信息

能够知道一个数据变化引擎的是模板的哪一块,而不至于整个刷新

而通用模板引擎很少提供这两个特性,所以没办法对不同的前端开发模式进行全面到位的支持

从模板引擎本身的实现上来说,一种方法是直接将模板解析后的类似AST的结构暴露出去,供其他框架合理地处理,同时提供对模板局部的刷新功能(也可与前面所说的模板片段一起考虑),但是大部分模板引擎为了性能等考虑,是不会解析出类似AST的语法结构来的

前端模板引擎要有实例间的隔离

在大型的前端项目,特别是单页式的项目中,会有完全未知个数的模板片段同时存在,如果这些片段是带有名称(出于复用的考虑)的,就很容易造成名称上的冲突对于同一层级的逻辑(如大家都是业务层代码,或者大家都是控件层代码),名称冲突是可以通过一些开发时的约定来解决的。但不同层之间,由于封装性的要求,外部不应该知道一些仅内部使用的片段的名称,此时如果不幸有名称与其它层有冲突,会让情况变得比较麻烦,这类问题甚至都不容易跟踪,往往会导致大量的精力和时间的浪费

因此,一个好的模板引擎应该是多实例的,且不同实例间应该相互具备隔离性,不会出现这种不可预期的冲突

将这个话题再往深地研究,就会发现单纯的隔离是不够的,不同层间除了不冲突的需求,同样还有片段复用的需求,我们还会需要不同模板实例间可以开放一些固定的片段共享,因此模板引擎各个实例的关系是一种组合依赖但又具备基本的封装和隔离的状态

95、datalist 用法

​​​​​​​

<input list="browsers"><datalist id="browsers">  <option value="Internet Explorer">  <option value="Firefox">  <option value="Chrome">  <option value="Opera">  <option value="Safari"></datalist>

这里注意绑定datalist的id给input的list属性,这样在input输入框下面就会出现列表

96、ajax同步和异步的区别

在使用ajax请求数据的时候,通常情况下我们都是把async:true当做默认来处理,让我们的请求成为一个异步的请求。但是在某种情况下我们是需要吧async:false设置为false的,方便我们进行观察数据的走向、去处。那同步和异步有什么区别呢?

同步请求 async:false

​​​​​​​

$.ajax({         async:false,        type:"POST",        url:"Venue.aspx?act=init",        dataType:"html",        success:function(result){  //function1()            f1();            f2();         }         failure:function (result) {             alert('我在弹');         }        }function2();

分析这个时候ajax块发出请求后,他会等待在function1()这个地方,不会去执行function2(),直到function1()部分执行完毕。异步请求 async:true

​​​​​​​

$.ajax({         async: true, //默认为 true        type:"POST",        url:"./xxx/xxx/a/b.html",        dataType:"html",        success:function(result){  //function1()             f1();             f2();         }        failure:function (result) {               alert('我弹');         },        }function2();

分析当ajax块发出请求后,他将停留function1(),等待返回结果,但同时(在这个等待过程中),function2()就可以跑起来。总结(两者的区别)同步的请求的时候,代码好比在排队,必须是一个挨着一个的去执行,前面的没有结束,后面的代码就处于一个阻塞的状态。异步执行的时候,数据请求的同时,其他代码语句也可以同步执行,比如,在数据请求的时候,由于某些愿意,需要慢慢的返回请求结果,在这个时候带宽是很空闲的,那么,代码不会等到前面的数据完全请求返回就可以开始后面的代码运行。

97、JavaScript伪数组

数组

定义: 数组是一种类列表对象,它的原型中提供了遍历和修改元素的相关操作。JavaScript 数组的长度和元素类型都是非固定的。只能用整数作为数组元素的索引,而不能用字符串。对象是没有索引的,是数组的基本特征。

​​​​​​​

var obj = {};var arr = [];obj[2] = 'a';arr[2] = 'a';console.log(obj[2]); // => aconsole.log(arr[2]); // => aconsole.log(obj.length); // => undefinedconsole.log(arr.length); // => 3

obj[2]输出’a’,是因为对象就是普通的键值对存取数据而arr[2]输出’a’ 则不同,数组是通过索引来存取数据,arr[2]之所以输出’a’,是因为数组arr索引2的位置已经存储了数据obj.length并不具有数组的特性,并且obj没有保存属性length,那么自然就会输出undefined而对于数组来说,length是数组的一个内置属性,数组会根据索引长度来更改length的值为什么arr.length输出3,而不是1在给数组添加元素时,并没有按照连续的索引添加,所以导致数组的索引不连续,那么就导致索引长度大于元素个数

伪数组

定义:

伪数组是一个对象(Object),而真实的数组是一个数组(Array)拥有length属性,且必须是number类型,其它属性(索引)为字符串不具有数组所具有的方法,forEach()等,不过有Object的方法伪数组长度不可变,真数组长度可以变可以通过for in遍历

​​​​​​​

var fakeArray = {    length: 3,    "0": "first",    "1": "second",    "2": "third"}var arr = [1, 2, 3, 4]// 真数组的方法来自Array.prototypeconsole.log(fakeArray instanceof Array) //falseconsole.log(arr instanceof Array) // trueArray.isArray(fakeArray) // false;Array.isArray(arr) // true;console.log(arr.__proto__ === Array.prototype) // trueconsole.log(fakeArray.__proto__ === Array.prototype) // falseconsole.log(fakeArray.__proto__ === Object.prototype) // truearr.forEach(x => console.log(x)) // 1 2 3 4fakeArray.forEach(x => console.log(x)) // fakeArray.forEach is not a functionObject.keys(fakeArray) //  ["0", "1", "2", "length"]

常见的伪数组有:

函数内部的 argumentsDOM 对象列表(比如通过 document.getElementsByTags 得到的列表)jQuery 对象(比如 $(“div”) )伪数组是一个 Object,而真实的数组是一个 Array。伪数组存在的意义,是可以让普通的对象也能正常使用数组的很多方法,比如:​​​​​​​

使用Array.prototype.slice.call();var arr = Array.prototype.slice.call(arguments);Array.prototype.forEach.call(arguments, function(v) {  // 循环arguments对象});// push// some// every// filter// map// ...使用[].slice.call()var fakeArray = {    length: 3,    "0": "first",    "1": "second",    "2": "third"}var arr = [].slice.call(fakeArray) console.log(arr) // ["first", "second", "third"]使用ES6中的Array.from方法var fakeArray = {  length: 3,    "0": "first",    "1": "second",    "2": "third"  }var arr = Array.from(fakeArray)console.log(arr) // ["first", "second", "third"]使用扩展运算符,也是ES6的语法var fakeArray = document.querySelectorAll('div')var newArr= [...fakeArray]console.log(newArr.__proto__ === Array.prototype) // true伪数组转换为真数组原理Array.prototype.slice = function (start, end) {  start = start || 0  end = start || this.length  const arr = []  for (var i = start; i < end; i++) {    arr.push(this[i])  }  return arr}

结论对象没有数组 Array.prototype 的属性值,类型是 Object ,而数组类型是 Array数组是基于索引的实现, length 会自动更新,而对象是键值对使用对象可以创建伪数组,伪数组可以正常使用数组的大部分方法

98、同源策略

何为同源?
域名、协议、端口完全一致即为同源。

www.juejin.com 和juejin.com
不同源,因为域名不同

www.bilibili.tv和http://www.bilibili.com
不同源,因为域名不同

http://localhost:3000 和 http://localhost:3001
不同源,因为端口不同

qq.com 和https://qq.com
不同源,因为协议不同

www.pixiv.net 和 www.pixiv.net/manage/illu…
同源,因为域名,协议,端口都相同

**何为策略?**
策略主要限制js的能力
1.无法读取非同源的 cookie、Storage、indexDB的内容
2.无法读取非同源的DOM
3.无法发送非同源的AJAX,更加准确的说应该是发送了请求但被浏览器拦截了。
为什么会有同源策略?

为了保护用户数据安全

1.为了防止恶意网页可以获取其他网站的本地数据。
2.为了防止恶意网站iframe其他网站的时候,获取数据。

3.为了防止恶意网站在自已网站有访问其他网站的权利,以免通过cookie免登,拿到数据。

跨域问题

前后端分离,和使用服务商数据时,导致前端页面地址和后端API不是同源的,例如前端地址为baidu.com,后端API为api.baidu.com。直接访问API会触发同源策略,所以需要想办法跨过去。

常见的跨域方法的原理
1.CORS
•CORS(跨域资源共享)使用专用的HTTP头,服务器(api.baidu.com)告诉浏览器,特定URL(baidu.com)的ajax请求可以直接使用,不会激活同源策略。
2.JSONP
•这个方案相当于黑魔法,因为js调用(实际上是所有拥有src属性的 <\script>、<\img>、<\iframe>)是不会经过同源策略,例如baidu.com引用了CDN的jquery。所以我通过调用js脚本的方式,从服务器上获取JSON数据绕过同源策略。
3.nginx反向代理
•当你访问baidu.com/api/login的时候,通过在baidu.com的nginx服务器会识别你是api下的资源,会自动代理到api.baidu.com/login,浏览器本身是不知道我实际上是访问的api.baidu.com的数据,和前端资源同源,所以也就不会触发浏览器的同源策略。

99、获取第二大的数字

方法一:将数组从大到小排序然后找第二个当然在JS中有sort()方法可以进行数组排序

​​​​​​​

var arr=[5,2,10,8,0,4,7,11,9,1];function array1(){    var max,min;    if(arr[0]<arr[1]){          max=arr[1];          min=arr[0];    }         else    {         max=arr[0];         min=arr[1];    }         for(i=2;i<arr.length;i++)    {        if(arr[i]>min)        {            if(arr[i]>max)            {                   min=max;                max=arr[i];            }            else                  min=arr[i];        }    }    alert(min);}array1();

方法二:

定义两个变量max min循环遍历分别存储当前最大和第二大的数然后输出第二大的数min;

​​​​​​​

var arr=[5,2,10,8,0,4,7,11,9,1];function array2(){    var temp,min;    for(var i=0;i<arr.length-1;i++){        min=i;        for(var j=i+1;j<arr.length;j++){            if(arr[j]>arr[i]){                  temp= arr[i];                arr[i] = arr[j];                arr[j] = temp;            }        }    }    alert(arr[1]);}array2();

100、forin和Object.keys的区别

使用for in 去遍历 对象会将prototype上面扩展的方法或者属性也打印出来

​​​​​​​

// 递归写法Object.prototype.clone = function(){    let o = this.constructor === Array ? [] : {};    for(let e in this){        o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];    }    return o; }let obj = {    a : 1,    b : {        c: 2    }}let obj2 = obj.clone();console.log(obj2);// { a: 1, b: { c: 2, clone: [Function] }, clone: [Function] }

解决方法可以为每一次的遍历加上hasOwnPropertyhasOwnProperty具体的作用就是判断该属性是否属于对象自身的属性

// 递归写法

​​​​​​​

Object.prototype.clone = function(){    let o = this.constructor === Array ? [] : {};    for(let e in this){        if(this.hasOwnProperty(e)){            o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];        }    }    return o; }let obj = {    a : 1,    b : {        c: 2    }}let obj2 = obj.clone();console.log(obj2); // { a: 1, b: { c: 2 } }也可以使用Object.keys()方式完成遍历操作// 递归写法Object.prototype.clone = function(){    let o = this.constructor === Array ? [] : {};    Object.keys(this).forEach(item => {        o[item] = typeof this[item] === "object" ? this[item].clone() : this[item]    })    return o; }let obj = {    a : 1,    b : {        c: 2    }}let obj2 = obj.clone();console.log(obj2);// { a: 1, b: { c: 2 } }

**更多前端精彩视频欢迎B站搜索“千锋教育” **

千锋前端浠浠呀老师HTML+CSS教程,零基础web前端开发入门必看视频

标签: okhttp

本文转载自: https://blog.csdn.net/GUDUzhongliang/article/details/133174578
版权归原作者 千锋教育官方 所有, 如有侵权,请联系我们删除。

“【直接收藏】前端JavaScript面试100问(终)”的评论:

还没有评论