前端基本功-常见概念(二)

于梦中2010

前端基本功-常见概念(一) 点这里
前端基本功-常见概念(二) 点这里
前端基本功-常见概念(三) 点这里

1.let、const/var

let 是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。
const 定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值
  • var存在的问题

    • var有作用域问题(会污染全局作用域)
    • var可已重复声明
    • var会变量提升预解释
    • var不能定义常量
  • let、const特性

    • let、const不可以重复声明
    • let、const不会声明到全局作用域上
    • let、const不会预解释变量
    • const做常量声明(一般常量名用大写)
    • let声明的变量具有块级作用域
    • let声明的变量不能通过window.变量名进行访问
    • 形如for(let x..)的循环是每次迭代都为x创建新的绑定

下面是 var 带来的不合理场景

var a = []
for (var i = 0; i < 10; i++) {
    a[i] = function () {
        console.log(i)
    }
}
a[5]() // 10

在上述代码中,变量i是var声明的,在全局范围类都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10
而如果对循环使用let语句的情况,那么每次迭代都是为x创建新的绑定代码如下

var a = []
for (let i = 0; i < 10; i++) {
    a[i] = function () {
        console.log(i)
    }
}
a[5]() // 5

重温一下闭包和立即函数两种方法

  • 闭包的方法
function showNum(i) {
    return function () {
        console.log(i)
    }
}
var a = []
for (var i = 0; i < 5; i++) {
    a[i] = showNum(i)
}
  • 立即函数的方法
var a = []
for (var i = 0; i < 5; i++) {
    a[i] = (function (i) {
        return function () {
            console.log(i)
        }
    })(i)
}
a[2]()

本节参考文章:前端面试之ES6篇

2.重排/重绘

在讨论重排(回流)与重绘之前,我们要知道:

  1. 浏览器使用流式布局模型 (Flow Based Layout)。
  2. 浏览器会把HTMLDOM,把CSS解析成CSSOMDOMCSSOM合并就产生了Render Tree
  3. 有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。
  4. 由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一。
一句话:重排必将引起重绘,重绘不一定会引起重排。

重排 (Reflow)

当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为重排。

会导致重排的操作:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法

一些常用且会导致重排的属性和方法:

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • scrollIntoView()、scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

重绘 (Repaint)

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

性能影响

回流比重绘的代价要更高。

有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。

现代浏览器会对频繁的回流或重绘操作进行优化:

浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

当你访问以下属性或方法时,浏览器会立刻清空队列:

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • width、height
  • getComputedStyle()
  • getBoundingClientRect()

因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。

如何避免

  • CSS

    • 避免使用table布局。
    • 尽可能在DOM树的最末端改变class。
    • 避免设置多层内联样式。
    • 将动画效果应用到position属性为absolute或fixed的元素上。
    • 避免使用CSS表达式(例如:calc())。
  • JavaScript

    • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
    • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
    • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
    • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
    • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
本节参考文章:[浏览器的回流与重绘 (Reflow & Repaint)](https://juejin.im/post/5a9923e9518825558251c96a)

3.函数节流(throttle)与函数去抖(debounce)

  • Debounce:一部电梯停在某一个楼层,当有一个人进来后,20秒后自动关门,这20秒的等待期间,又一个人按了电梯进来,这20秒又重新计算,直到电梯关门那一刻才算是响应了事件。
  • Throttle:好比一台自动的饮料机,按拿铁按钮,在出饮料的过程中,不管按多少这个按钮,都不会连续出饮料,中间按钮的响应会被忽略,必须要等这一杯的容量全部出完之后,再按拿铁按钮才会出下一杯。

4.强缓存/协商缓存

浏览器缓存分为强缓存和协商缓存,优先读取强制缓存。
当客户端请求某个资源时,获取缓存的流程如下:

  • 先根据这个资源的一些 http header 判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器;
  • 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些request header验证这个资源是否命中协商缓存,称为http再验证,如果命中,服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;
  • 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源;
  • 区别是,强缓存不对发送请求到服务器,但协商缓存会。
  • 当协商缓存也没命中时,服务器就会将资源发送回客户端。
  • 当 ctrl+f5 强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;
  • 当 f5 刷新网页时,跳过强缓存,但是会检查协商缓存;

clipboard.png

强缓存

  • Expires(该字段是 http1.0 时的规范,值为一个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)
  • Cache-Control:max-age(该字段是 http1.1 的规范,强缓存利用其 max-age 值来判断缓存资源的最大生命周期,它的值单位为秒)

协商缓存

协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的

  • Last-Modified,If-Modified-Since

Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来

但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag
  • ETag、If-None-Match

Etag就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的

If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来
  • ETag的优先级比Last-Modified更高

    • 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
    • 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
    • 某些服务器不能精确的得到文件的最后修改时间。

推荐必读:http协商缓存VS强缓存浏览器缓存知识小结及应用缓存(二)——浏览器缓存机制:强缓存、协商缓存

5.原始类型 / 引用类型

JavaScript中的内存分为栈内存和堆内存。栈内存和堆内存区别:栈内存运行效率比堆内存高,空间相对堆内存来说较小。

区别:

  • 值类型属于不可变类型, 由于具有固定长度大小, 其地址和具体内容都存在与栈内存中
  • 而引用类型属于可变类型, 一个对象可以赋予多个属性及值,属性值又可以为一个新的引用对象。其地址存在栈内存,其具体内容存在堆内存中。

6.cookie/token

  1. token和cookie一样都是首次登陆时,由服务器下发,都是当交互时进行验证的功能,作用都是为无状态的HTTP提供的持久机制。
  2. token存在哪儿都行,localstorage或者cookie。
  3. token和cookie举例,token就是说你告诉我你是谁就可以。

    cookie 举例:服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。

    token 举例:直接给服务员看自己身份证,服务器不需要去查看你是谁,不需要保存你的会话。

  4. 当用户logout的时候,cookie和服务器的session都会注销;但是当logout时候token只是注销浏览器信息,不查库。
  5. token优势在于,token由于服务器端不存储会话,所以可扩展性强,token还可用于APP中。

小结:

Token 完全由应用管理,所以它可以避开同源策略
Token 可以避免 CSRF 攻击
Token 可以是无状态的,可以在多个服务间共享

如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token,如果之上自己的那就无所谓了。

本节参考文章:cookie和token的五点区别

推荐必读:前后端常见的几种鉴权方式

7.cookie/sessionStorage/localStorage

1.cookie

cookie分为cookie机制和session机制(请大神判断正确性)
Session: 是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中,通过在服务器端记录信息确定用户身份
Cookie: 是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式,通过在客户端记录信息确定用户身份

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

cookie机制
cookie可以通过设置domain属性值,可以不同二级域名下共享cookie,而Storage不可以,比如http://image.baidu.com的cookie http://map.baidu.com是可以访问的,前提是Cookie的domain设置为.http://baidu.com,而Storage是不可以的

session机制

当程序需要为某个客户端的请求创建一个session时,

  • 服务器首先检查这个客户端的请求里是否已包含了一个session标识---称为session id,
  • 如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),
  • 如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

比较

  • session 在服务器端,cookie 在客户端(浏览器)
  • session保存在服务器,客户端不知道其中的信息;反之,cookie保存在客户端,服务器能够知道其中的信息
  • session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
  • session中保存的是对象,cookie中保存的是字符串
  • cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。用户验证这种场合一般会用 session
  • 用户验证这种场合一般会用 session,因此,维持一个会话的核心就是客户端的唯一标识,即 session id
  • session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
  • session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的

JavaScript原生的用法。

Cookie 以名/值对形式存储
例如username=John Doe,这里的数据是string类型,如要是其他格式注意进行格式转换。

JavaScript 可以使用 document.cookie 属性来创建 、读取、及删除 cookie。JavaScript 中,创建 cookie 如下所示:

document.cookie="username=John Doe";

您还可以为 cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除:

document.cookie="username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT";

您可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。

document.cookie="username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT; path=/";

设置cookie

function setCookie(cname,cvalue,exdays){
  var SetTime = new Date();                                         //设置过期时间
  SetTime.setTime(SetTime.getTime()+(exdays*24*60*60*1000));        //设置过期时间
  var expires = "expires="+SetTime.toGMTString();                   //设置过期时间
  document.cookie = cname + "=" + cvalue + "; " + expires;          //创建一个cookie
}

读取cookie

function getCookie(c_name){
if (document.cookie.length>0) {
  c_start=document.cookie.indexOf(c_name + "=")
  if (c_start!=-1){ 
    c_start=c_start + c_name.length+1 
    c_end=document.cookie.indexOf(";",c_start)
    if (c_end==-1) c_end=document.cookie.length
    return unescape(document.cookie.substring(c_start,c_end))
    } 
  }
return ""
}

删除cookie

将cookie的有效时间改成昨天。

使用jquery.cookies.2.2.0.min.js插件

添加/修改cookie并设定过期时间:

$.cookies.set('cookie_id', 'cookie_value', { hoursToLive: 10 });

这里设置的是过期时间是10小时, 还可以这样设置过期时间:

expireDate = new Date();
expireDate.setTime( expireDate.getTime() + ( 10 * 60 * 60 * 1000 ) );
$.cookies.set('cookie_id', 'cookie_value', {expiresAt:expireDate});

获取cookie

$.cookies.get('cookie_id');

删除cookie

$.cookies.del('cookie_id');

2.Storage:localStorage、sessionStorage

大小:官方建议是5M存储空间
类型:只能操作字符串,在存储之前应该使用JSON.stringfy()方法先进行一步安全转换字符串,取值时再用JSON.parse()方法再转换一次
存储的内容: 数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储)
区别:sessionStorage将数据临时存储在session中,浏览器关闭,数据随之消失,localStorage将数据存储在本地,理论上来说数据永远不会消失,除非人为删除
注意:数据是明文存储,毫无隐私性可言,绝对不能用于存储

基础操作API

保存数据

localStorage.setItem( key, value );
sessionStorage.setItem(keyName,value);   // 将value存储到key字段中
//或者
sessionStorage.keyName='value';

读取数据

localStorage.getItem( key );
sessionStorage.getItem(keyName);          //获取指定key的本地存储的值
//或者
var keyName=sessionStorage.key;

删除单个数据

localStorage.removeItem( key );
sessionStorage.removeItem( key );

删除全部数据

localStorage.clear( );
sessionStorage.clear( );

获取索引的key

localStorage.key( index );
sessionStorage.key( index );

监听storage事件

可以通过监听 window 对象的 storage 事件并指定其事件处理函数,当页面中对 localStorage 或 sessionStorage 进行修改时,则会触发对应的处理函数

window.addEventListener('storage',function(e){
   console.log('key='+e.key+',oldValue='+e.oldValue+',newValue='+e.newValue);
})

localstorage是浏览器多个标签共用的存储空间,可以用来实现多标签之间的通信(ps:session是会话级的存储空间,每个标签页都是单独的

触发事件的时间对象(e 参数值)有几个属性:
key : 键值。
oldValue : 被修改前的值。
newValue : 被修改后的值。
url : 页面url。
storageArea : 被修改的 storage 对象。

3.对比

共同点:都是保存在浏览器端、且同源的,都受同源策略的制约。

区别:

  • cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
  • 存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  • 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
  • 作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的

本节参考文章:缓存(三)——数据存储...

其他阅读:关于Cookie、session和Web Storage

8.js事件

1.事件

事件指可以被 JavaScript 侦测到的行为。即鼠标点击、页面或图像载入、鼠标悬浮于页面的某个热点之上、在表单中选取输入框、确认表单、键盘按键等操作。
事件通常与函数配合使用,当事件发生时函数才会执行。

事件名称:click/mouseover/blur("不带on")
事件处理程序(事件侦听器):响应某个事件的函数,名称为:onclick/onmouseove/onblur,例如<button onclick="alert('hello')"></button>

2.DOM事件模型:冒泡和捕获

冒泡:往上
捕获:向下

3.事件流

事件流指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。

DOM2级事件规定的事件流包括三个阶段:
(1)事件捕获阶段(2)处于目标阶段(3)事件冒泡阶段。

当事件发生时,最先得到通知的是window,然后是document,由上至下逐级依次而入,直到真正触发事件的那个元素(目标元素)为止,这个过程就是捕获。
接下来,事件会从目标元素开始起泡,由下至上逐级依次传播,直到window对象为止,这个过程就是冒泡。
所以捕获比冒泡先执行。

希望注册在DOM元素上的事件处理程序在捕获阶段还是在冒泡阶段触发,取决于 addEventListener() 方法的第三个参数为 true 还是 false 。

其中DOM3级事件在DOM2的基础之上添加了更多的事件类型。

描述DOM事件捕获的具体流程

window-->document-->html(document.documentElement)-->body(document.body)...

4.DOM级别/DOM事件

DOM级别一共可以分为4个级别:DOM0级,DOM1级,DOM2级和DOM3级,
而DOM事件分为3个级别:DOM0级事件处理,DOM2级事件处理和DOM3级事件处理。

其中1级DOM标准中并没有定义事件相关的内容,所以没有所谓的1级DOM事件模型。

DOM0:element.onclick = function(){}
DOM2:element.addEventlistenter('click',function(){},flase)
DOM3:element.addEventlistenter('keyup',function(){},flase)
  • HTML事件处理程序

    <button onclick="alert('hello')"></button>
    <button onclick="doSomething()"></button>
    <button onclick="try{doSomething();}catch(err){}"></button>
  • DOM0级事件处理程序

     btn.onclick=function(){
        alert("hello");
      }
    btn.onclick = null;//来删除指定的事件处理程序。

    如果我们尝试给事件添加两个事件,如:

    <button id="btn">点击</button>
     
    <script>
      var btn=document.getElementById("btn");
      btn.onclick=function(){
        alert("hello");
      }
      btn.onclick=function(){
        alert("hello again");
      }
    </script>

    输出,hello again,很明显,第一个事件函数被第二个事件函数给覆盖掉了, 所以,DOM0级事件处理程序不能添加多个,也不能控制事件流到底是捕获还是冒泡。

  • DOM2级事件处理程序

    addEventListener() ---添加事件侦听器

    函数均有3个参数,
    第一个参数是要处理的事件名(不带on前缀的才是事件名)
    第二个参数是作为事件处理程序的函数
    第三个参数是一个boolean值,默认false表示使用冒泡机制,true表示捕获机制。

    <button id="btn">点击</button>
     
    <script>
      var btn=document.getElementById("btn");
      btn.addEventListener('click',hello,false);
      btn.addEventListener('click',helloagain,false);
      function hello(){
        alert("hello");
      }
      function helloagain(){
        alert("hello again");
      }
    </script>
     removeEventListener() //删除事件侦听器

可以绑定多个事件处理程序,但是注意,如果定义了一摸一样时监听方法,是会发生覆盖的,即同样的事件和事件流机制下相同方法只会触发一次,事件触发的顺序是添加的顺序


```
// 为了兼容IE浏览器和标准的浏览器,我们需要编写通用的方法来处理:
var EventUtil = {
    addHandler: function (element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    removeHandler: function (element, type, handler) {
        if (element.removeEventListener()) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    }
};
```


5.事件对象

事件对象是用来记录一些事件发生时的相关信息的对象,但事件对象只有事件发生时才会产生,并且只能是事件处理函数内部访问,在所有事件处理函数运行结束后,事件对象就被销毁!

//currentTarget、eventPhase 一个例子:

<button id="btn">点击</button>
 
<script>
        var btn=document.getElementById("btn");
        btn.ddEventListener('click', doCurrent, true);
        // 判断事件的属性
        function doCurrent(event) {
            //获取当前事件触发的div
            var target = event.currentTarget;

            //通过判断事件的event.eventPhase属性返回事件传播的当前阶段
            //1:捕获阶段、2:正常事件派发和3:起泡阶段。
            //得到当前阶段和id值并输出
            var msg = (event.eventPhase == 1 ? '捕获阶段:' : '冒泡阶段:')+ target.attributes["id"].value;;
            console.log(msg);
        }
</script>

当然,事件对象也存在一定的兼容性问题,在IE8及以前本版之中,通过设置属性注册事件处理程序时,调用的时候并未传递事件对象,需要通过全局对象window.event来获取。解决方法如下:

function getEvent(event) {
 event = event || window.event;
}

在IE浏览器上面是event事件是没有preventDefault()这个属性的,所以在IE上,我们需要设置的属性是returnValue

window.event.returnValue=false

stopPropagation()也是,所以需要设置cancelBubble,cancelBubble是IE事件对象的一个属性,设置这个属性为true能阻止事件进一步传播。

event.cancelBubble=true
常见属性 解析
event.preventDefault() 阻止默认行为
event.stopPropagation() 阻止冒泡。使用了stopPropagation()之后,事件就不能进一步传播了,同时如果是同一个div上有捕获和冒泡两种事件监听,在捕获阶段传播阻止后冒泡阶段事件监听也不会触发。
event.stopImmediatePropagation() 使用了stopImmediatePropagation()之后,绑定的后续事件监听都会忽略。
event.currentTarget 当前绑定的事件
event.target 事件代理时 点击的元素

关于捕获和冒泡:理解 addEventListener、捕获和冒泡

6.自定义事件

jq

 // 添加一个适当的事件监听器
$('#foo').on('custom', function(event, param1, param2) {
  alert(param1 + "\n" + param2);
});
$('#foo').trigger('custom', ['Custom', 'Event']);

原生Event:

var eve = new Event('custome')
element.addEventListenter('custome',function(){
    console.log('custome')
})
element.dispatchEvent(eve)

原生CustomEvent

// 添加一个适当的事件监听器
obj.addEventListener("custom", function(e) { 
   console.log(JSON.stringify(e.detail));
 })
// 创建并分发事件
var event = new CustomEvent("custom", {"detail":{"Custom":true}});
obj.dispatchEvent(event)

本节参考文章:一个能拖动,能调整大小,能更新bind值的vue指令-vuedragx

7.事件委托(代理)

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

案例一:

    <button id="btnAdd">添加</button>
    <ul id="ulList">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
    <script>
        var btnAdd = document.getElementById('btnAdd');
        var ulList = document.getElementById('ulList');
        var list = document.getElementsByTagName('li');
        var num = 3;
        btnAdd.onclick = function () {
            num++;
            var li = document.createElement('li');
            li.innerHTML = num;
            ulList.appendChild(li)
        }
        for (i = 0; i < list.length; i++) {
            list[i].onclick = function(){
                alert(this.innerHTML);
            }
        }
    </script>
    //例子说明,我们为ul添加新的li,
    //其中对li标签元素绑定了click事件,
    //但是发现,后增加的元素没有办法触发我们的click事件。

解决方法:事件委托

 <button id="btnAdd">添加</button>
    <ul id="ulList">
        <li class="class-1">1</li>
        <li class="class-1">2</li>
        <li class="class-1">3</li>
    </ul>
    <script>
        var btnAdd = document.getElementById('btnAdd');
        var ulList = document.getElementById('ulList');
        var num = 3;

        ulList.onclick = function(event){
            var event = event || window.event;
            var target = event.target || event.srcElement;
            // if (target.matches('li.class-1')) 
            //#ulList 元素下的 li 元素(并且它的 class 为 class-1)
            if(target.nodeName.toLowerCase() == 'li'){
                alert(target.innerHTML);
            }
        }

        btnAdd.onclick = function () {
            num++;
            var li = document.createElement('li');
            li.innerHTML = num;
            ulList.appendChild(li);
        }
    </script>

案例二:

点击“添加”按钮添加一个按钮,点击添加的按钮移除这个按钮

<div class="wrap" id="wrap">
    <div class="btn" data-type="btn" data-feat="add">添加</div>
    <div class="btn" data-type="btn" data-feat="delete">绘画</div>
    <div class="btn" data-type="btn" data-feat="delete">散步</div>
    <div class="btn" data-type="btn" data-feat="delete">静坐</div>
</div>
document.getElementById('wrap').addEventListener('click', function(e){
    var target = e.target;
    while(target !== this){
        var type = target.dataset.type;
        if(type == 'btn'){
            var feat = target.dataset.feat;
            switch(feat){
                case 'add':
                    this.innerHTML += '<div class="btn" data-type="btn" data-feat="delete">静坐</div>'
                    return;
                case 'delete':
                    target.parentNode.removeChild(target);
                    return;
            }
        }
        target = target.parentNode;
    }
}, false);
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。

推荐阅读:JavaScript 事件委托详解

本节参考文章:前端小知识--JavaScript事件流

9.link / @import

两者都是外部引用 CSS 的方式,但是存在一定的区别:

  1. link是XHTML标签,除了能够加载CSS,还可以定义RSS等其他事务;而@import属于CSS范畴,只可以加载CSS。
  2. link引用CSS时,在页面载入时同时加载;@import需要页面完全载入以后再加载。
  3. link是XHTML标签,无兼容问题;@import则是在CSS2.1提出的,低版本的浏览器不支持。
  4. link方式的样式的权重 高于@import的权重.
  5. link支持使用Javascript控制DOM改变样式;而@import不支持。

本节参考文章:前端面试题-url、href、src

10.异步编程的实现方式

  • 1.回调函数
    优点:简单、容易理解
    缺点:不利于维护,代码耦合高
  • 2.事件监听(采用时间驱动模式,取决于某个事件是否发生):
    优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
    缺点:事件驱动型,流程不够清晰
  • 3.发布/订阅(观察者模式)
    类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
  • 4.Promise对象
    优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;
    缺点:编写和理解,相对比较难
  • 5.Generator函数
    优点:函数体内外的数据交换、错误处理机制
    缺点:流程管理不方便
  • 6.async函数
    优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。
    缺点:错误处理机制

11.documen.write/ innerHTML的区别

document.write只能重绘整个页面
innerHTML可以重绘页面的一部分

12.isPrototypeOf()/instanceof

isPrototypeOf() 与 instanceof 运算符不同。

  • 在表达式 "object instanceof AFunction"中,object 的原型链是针对 AFunction.prototype 进行检查的,而不是针对 AFunction 本身。
  • isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上。
function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

//isPrototypeOf
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true

if (Foo.prototype.isPrototypeOf(baz)) {
  // do something safe
}

//instanceof
console.log(baz instanceof Baz); // true
console.log(baz instanceof Bar); // true
console.log(baz instanceof Foo); // true
console.log(baz instanceof Object); // true
var obj1 = {
    name: 'esw'
}
var obj2 = Object.create(obj1)

// isPrototypeOf()方法
Object.prototype.isPrototypeOf(obj1)  // true
obj1.isPrototypeOf(obj2)  // true
Object.prototype.isPrototypeOf(obj2)  // true

13.constructor、__proto__与prototype

在javascript中我们每创建一个对象,该对象都会获得一个__proto__属性(该属性是个对象),该属性指向创建该对象的构造函数的原型prototype,同时__proto__对象有一个constructor属性指向该构造函数。这里我们需要注意的是只有函数才有prototype,每个对象(函数也是对象)都有__proto__Object本身是个构造函数。举例来说:

var obj = new Object()
// 也可以使用对象字面量创建,但使用Object.create()情况会不一样
// Object本身是个构造函数
Object instanceof Function  // true
obj.__proto__ === Object.prototype  // true
obj.__proto__.constructor === Object  // true
// 我们一般习惯这样写
obj.constructor === Object  // true

当我们访问obj.constructor的时候,obj本身是没有constructor属性的,但属性访问会沿着__proto__向上查找,即在obj.__proto__里面寻找constructor属性,如果找到了就返回值,如果未找到则继续向上查找直到obj.__proto__.__proto__...(__proto__) === null为止,没有找到则返回undefined。这样由__proto__构成的一条查找属性的线称为‘原型链’。

本节参考文章:重新认识javascript对象(三)——原型及原型链一篇文章带你进一步了解object属性

14.浅拷贝/深拷贝

1.浅拷贝只能复制值类型的属性。对于引用类型的属性,复制前后的两个对象指向同一内存地址,操作其中一个对象的引用类型属性,另一个对象也会相应发生改变;也就是说只有改变值类型的属性两个对象才不会相互影响。
2.深拷贝不仅可以复制值类型的属性,还可以复制引用类型的属性,无论两个对象怎么改变都不会相互影响。

浅复制

var obj = {
    a : 1,
    b: {
        c: 2
    }
}
// 浅复制
function lowerClone(obj){
    var newObj=obj.constructor === Array ? [] : {};
    for(var i in obj){
      newObj[i]=obj[i]
    }
    return newObj;
}
var objClone = lowerClone(obj)
objClone.a   // 1
obj.a  // 1
objClone.a = 100
// 改变复制对象的值类型属性,值类型属性的值不变
obj.a // 1
objClone.b.c = 200
// 改变复制对象的引用类型的属性,引用类型的属性值改变
obj.b.c //200

深复制

var obj = {
    a : 1,
    b: {
        c: 2
    }
}
function deepClone(obj){
    if( typeof obj != 'object'){
      return obj;
    }
    var newObj=obj.constructor === Array ? [] : {};
    for(var i in obj){
      newObj[i]=deepClone(obj[i]);
    }
    return newObj;
}
var objClone = deepClone(obj)
objClone.a   // 1
obj.a  // 1
objClone.a = 100
// 改变复制对象的值类型属性,值类型属性的值不变
obj.a // 1
objClone.b.c = 200
// 改变复制对象的引用类型的属性,引用类型的属性值不变
obj.b.c // 2

本节参考文章:javascript浅复制与深复制

15.apply/call/bind

apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用后续参数传参;
bind是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
call apply 的区别是他们指定参数的方式不同。

案例

function fn(a,b){
    console.log(this);
    console.log(a);
    console.log(b);
}
// bind(this,args...)
bf = fn.bind("Bind this",10); // 没有任何输出,也就是说没有执行这个函数
bf(); // "Bind this",10,undefined
bf(20);// “Bind this”,10,20
// 原函数不受影响
fn(1,2); //window, 1,2
bf2 = fn.bind("Bind this",1,2);
bf2(); // "Bind this",1,2

// call(this,args...)
fn.call("Call this",1) // "Call this",1,undefined
fn.call("Call this",1,2) // "Call this",1,2

// apply(this,[args])
fn.apply("Apply this",[1]) // "Apply this",1,undefined
fn.apply("Apply this",[1,2]) // "Apply this",1,2
阅读 1.9k

于梦中的前端成长日记
于梦中的前端成长日记——记录点滴

前端菜鸟儿,请多关照!

2.1k 声望
180 粉丝
0 条评论

前端菜鸟儿,请多关照!

2.1k 声望
180 粉丝
文章目录
宣传栏