前端面试总结五

1.OSI七层与TCP/IP四/五层网络架构
OSI七层模型:
开放系统互连参考模型 (Open System Interconnect 简称OSI)是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。它从低到高分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
在这里插入图片描述
OSI七层参考模型的各个层次的划分遵循下列原则:

  • 同一层中的各网络节点都有相同的层次结构,具有同样的功能。
  • 同一节点内相邻层之间通过接口(可以是逻辑接口)进行通信。
  • 七层结构中的每一层使用下一层提供的服务,并且向其上层提供服务。
  • 不同节点的同等层按照协议实现对等层之间的通信。

第一层:物理层(PhysicalLayer)

规定通信设备的机械的、电气的、功能的和过程的特性,用以建立、维护和拆除物理链路连接。具体地讲,机械 特性规定了网络连接时所需接插件的规格尺寸、引脚数量和排列情况等;电气特性规定了在物理连接上传输bit流时线路上信号电平的大小、阻抗匹配、传输速率 距离限制等;功能特性是指对各个信号先分配确切的信号含义,即定义了DTE和DCE之间各个线路的功能;规程特性定义了利用信号线进行bit流传输的一组 操作规程,是指在物理连接的建立、维护、交换信息是,DTE和DCE双放在各电路上的动作系列。在这一层,数据的单位称为比特(bit)。属于物理层定义的典型规范代表包括:EIA/TIA RS-232、EIA/TIA RS-449、V.35、RJ-45等。

第二层:数据链路层(DataLinkLayer)

在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路,通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上的动作系列。数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。在这一层,数据的单位称为帧(frame)。数据链路层协议的代表包括:SDLC、HDLC、PPP、STP、帧中继等。

第三层:网络层

在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。如 果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地 址解析协议(ARP)。有关路由的一切事情都在这第3层处理。地址解析和路由是3层的重要目的。网络层还可以实现拥塞控制、网际互连等功能。在这一层,数据的单位称为数据包(packet)。网络层协议的代表包括:IP、IPX、RIP、OSPF等。

第四层:传输层(处理信息)

第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段 (segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的 数据包和其它在传输过程中可能发生的危险。第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所为透明的传输是指在通信过程中 传输层对上层屏蔽了通信传输系统的具体细节。传输层协议的代表包括:TCP、UDP、SPX等。

第五层:会话层

这一层也可以称为会晤层或对话层,在会话层及以上的高层次中,数据传送的单位不再另外命名,而是统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的。

第六层:表示层

这一层主要解决拥护信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。

第七层:应用层

应用层为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括:Telnet、FTP、HTTP、SNMP等。

TCP/IP 四/五层模型的协议:
在这里插入图片描述
TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复 用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、 流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。

TCP支持的应用协议主要 有:Telnet、FTP、SMTP等;UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系 统)、TFTP(通用文件传输协议)等.

OSI七层与TCP/IP四层对应:
在这里插入图片描述

除了层的数量之外,开放式系统互联(OSI)模型与TCP/IP协议有什么区别?
(1) TCP/IP协议中的应用层处理开放式系统互联模型中的第五层、第六层和第七层的功能。
(2) TCP/IP协议与低层的数据链路层和物理层无关,这也是TCP/IP的重要特点。
(3) TCP/IP协议中的传输层并不能总是保证在传输层可靠地传输数据包,而开放式系统互联模型可以做到。TCP/IP协议还提供一项名为UDP(用户数据报协议)的选择。UDP不能保证可靠的数据包传输。

模型比较
共同点
(1)OSI参考模型和TCP/IP参考模型都采用了层次结构的概念。
(2)都能够提供面向连接和无连接两种通信服务机制。

不同点
(1)OSI采用的七层模型,而TCP/IP是四层结构。
(2)TCP/IP参考模型的网络接口层实际上并没有真正的定义,只是一些概念性的描述。而OSI参考模型不仅分了两层,而且每一层的功能都很详尽,甚至在数据链路层又分出一个介质访问子层,专门解决局域网的共享介质问题。
(3)OSI模型是在协议开发前设计的,具有通用性。TCP/IP是先有协议集然后建立模型,不适用于非TCP/IP网络。
(4)OSI参考模型与TCP/IP参考模型的传输层功能基本相似,都是负责为用户提供真正的端对端的通信服务,也对高层屏蔽了底层网络的实现细节。所不同的是TCP/IP参考模型的传输层是建立在网络互联层基础之上的,而网络互联层只提供无连接的网络服务,所以面向连接的功能完全在TCP协议中实现,当然TCP/IP的传输层还提供无连接的服务,如UDP;相反OSI参考模型的传输层是建立在网络层基础之上的,网络层既提供面向连接的服务,又提供无连接的服务,但传输层只提供面向连接的服务。
(5)OSI参考模型的抽象能力高,适合与描述各种网络;而TCP/IP是先有了协议,才制定TCP/IP模型的。
(6)OSI参考模型的概念划分清晰,但过于复杂;而TCP/IP参考模型在服务、接口和协议的 区别上不清楚,功能描述和实现细节混在一起。
(7)TCP/IP参考模型的网络接口层并不是真正的一层;OSI参考模型的缺点是层次过多,划分意义不大但增加了复杂性。
(8)OSI参考模型虽然被看好,由于没把握好时机,技术不成熟,实现困难;相反,TCP/IP参考模型虽然有许多不尽人意的地方,但还是比较成功的。

参考:OSI七层与TCP/IP四/五层网络架构

2.Proxy与Object.defineProperty
Object.defineProperty缺点:
(1)Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。 为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

由于只针对了以上八种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
(2)Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

Proxy优点:
(1)可以劫持整个对象,并返回一个新对象
(2)有13种劫持操作

Proxy:

  • Proxy是 ES6 中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。
  • Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
  • Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
  • 使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。

基本用法:

let p = new Proxy(target, handler);

target 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。
p 是代理后的对象。当外界每次对 p 进行操作时,就会执行 handler 对象上的一些方法。Proxy共有13种劫持操作,handler代理的一些常用的方法有如下几个:

get:读取
set:修改
has:判断对象是否有该属性
construct:构造函数

示例:
下面就用Proxy来定义一个对象的get和set,作为一个基础demo

 let obj = {};
 let handler = {
   get(target, property) {
    console.log(`${property} 被读取`);
    return property in target ? target[property] : 3;
   },
   set(target, property, value) {
    console.log(`${property} 被设置为 ${value}`);
    target[property] = value;
   }
 }
 
 let p = new Proxy(obj, handler);
 p.name = 'tom' //name 被设置为 tom
 p.age; //age 被读取 3

p 读取属性的值时,实际上执行的是 handler.get() :在控制台输出信息,并且读取被代理对象 obj 的属性。
p 设置属性值时,实际上执行的是 handler.set() :在控制台输出信息,并且设置被代理对象 obj 的属性的值。

基于Proxy来实现双向绑定

 <div id="app">
      <input type="text" id="input" />
      <div>您输入的是: <span id="title"></span></div>
      <button type="button" name="button" id="btn">添加到todolist</button>
      <ul id="list"></ul>
 </div>

先来一个Proxy,实现输入框的双向绑定显示:

    const obj = {};
    const input = document.getElementById("input");
    const title = document.getElementById("title");
    
    const newObj = new Proxy(obj, {
      get: function(target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
      },
      set: function(target, key, value, receiver) {
        console.log(target, key, value, receiver);
        if (key === "text") {
          input.value = value;
          title.innerHTML = value;
        }
        return Reflect.set(target, key, value, receiver);
      }
    });

    input.addEventListener("keyup", function(e) {
      newObj.text = e.target.value;
    });

接下来就是添加todolist列表,先把数组渲染到页面上去:


     // 渲染todolist列表
    const Render = {
      // 初始化
      init: function(arr) {
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < arr.length; i++) {
          const li = document.createElement("li");
          li.textContent = arr[i];
          fragment.appendChild(li);
        }
        list.appendChild(fragment);
      },
      addList: function(val) {
        const li = document.createElement("li");
        li.textContent = val;
        list.appendChild(li);
      }
    };

再来一个Proxy,实现Todolist的添加:

    const arr = [];
    // 监听数组
    const newArr = new Proxy(arr, {
      get: function(target, key, receiver) {
        return Reflect.get(target, key, receiver);
      },
      set: function(target, key, value, receiver) {
        console.log(target, key, value, receiver);
        if (key !== "length") {
          Render.addList(value);
        }
        return Reflect.set(target, key, value, receiver);
      }
    });

    // 初始化
    window.onload = function() {
      Render.init(arr);
    };

    btn.addEventListener("click", function() {
      newArr.push(parseInt(newObj.text));
    });

基于Proxy来实现vue的观察者机制
Proxy实现observe

    observe(data) {
        const that = this;
        let handler = {
         get(target, property) {
            return target[property];
          },
          set(target, key, value) {
            let res = Reflect.set(target, key, value);
            that.subscribe[key].map(item => {
              item.update();
            });
            return res;
          }
        }
        this.$data = new Proxy(data, handler);
      }

这段代码里把代理器返回的对象代理到this. d a t a , 即 t h i s . data,即this. datathis.data是代理后的对象,外部每次对this.$data进行操作时,实际上执行的是这段代码里handler对象上的方法。

compile和watcher
比较熟悉vue的同学都很清楚,vue2.x在 new Vue() 之后。 Vue 会调用 _init 函数进行初始化,它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是通过 Object.defineProperty 设置 setter 与 getter 函数,用来实现「响应式」以及「依赖收集」。类似于下面这个内部流程图:
在这里插入图片描述
而我们上面已经用Proxy取代了Object.defineProperty这部分观察者机制,而要实现整个基本mvvm双向绑定流程,除了observe还需要compile和watche等一系列机制,我们这里像模板编译的工作就不展开描述了,为了实现基于Proxy的vue添加Totolist,这里只写了
compile和watcher来支持observe的工作

参考:vue3.0尝鲜 – 摒弃Object.defineProperty,基于 Proxy 的观察者机制探索

3.Ajax在请求过程中显示进度的简单实现
beforeSend, 在请求发送前执行 比如 可以判断用户有没有登录 ,如果没有登录就停止请求 并提示。

$.ajax({
  url : 'my_action',
  dataType: 'script',
  beforeSend : function(xhr, opts){
    if(1 == 1) //just an example
    {
      xhr.abort(); // 停止请求
    }
  },
  complete: function(){
    console.log('DONE');
  }
});

$.ajax有一个参数是complete:function(){} 是在 请求完成之后执行的 ,配合beforeSend可以用来展示进度条

$.ajax({
     url : 'my_action',
     dataType: 'script',
     beforeSend : function(){
       // 设置 进度条到20%慢慢变到50%
     },
     complete: function(){
       // 设置 进度条到80%
     }
     success:function(){
       // 渲染页面
       // 进度到100%
     }
   });

这个只是表面上的看到的进度条 ,展示大概的进度,并不是真正的加载进度。

Ajax在Web应用中使用得越来越频繁。在进行Ajax调用过程中一般都具有这样的做法:显示一个GIF图片动画表明后台正在工作,同时阻止用户操作本页面(比如Ajax请求通过某个按钮触发,用户不能频繁点击该按钮产生多个并发Ajax请求);调用完成后,图片消失,当前页面运行重新编辑。以下图为例,页面中通过一个Load链接以Ajax请求的方式加载数据(左)。当用户点击该链接之后,Ajax请求开始,GIF图片显示“Loading“状态,同时当前页面被“罩住”防止用户继续点击Load按钮(中);Ajax请求完成被返回响应的结果,结果被呈现出来的同时,GIF图片和“遮罩”同时消失(右)。
在这里插入图片描述
在这里我同样以ASP.NET MVC应用为例,提供一种简单的实现方式。我们GIF图片和作为遮罩的

定义在布局文件中,并为它们定制了相应的CSS。其中GIF和遮罩
的z-index分别设置为2000和1000(这个任意,只要能够让遮罩的
遮住当前页面,GIF图片显示在最上层即可)。后者通过设置position、top、bottom、left和right是它可以遮住整个页面,并且将其背景设置为黑色。

<!DOCTYPE html>
<html>
  <head>
    <title>@ViewBag.Title</title>  
    <style type="text/css">
      .hide{displaynone }
      .progress{z-index }
      .mask{position fixed;top ;right ;bottom ;left ; z-index ; background-color #}
    </style>   
    ...
  </head>
  <body> 
    <div>@RenderBody()</div>
    <img id="progressImgage" class="progress hide" alt="" src="@Url.Content("~/Images/ajax-loader.gif")"/>
    <div id="maskOfProgressImage" class="mask hide"></div>
  </body>
</html>

然后我们通过如下的代码为jQuery定义了另一个实现Ajax调用的方法ajax2,该方法依然调用$.ajax(options)实现Ajax调用。在ajax2方法中我们将options参数complete属性进行了“封装”,让可以将显示出来的GIF图片和遮罩<div>隐藏起来。同时覆盖了options的async属性,是之总是以异步方式执行,因为只有这样浏览器才不能被锁住,GIF也才能正常显示。在调用$.ajax(options)进行Ajax请求之前,我们将GIF图片和遮罩<div>显示出来,并且将其定位在正中央。遮罩<div>的透明度进行了相应设置,所以会出现上图(中)的效果。

<!DOCTYPE html>
 <html>
   <head>
     ...
     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-...min.js")"></script>
     <script type="text/javascript">
       $(function () {
         $.ajax = function (options) {
           var img = $("#progressImgage");
           var mask = $("#maskOfProgressImage");
           var complete = options.complete;
           options.complete = function (httpRequest, status) {
             img.hide();
             mask.hide();
             if (complete) {
               complete(httpRequest, status);
             }
           };
           options.async = true;
           img.show().css({
             "position" "fixed",
             "top" "%",
             "left" "%",
             "margin-top" function () { return - * img.height() / ; },
             "margin-left" function () { return - * img.width() / ; }
           });
           mask.show().css("opacity", ".");
           $.ajax(options);
         };
       });
     </script>
   </head>
   ...
 </html>

那么现在进行Ajax调用的时候只需要调用$.ajax2就可以,如下所示的是实例中“Load”链接的click事件的注册代码:

<a href="#" id="load">Load</a>
<div id="result"></div>
<script type="text/javascript">
  $("#load").click(function () {
    $.ajax ({
      url '@Url.Action("GetContacts")',
      success function(result)
      {
        $("#result").html(result);
      }
    });
  });
</script>

参考:Ajax在请求过程中显示进度的简单实现

4.箭头函数与普通函数的区别
基本语法:
ES6中允许使用箭头=>来定义箭头函数,具体语法,我们来看一个简单的例子:

// 箭头函数
let fun = (name) => {
    // 函数体
    return `Hello ${name} !`;
};

// 等同于
let fun = function (name) {
    // 函数体
    return `Hello ${name} !`;
};

可以看出,定义箭头函在数语法上要比普通函数简洁得多。箭头函数省去了function关键字,采用箭头=>来定义函数。函数的参数放在=>前面的括号中,函数体跟在=>后的花括号中。

关于箭头函数的参数:
① 如果箭头函数没有参数,直接写一个空括号即可。
② 如果箭头函数的参数只有一个,也可以省去包裹参数的括号。
③ 如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中即可。

// 没有参数
let fun1 = () => {
    console.log(111);
};

// 只有一个参数,可以省去参数括号
let fun2 = name => {
    console.log(`Hello ${name} !`)
};

// 有多个参数
let fun3 = (val1, val2, val3) => {
    return [val1, val2, val3];
};

关于箭头函数的函数体:
① 如果箭头函数的函数体只有一句代码,就是简单返回某个变量或者返回一个简单的JS表达式,可以省去函数体的大括号{ }。

let f = val => val;
// 等同于
let f = function (val) { return val };

let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
  return num1 + num2;
};

② 如果箭头函数的函数体只有一句代码,就是返回一个对象,可以像下面这样写:

// 用小括号包裹要返回的对象,不报错
let getTempItem = id => ({ id: id, name: "Temp" });

// 但绝不能这样写,会报错。
// 因为对象的大括号会被解释为函数体的大括号
let getTempItem = id => { id: id, name: "Temp" };

③ 如果箭头函数的函数体只有一条语句并且不需要返回值(最常见是调用一个函数),可以给这条语句前面加一个void关键字

let fn = () => void doesNotReturn();

箭头函数最常见的用处就是简化回调函数。

// 例子一
// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

// 例子二
// 正常函数写法
var result = [2, 5, 1, 4, 3].sort(function (a, b) {
  return a - b;
});

// 箭头函数写法
var result = [2, 5, 1, 4, 3].sort((a, b) => a - b);

箭头函数与普通函数的区别:
(1)语法更加简洁、清晰
从上面的基本语法示例中可以看出,箭头函数的定义要比普通函数定义简洁、清晰得多,很快捷。
(2)箭头函数不会创建自己的this(重要!!深入理解!!)
箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this。
箭头函数没有自己的this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变。

来看个例子:

var id = 'Global';

function fun1() {
    // setTimeout中使用普通函数
    setTimeout(function(){
        console.log(this.id);
    }, 2000);
}

function fun2() {
    // setTimeout中使用箭头函数
    setTimeout(() => {
        console.log(this.id);
    }, 2000)
}

fun1.call({id: 'Obj'});     // 'Global'

fun2.call({id: 'Obj'});     // 'Obj'

上面这个例子,函数fun1中的setTimeout中使用普通函数,2秒后函数执行时,这时函数其实是在全局作用域执行的,所以this指向Window对象,this.id就指向全局变量id,所以输出’Global’。
但是函数fun2中的setTimeout中使用的是箭头函数,这个箭头函数的this在定义时就确定了,它继承了它外层fun2的执行环境中的this,而fun2调用时this被call方法改变到了对象{id: ‘Obj’}中,所以输出’Obj’。

再来看另一个例子:

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
      console.log(this.id);
  }
};

obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'

上面这个例子,对象obj的方法a使用普通函数定义的,普通函数作为对象的方法调用时,this指向它所属的对象。所以,this.id就是obj.id,所以输出’OBJ’。
但是方法b是使用箭头函数定义的,箭头函数中的this实际是继承的它定义时所处的全局执行环境中的this,所以指向Window对象,所以输出’GLOBAL’。(这里要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中!!)
(3)箭头函数继承而来的this指向永远不变(重要!!深入理解!!)
上面的例子,就完全可以说明箭头函数继承而来的this指向永远不变。对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。
(4).call()/.apply()/.bind()无法改变箭头函数中this的指向
.call()/.apply()/.bind()方法可以用来动态修改函数执行时this的指向,但由于箭头函数的this定义时就已经确定且永远不会改变。所以使用这些方法永远也改变不了箭头函数this的指向,虽然这么做代码不会报错。

var id = 'Global';
// 箭头函数定义在全局作用域
let fun1 = () => {
    console.log(this.id)
};

fun1();     // 'Global'
// this的指向不会改变,永远指向Window对象
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(5)箭头函数不能作为构造函数使用
我们先了解一下构造函数的new都做了些什么?简单来说,分为四步:
① JS内部首先会先生成一个对象;
② 再把函数中的this指向该对象;
③ 然后执行构造函数中的语句;
④ 最终返回该对象实例。

但是!!因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!

let Fun = (name, age) => {
    this.name = name;
    this.age = age;
};

// 报错
let p = new Fun('cao', 24);

(6)箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。

// 例子一
let fun = (val) => {
    console.log(val);   // 111
    // 下面一行会报错
    // Uncaught ReferenceError: arguments is not defined
    // 因为外层全局环境没有arguments对象
    console.log(arguments); 
};
fun(111);

// 例子二
function outer(val1, val2) {
    let argOut = arguments;
    console.log(argOut);    // ①
    let fun = () => {
        let argIn = arguments;
        console.log(argIn);     // ②
        console.log(argOut === argIn);  // ③
    };
    fun();
}
outer(111, 222);

很明显,普通函数outer内部的箭头函数fun中的arguments对象,其实是沿作用域链向上访问的外层outer函数的arguments对象。

可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表!!
(7)箭头函数没有原型prototype

let sayHi = () => {
    console.log('Hello World !')
};
console.log(sayHi.prototype); // undefined

(8)箭头函数不能用作Generator函数,不能使用yeild关键字

参考:箭头函数与普通函数的区别总结

5.链表与数组的区别
链表:
链表是一种上一个元素的引用指向下一个元素的存储结构,链表通过指针来连接元素与元素;
链表是线性表的一种,所谓的线性表包含顺序线性表和链表,顺序线性表是用数组实现的,在内存中有顺序排列,通过改变数组大小实现。而链表不是用顺序实现的,用指针实现,在内存中不连续。意思就是说,链表就是将一系列不连续的内存联系起来,将那种碎片内存进行合理的利用,解决空间的问题。
所以,链表允许插入和删除表上任意位置上的节点,但是不允许随即存取。链表有很多种不同的类型:单向链表、双向链表及循环链表。

单向链表:
在这里插入图片描述
单向链表包含两个域,一个是信息域,一个是指针域。也就是单向链表的节点被分成两部分,一部分是保存或显示关于节点的信息,第二部分存储下一个节点的地址,而最后一个节点则指向一个空值。

双向链表:
在这里插入图片描述
从上图可以很清晰的看出,每个节点有2个链接,一个是指向前一个节点(当此链接为第一个链接时,指向的是空值或空列表),另一个则指向后一个节点(当此链接为最后一个链接时,指向的是空值或空列表)。意思就是说双向链表有2个指针,一个是指向前一个节点的指针,另一个则指向后一个节点的指针。

循环链表:
在这里插入图片描述
循环链表就是首节点和末节点被连接在一起。循环链表中第一个节点之前就是最后一个节点,反之亦然。

数组和链表的区别:
不同:
链表是链式的存储结构;数组是顺序的存储结构。
链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。
链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难;
数组寻找某个元素较为简单,但插入与删除比较复杂,由于最大长度需要再编程一开始时指定,故当达到最大长度时,扩充长度不如链表方便。

相同:
两种结构均可实现数据的顺序存储,构造出来的模型呈线性结构。

参考:数组和链表的区别浅析

6.浏览器请求并发限制
不同浏览器不同版本的并发限制统计:
在这里插入图片描述
原因:

  • 对客户端操作系统而言,过多的并发涉及到端口数量和线程切换开销。
  • HTTP/1.1有Keep Alive,支持复用现有连接,等请求返回回来后,再复用连接请求可以快很多。
  • 将所有请求一起发给服务器,也很可能会引发服务器的并发阈值控制而被BAN。

提升页面打开速度的技术:

  • domain hash:对资源做哈希,请求到不同的域下面。
  • cookie free:前后端分离,减少不必要的cookie提交。
  • css sprite:将零星的图片整合到一张大图中,减少请求次数。
  • js/css combine:资源合并,减少请求次数,不过也会增加文件修改的几率。
  • max expires time:合理设置客户端缓存时间。
  • loading images on demand:图片按需加载。

参考:聊聊浏览器请求并发限制

7.for…in与for…of区别
遍历数组通常用for循环:
ES5的话也可以使用forEach,ES5具有遍历数组功能的还有map、filter、some、every、reduce、reduceRight等,只不过他们的返回结果不一样。但是使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。

Array.prototype.method=function(){
  console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="数组"
for (var index in myArray) {
  console.log(myArray[index]);
}

for in遍历数组的毛病:
(1)index索引为字符串型数字,不能直接进行几何运算
(2)遍历顺序有可能不是按照实际数组的内部顺序
(3)使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性
所以for in更适合遍历对象,不要使用for in遍历数组

那么除了使用for循环,如何更简单的正确的遍历数组达到我们的期望呢(即不遍历method和name),ES6中的for of更胜一筹.

Array.prototype.method=function(){
  console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="数组";
for (var value of myArray) {
  console.log(value);
}

记住,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name

遍历对象:
遍历对象 通常用for in来遍历对象的键名

Object.prototype.method=function(){
  console.log(this);
}
var myObject={
  a:1,
  b:2,
  c:3
}
for (var key in myObject) {
  console.log(key);
}

for in 可以遍历到myObject的原型方法method,如果不想遍历原型方法和属性的话,可以在循环内部判断一下,hasOwnPropery方法可以判断某属性是否是该对象的实例属性

for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}

同样可以通过ES5的Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性

Object.prototype.method=function(){
  console.log(this);
}
var myObject={
  a:1,
  b:2,
  c:3
}

区别:
(1)for…in 循环:只能获得对象的键名,不能获得键值
for…of 循环:允许遍历获得键值

var arr = ['red', 'green', 'blue']
 
for(let item in arr) {
  console.log('for in item', item)
}
/*
  for in item 0
  for in item 1
  for in item 2
*/
 
for(let item of arr) {
  console.log('for of item', item)
}
/*
  for of item red
  for of item green
  for of item blue
*/

(2)对于普通对象,没有部署原生的 iterator 接口,直接使用 for…of 会报错

var obj = {
   'name': 'Jim Green',
   'age': 12
 }
 
 for(let key of obj) {
   console.log('for of obj', key)
 }
 // Uncaught TypeError: obj is not iterable

可以使用 for…in 循环遍历键名

for(let key in obj) {
   console.log('for in key', key)
 }
 /*
   for in key name
   for in key age
 */

也可以使用 Object.keys(obj) 方法将对象的键名生成一个数组,然后遍历这个数组

for(let key of Object.keys(obj)) {
   console.log('key', key)
 }
 /*
   key name
   key age
 */

(3)for…in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。for…of 则不会这样

let arr = [1, 2, 3]
arr.set = 'world'  // 手动添加的键
Array.prototype.name = 'hello'  // 原型链上的键
 
for(let item in arr) {
  console.log('item', item)
}
 
/*
  item 0
  item 1
  item 2
  item set
  item name
*/
 
for(let value of arr) {
  console.log('value', value)
}
 
/*
  value 1
  value 2
  value 3
*/

(4)forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效

let arr = [1, 2, 3, 5, 9]
arr.forEach(item => {
  if(item % 2 === 0) {
    return
  }
  console.log('item', item)
})
/*
  item 1
  item 3
  item 5
  item 9
*/

for…of 循环可以与break、continue 和 return 配合使用,跳出循环

for(let item of arr) {
   if(item % 2 === 0) {
     break
   }
   console.log('item', item)
 }
 // item 1

(5)无论是 for…in 还是 for…of 都不能遍历出 Symbol 类型的值,遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法

{
  let a = Symbol('a')
  let b = Symbol('b')

  let obj = {
    [a]: 'hello',
    [b]: 'world',
    c: 'es6',
    d: 'dom'
  }

  for(let key in obj) {
    console.info(key + ' --> ' + obj[key])
  }

  /*
    c --> es6
    d --> dom
  */

  let objSymbols = Object.getOwnPropertySymbols(obj)
  console.info(objSymbols)    //  [Symbol(a), Symbol(b)]
  objSymbols.forEach(item => {
    console.info(item.toString() + ' --> ' + obj[item])
  })

  /*
    Symbol(a) --> hello
    Symbol(b) --> world
  */

  // Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和Symbol键名
  let keyArray = Reflect.ownKeys(obj)
  console.log(keyArray)      //  ["c", "d", Symbol(a), Symbol(b)]
}

总之,for…in 循环主要是为了遍历对象而生,不适用于遍历数组
for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象

参考:for in 和for of的区别
for in 和 for of 的区别

8.cookie
cookie是什么
cookie是当你浏览某个网站的时候,由web服务器存储在你的机器硬盘上的一个小的文本文件。它其中记录了你的用户名、密码、浏览的网页、停留的时间等等信息。当你再次来到这个网站时,web服务器会先看看有没有它上次留下来的cookie。如果有的话,会读取cookie中的内容,来判断使用者,并送出相应的网页内容,比如在页面显示欢迎你的标语,或者让你不用输入ID、密码就直接登录等等。

当客户端要发送http请求时,浏览器会先检查下是否有对应的cookie。有的话,则自动地添加在request header中的cookie字段。注意,每一次的http请求时,如果有cookie,浏览器都会自动带上cookie发送给服务端。那么把什么数据放到cookie中就很重要了,因为很多数据并不是每次请求都需要发给服务端,毕竟会增加网络开销,浪费带宽。所以对于那设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。

简单的说就是:
(1) cookie是以小的文本文件形式(即纯文本),完全存在于客户端;cookie保存了登录的凭证,有了它,只需要在下次请求时带着cookie发送,就不必再重新输入用户名、密码等重新登录了。
(2) 是设计用来在服务端和客户端进行信息传递的;

这里我简单地画了个图,可以方便理解:
第一次请求时:
在这里插入图片描述
下一次请求时:
在这里插入图片描述
浏览器会把cookie放到请求头一起提交给服务器,cookie携带了会话ID信息。服务器会根据cookie辨认用户:由于cookie带了会话的ID信息,可以通过cookie找到对应会话,通过判断会话来判断用户状态。

cookie的属性
在浏览器的控制台中,可以直接输入:document.cookie来查看cookie。cookie是一个由键值对构成的字符串,每个键值对之间是“; ”即一个分号和一个空格隔开。
在这里插入图片描述
注意,这个方法只能获取非 HttpOnly 类型的cookie

每个cookie都有一定的属性,如什么时候失效,要发送到哪个域名,哪个路径等等。这些属性是通过cookie选项来设置的,cookie选项包括:expires、domain、path、secure、HttpOnly。在设置任一个cookie时都可以设置相关的这些属性,当然也可以不设置,这时会使用这些属性的默认值。在设置这些属性时,属性之间由一个分号和一个空格隔开。代码示例如下:

"key=name; expires=Sat, 08 Sep 2018 02:26:00 GMT; domain=ppsc.sankuai.com; path=/; secure; HttpOnly"

cookie的属性可以在控制台查看:Application选项,左边选择Storage,最后一个就是cookie,点开即可查看。

Expires、Max Age:
Expires选项用来设置“cookie 什么时间内有效”。Expires其实是cookie失效日期,Expires必须是 GMT 格式的时间(可以通过 new Date().toGMTString()或者 new Date().toUTCString() 来获得)。

new Date().toGMTString()或者 new Date().toUTCString()

如expires=Sat, 08 Sep 2018 02:26:00 GMT表示cookie将在2018年9月8日2:26分之后失效。对于失效的cookie浏览器会清空。如果没有设置该选项,这样的cookie称为会话cookie。它存在内存中,当会话结束,也就是浏览器关闭时,cookie消失。

补充:
Expires是 http/1.0协议中的选项,在http/1.1协议中Expires已经由 Max age 选项代替,两者的作用都是限制cookie 的有效时间。Expires的值是一个时间点(cookie失效时刻= Expires),而Max age的值是一个以秒为单位时间段(cookie失效时刻= 创建时刻+ Max age)。 另外, Max age的默认值是 -1(即有效期为 session ); Max age有三种可能值:负数、0、正数。负数:有效期session;0:删除cookie;正数:有效期为创建时刻+ Max age

Domain和Path
Domain是域名,Path是路径,两者加起来就构成了 URL,Domain和Path一起来限制 cookie 能被哪些 URL 访问。即请求的URL是Domain或其子域、且URL的路径是Path或子路径,则都可以访问该cookie,例如:

某cookie的 Domain为“baidu.com”, Path为“/ ”,若请求的URL(URL 可以是js/html/img/css资源请求,但不包括 XHR 请求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路径是“/ ”或子路径“/home”、“/home/login”,则都可以访问该cookie。

补充:
发生跨域xhr请求时,即使请求URL的域名和路径都满足 cookie 的 Domain和Path,默认情况下cookie也不会自动被添加到请求头部中。

Size
Cookie的大小

Secure
Secure选项用来设置cookie只在确保安全的请求中才会发送。当请求是HTTPS或者其他安全协议时,包含 Secure选项的 cookie 才能被发送至服务器。

默认情况下,cookie不会带Secure选项(即为空)。所以默认情况下,不管是HTTPS协议还是HTTP协议的请求,cookie 都会被发送至服务端。但要注意一点,Secure选项只是限定了在安全情况下才可以传输给服务端,但并不代表你不能看到这个 cookie。

补充:
如果想在客户端即网页中通过 js 去设置Secure类型的 cookie,必须保证网页是https协议的。在http协议的网页中是无法设置secure类型cookie的。

httpOnly
这个选项用来设置cookie是否能通过 js 去访问。默认情况下,cookie不会带httpOnly选项(即为空),所以默认情况下,客户端是可以通过js代码去访问(包括读取、修改、删除等)这个cookie的。当cookie带httpOnly选项时,客户端则无法通过js代码去访问(包括读取、修改、删除等)这个cookie。

在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。

可以在浏览器的控制台中看出哪些cookie是httpOnly类型的,HTTP下带绿色对勾的即是,如图:
在这里插入图片描述
只要是httponly类型的,在控制台通过document.cookie是获取不到的,也不能进行修改。

之所以限制客户端去访问cookie,主要还是出于安全的目的。因为如果任何 cookie 都能被客户端通过document.cookie获取,那么假如合法用户的网页受到了XSS攻击,有一段恶意的script脚本插到了网页中,这个script脚本,通过document.cookie读取了用户身份验证相关的 cookie,那么只要原样转发cookie,就可以达到目的了。

cookie的设置、读取、删除方法
cookie既可以由服务端来设置,也可以由客户端来设置。

服务端设置cookie
客户端第一次向服务端请求时,在相应的请求头中就有set-cookie字段,用来标识是哪个用户。

下图我是登录腾讯云的某个页面的响应头截图,可以看到响应头中有两个set-cookie字段,每段对应一个cookie,注意每个cookie放一个set-cookie字段中,不能将多个cookie放在一个set-cookie字段中。具体每个cookie设置了相关的属性:expires、path、httponly,具体属性含义可以结合1.2 cookie的属性来看:
在这里插入图片描述
服务端设置cookie的范围:
服务端可以设置cookie 的所有选项:expires、domain、path、secure、HttpOnly

客户端设置cookie
cookie不像web Storage有setItem,getItem,removeItem,clear等方法,需要自己封装。简单地在浏览器的控制台里输入:

document.cookie="name=lynnshen; age=18"

但发现只添加了第一个cookie:“name=lynnshen”,后面的cookie并没有添加进来:
在这里插入图片描述
最简单的设置多个cookie的方法就是重复执行document.cookie = “key=name”:

document.cookie = "name=lynnshen";
document.cookie = "age=18";

再看控制台:
在这里插入图片描述
注意:
当name、domain、path 这3个字段都相同的时候,cookie会被覆盖。

下面是我自己简单封装的设置、读取、删除cookie的方法:
设置cookie:

function setCookie(name,value,iDay){
    var oDate = new Date();
    oDate.setDate(oDate.getDate() + iDay);
    document.cookie = name + "=" + value + ";expires=" + oDate;
}

读取cookie,该方法简单地认为cookie中只有一个“=”,即key=value,如有更多需求可以在此基础上完善:

function getCookie(name){
    //例如cookie是"username=abc; password=123"
    var arr = document.cookie.split('; ');//用“;”和空格来划分cookie
    for(var i = 0 ;i < arr.length ; i++){
        var arr2 = arr[i].split("=");
        if(arr2[0] == name){
            return arr2[1];
        }
    }
    return "";//整个遍历完没找到,就返回空值
}

删除cookie:

function removeCookie(name){
    setCookie(name, "1", -1)//第二个value值随便设个值,第三个值设为-1表示:昨天就过期了,赶紧删除
}

cookie的缺点
(1) 每个特定域名下的cookie数量有限:
IE6或IE6-(IE6以下版本):最多20个cookie
IE7或IE7+(IE7以上版本):最多50个cookie
FF:最多50个cookie
Opera:最多30个cookie
Chrome和safari没有硬性限制
当超过单个域名限制之后,再设置cookie,浏览器就会清除以前设置的cookie。IE和Opera会清理近期最少使用的cookie,FF会随机清理cookie;
(2) 存储量太小,只有4KB;
(3) 每次HTTP请求都会发送到服务端,影响获取资源的效率;
(4) 需要自己封装获取、设置、删除cookie的方法;

参考:一文带你看懂cookie,面试前端不用愁

9.Symbol
ES6新加入了一种原始数据类型Symbol,表示独一无二的值,这是js的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
因此,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol值通过Symbol函数生成。

let s = Symbol();
typeof s
// "symbol"

Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象,所以不能添加属性。基本上,它是一种类似于字符串的原始数据类型。
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。这个参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不同的Symbol实例。

var s1 = Symbol("foo");
var s2 = Symbol("foo");
console.log(s1 == s2); // false
console.log(s1 === s2); // false

Symbol值不能与其他类型的值进行运算,会报错。但是Symbol值可以显式转为字符串。

作为属性名的Symbol
由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
console.log(a[mySymbol]); // "Hello!"

上面代码通过方括号结构和Object.defineProperty,将对象的属性名指定为一个Symbol值。
Symbol值作为对象属性名时,不能用点运算符。
在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。

let s = Symbol();
let obj = {
  [s](arg) { console.log("a function called!") }
};

Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。
Symbol值作为属性名时,该属性还是公开属性,不是私有属性。

属性名遍历:
Symbol作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。

var obj = {};
var a = Symbol('a');
var b = Symbol('b');
 
obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

Symbol.for()
使用给定的key搜索现有符号,如果找到则返回符号。否则将得到一个新的使用给定的key在全局符号注册表中创建的符号。
有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s1 === s2); // true

上面代码中,s1和s2都是Symbol值,但是它们都是同样参数的Symbol.for方法生成的,所以实际上是同一个值。
Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”) 30次,每次都会返回同一个Symbol值,但是调用Symbol(“cat”) 30次,会返回30个不同的Symbol值。

Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false

上面代码中,由于Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

Symbol.keyFor()
为给定符号从全局符号注册表中检索一个共享符号键。
Symbol.keyFor方法返回一个已登记的Symbol类型值的key。

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
 
var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

上面代码中,变量s2属于未登记的Symbol值,所以返回undefined。

10.判断JS数据类型的五种方法
在 ECMAScript 规范中,共定义了 7 种数据类型,分为基本类型和引用类型两大类,如下所示:
基本类型:String、Number、Boolean、Symbol、Undefined、Null
引用类型:Object

基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈中,即按值访问。
引用类型也称为复杂类型,由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此,其值存储在堆(heap)中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。引用类型除 Object 外,还包括 Function 、Array、RegExp、Date 等等。
鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型。对于这个问题,JavaScript 也提供了多种方法,但遗憾的是,不同的方法得到的结果参差不齐。

下面介绍常用的5种方法,并对各个方法存在的问题进行简单的分析。
(1)typeof
typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。

typeof ''; // string 有效
typeof 1; // number 有效
typeof Symbol(); // symbol 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Function(); // function 有效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效

有些时候,typeof 操作符会返回一些令人迷惑但技术上却正确的值:

  • 对于基本类型,除 null 以外,均可以返回正确的结果。
  • 对于引用类型,除 function 以外,一律返回 object 类型。
  • 对于 null ,返回 object 类型。
  • 对于 function 返回 function 类型。

其中,null 有属于自己的数据类型 Null , 引用类型中的 数组、日期、正则 也都有属于自己的具体类型,而 typeof 对于这些类型的处理,只返回了处于其原型链最顶端的 Object 类型,没有错,但不是我们想要的结果。

(2)instanceof
instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。

[] instanceof Array; // true
{} instanceof Object;// true
new Date() instanceof Date;// true
 
function Person(){};
new Person() instanceof Person;
 
[] instanceof Object; // true
new Date() instanceof Object;// true
new Person instanceof Object;// true

我们发现,虽然 instanceof 能够判断出 [ ] 是Array的实例,但它认为 [ ] 也是Object的实例。

(3)constructor
在这里插入图片描述
细节问题:

  • null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
  • 函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object

(4)toString
toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用

(5)无敌万能的方法:jquery.type()

//如果对象是undefined或null,则返回相应的“undefined”或“null”。
jQuery.type( undefined ) === "undefined"
jQuery.type() === "undefined"
jQuery.type( window.notDefined ) === "undefined"
jQuery.type( null ) === "null"
//如果对象有一个内部的[[Class]]和一个浏览器的内置对象的 [[Class]] 相同,我们返回相应的 [[Class]] 名字。 (有关此技术的更多细节。 )
jQuery.type( true ) === "boolean"
jQuery.type( 3 ) === "number"
jQuery.type( "test" ) === "string"
jQuery.type( function(){} ) === "function"
jQuery.type( [] ) === "array"
jQuery.type( new Date() ) === "date"
jQuery.type( new Error() ) === "error" // as of jQuery 1.9
jQuery.type( /test/ ) === "regexp"
//其他一切都将返回它的类型“object”。

11.Ajax与JSON的区别
Ajax技术的核心是XMLHttpRequest对象(简称XHR),可以通过使用XHR对象获取到服务器的数据,然后再通过DOM将数据插入到页面中呈现。虽然名字中包含XML,但Ajax通讯与数据格式无关,所以我们的数据格式可以是XML或JSON等格式。
XMLHttpRequest对象用于在后台与服务器交换数据,具体作用如下:

  • 在不重新加载页面的情况下更新网页
  • 在页面已加载后从服务器请求数据
  • 在页面已加载后从服务器接收数据
  • 在后台向服务器发送数据

XMLHttpRequest是一个JavaScript对象,它提供了一种简单的方法来检索URL中的数据。
要创建一个XMLHttpRequest实例,只需new一个:

var req = new XMLHttpRequest();

在创建XHR对象后,接着我们要调用一个初始化方法open()。
一是URL相对于执行代码的当前页面(使用绝对路径);二是调用open()方法并不会真正发送请求,而只是启动一个请求准备发送。
真正发送请求要使用send()方法,send()方法接受一个参数,即要作为请求主体发送的数据,如果不需要通过请求主体发送数据,我们必须传递一个null值。在调用send()之后,请求就会被分派到服务器

var req = new XMLHttpRequest();
req.open(
    "GET",
    "myxhrtest.aspx",
    false
);
req.send(null);

在发送请求之后,我们需要检查请求是否执行成功,首先可以通过status属性判断,一般来说,可以将HTTP状态代码为200作为成功标志。这时,响应主体内容会保存到responseText中。此外,状态代码为304表示请求的资源并没有被修改,可以直接使用浏览器缓存的数据,Ajax的同步请求代码如下:

if (req != null) {
    req.onreadystatechange = function() {
        if ((req.status >= 200 && req.status < 300) || req.status == 304) {
        }
        else {
            alert("Request was unsuccessful: " + req.status);
        }
    };
    req.open("GET", "www.myxhrtest.aspx", true);
    req.send(null);
}

通过readyState属性判断请求的状态,当readyState = 4时,表示收到全部响应数据,属性值的定义如下:
readyState值:0 未初始化;尚未调用open()方法;1 启动;尚未调用send()方法;2 已发送;但尚未收到响应;3 接收;已经收到部分响应数据;4 完成;收到全部响应数据

jsonp的产生原因
(1)Ajax直接请求普通文件存在跨域无权限访问的问题(静态页、动态页、web服务、wcf只要是跨域请求一律不准)
(2)web的页面上调用js文件是不受跨域的影响(凡拥有src属性的标签都拥有跨域能力script img iframe)
(3)可以判断现在想通过纯web端(ActiveX控件、服务端代理、H5之Websocket等方式不算)跨域访问数据就只有一种可能,就是在远程服务器上设法把数据装进js格式的文件里,供客户度调用和进一步处理;
(4)json的纯字符数格式可以简洁的描述复杂数据还被js原生支持
(5)web客户端通过与调用脚本一样的方式来调用跨域服务器上动态生成的js格式文件(后缀.json),服务器之所以要动态生成json文件目的把客户端需要的数据装入进去
(6)客户端在对json文件调用成功后获得自己所需的数据剩下的就按照自己需求进行处理和展现,这种获取远程数据的方式非常像ajax其实不一样
(7)为了方便客户端使用数据逐渐形成非正式传输协议jsonp
  该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住json数据 这样客户端就可以随意定制自己的函数来自动处理返回数据

ajax和jsonp的实质核心、区别联系
(1)ajax和jsonp的调用方式很像,目的一样,都是请求url,然后把服务器返回的数据进行处理,
(2)实质不同
    ajax的核心是通过xmlHttpRequest获取非本页内容
    jsonp的核心是动态添加script标签调用服务器提供的js脚本
(3)区别联系: 不在于是否跨域
    ajax通过服务端代理一样跨域
    jsonp也不并不排斥同域的数据的获取
(4)jsonp是一种方式或者说非强制性的协议
  ajax也不一定非要用json格式来传递数据

12.ES5和ES6的类
ES6中的类只是语法糖,它并没有改变类实现的本质。
举个例子,在ES5中定义一个类:

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function(){
    return 'Hi, I am ' + this.name;
}

而用ES6的写法重写一下,检测类型发现Person本质上仍然是函数:

class Person {
    constructor(name){
        this.name = name;
    }

    sayHello(){
        return 'Hi, I am ' + this.name;
    }
}

typeof Person; //'function'

调用的方式都是一致的:

var me = new Person('Yecao');   

用ES6定义class中的方法,定义在原型对象上的。与ES5不同的是,这些定义在原型对象的方法是不可枚举的。
ES6类和模块是严格模式下的;
不存在变量提升,保证子类父类的顺序;
类的继承也有新的写法:

class Female extends Person {
    constructor(name){
        super(name); //调用父类的,调用之后,子类才有this
        this.gender = 'female';
    }

    sayHello(){
        return super.sayHello() + ', I am ' + this.gender;
    }
}

注意点,子类必须在父类的构造函数中调用super(),这样才有this对象,因为this对象是从父类继承下来的。而要在子类中调用父类的方法,用super关键词可指代父类。
ES5中类继承的关系是相反的,先有子类的this,然后用父类的方法应用在this上。
ES6类继承子类的this是从父类继承下来的这个特性,使得在ES6中可以构造原生数据结构的子类,这是ES5无法做到的。
ES6也可以定义类的静态方法和静态属性,静态的意思是这些不会被实例继承,不需要实例化类,就可以直接拿来用。ES6中class内部只能定义方法,不能定义属性。在方法名前加上static就表示这个方式是静态方法,而属性还是按照ES5的方式来实现。

// ES5写法
Person.total = 0; //静态属性
Person.counter = function(){  //静态方法
    return this.total ++ ;
}

// ES6写法
class Person {
    ...
    static counter(){
        return this.total ++ ;
    }
}
Person.total = 0;

ES6中当函数用new关键词的时候,增加了new.target属性来判断当前调用的构造函数。这个有什么用处呢?可以限制函数的调用,比如一定要用new命令来调用,或者不能直接被实例化需要调用它的子类。

function Person(name){
    if(new.target === Person){
        this.name = name;
    }
    else{
        throw new Error('必须用new生成实例');
    }
}

参考:小记ES5和ES6的类

13.import和require的区别
node编程中最重要的思想就是模块化,import和require都是被模块化所使用。
(1)遵循规范
require 是 AMD规范引入方式
import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法
(2)调用时间
require是运行时调用,所以require理论上可以运用在代码的任何地方
import是编译时调用,所以必须放在文件开头
(3)本质
require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
import是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require

require / exports :
遵循 CommonJS/AMD,只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化。
用法只有以下三种简单的写法:

const fs = require('fs')
exports.fs = fs
module.exports = fs

import / export:
遵循 ES6 规范,支持编译时静态分析,便于JS引入宏和类型检验。动态绑定。
写法就比较多种多样:

import fs from 'fs'
import {default as fs} from 'fs'
import * as fs from 'fs'
import {readFile} from 'fs'
import {readFile as read} from 'fs'
import fs, {readFile} from 'fs'

export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'
  • 通过require引入基础数据类型时,属于复制该变量。
  • 通过require引入复杂数据类型时,数据浅拷贝该对象。
  • 出现模块之间的循环引用时,会输出已经执行的模块,而未执行的模块不输出(比较复杂)
  • CommonJS模块默认export的是一个对象,即使导出的是基础数据类型

在这里插入图片描述

参考:import和require的区别

14.JS延迟加载的几种方式
JS延迟加载,也就是等页面加载完成之后再加载 JavaScript 文件。JS延迟加载有助于提高页面加载速度。

一般有以下几种方式:

  • defer 属性
  • async 属性
  • 动态创建DOM方式
  • 使用jQuery的getScript方法
  • 使用setTimeout延迟方法
  • 让JS最后加载

(1)defer 属性
HTML 4.01 为 <script>标签定义了 defer属性。
用途:
表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕之后再执行。
<script>元素中设置 defer 属性,等于告诉浏览器立即下载,但延迟执行。

<!DOCTYPE html>
<html>
<head>
    <script src="test1.js" defer="defer"></script>
    <script src="test2.js" defer="defer"></script>
</head>
<body>
<!-- 这里放内容 -->
</body>
</html>  

说明:虽然<script>元素放在了<head>元素中,但包含的脚本将延迟浏览器遇到</html>标签后再执行。
HTML5规范要求脚本按照它们出现的先后顺序执行。在现实当中,延迟脚本并不一定会按照顺序执行。
defer属性只适用于外部脚本文件。支持 HTML5 的实现会忽略嵌入脚本设置的 defer属性。

(2)async 属性
HTML5 为<script>标签定义了 async属性。与defer属性类似,都用于改变处理脚本的行为。同样,只适用于外部脚本文件。
目的:
不让页面等待脚本下载和执行,从而异步加载页面其他内容。

异步脚本一定会在页面 load 事件前执行,不能保证脚本会按顺序执行。

<!DOCTYPE html>
<html>
<head>
    <script src="test1.js" async></script>
    <script src="test2.js" async></script>
</head>
<body>
<!-- 这里放内容 -->
</body>
</html> 

async和defer一样,都不会阻塞其他资源下载,所以不会影响页面的加载。
缺点:不能控制加载的顺序。

(3)动态创建DOM方式

//这些代码应被放置在</body>标签前(接近HTML文件底部)
<script type="text/javascript">  
   function downloadJSAtOnload() {  
       varelement = document.createElement("script");  
       element.src = "defer.js";  
       document.body.appendChild(element);  
   }  
   if (window.addEventListener)  
      window.addEventListener("load",downloadJSAtOnload, false);  
   else if (window.attachEvent)  
      window.attachEvent("onload",downloadJSAtOnload);  
   else 
      window.onload =downloadJSAtOnload;  
</script> 

(4)使用jQuery的getScript()方法

$.getScript("outer.js",function(){//回调函数,成功获取文件后执行的函数  
      console.log("脚本加载完成")  
}); 

(5)使用setTimeout延迟方法
(6)让JS最后加载
把js外部引入的文件放到页面底部,来让js最后引入,从而加快页面加载速度。

15.apply()与call()的区别
JavaScript中的每一个Function对象都有一个apply()方法和一个call()方法,它们的语法分别为:

/*apply()方法*/
function.apply(thisObj,[ argArray])

/*call()方法*/
function.call(thisObj,[ arg1[, arg2[, [,...argN]]]]);

apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。
call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。

共同之处:
都可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。
不同之处:
apply:
最多只能有两个参数—新this对象和一个数组argArray。如果给该方法传递多个参数,则把参数都写进这个数组里面,当然,即使只有一个参数,也要写进数组里。如果argArray不是一个有效的数组或arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。
call:
它可以接受多个参数,第一个参数与apply一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下需要改变this指针。如果没有提供thisObj参数,那么 Global 对象被用作thisObj。

实际上,apply和call的功能是一样的,只是传入的参数列表形式不同。

apply的一些其他巧妙用法:
(1)Math.max 可以实现得到数组中最大的一项:
因为Math.max不支持Math.max([param1,param2])也就是数组,但是它支持Math.max(param1,param2...),所以可以根据apply的特点来解决 var max=Math.max.apply(null,array),这样就轻易的可以得到一个数组中的最大项(apply会将一个数组转换为一个参数接一个参数的方式传递给方法)。
这块在调用的时候第一个参数给了null,这是因为没有对象去调用这个方法,我只需要用这个方法帮我运算,得到返回的结果就行,所以直接传递了一个null过去。
用这种方法也可以实现得到数组中的最小项:Math.min.apply(null,array)
(2)Array.prototype.push可以实现两个数组的合并
同样push方法没有提供push一个数组,但是它提供了push(param1,param2…paramN),同样也可以用apply来转换一下这个数组,即:

var arr1=new Array("1","2","3");
var arr2=new Array("4","5","6");
Array.prototype.push.apply(arr1,arr2);    //得到合并后数组的长度,因为push就是返回一个数组的长度

16.JS中的作用域、变量提升
作用域:
在大多数编程语言中,会用花括号{}来形成一个作用域,俗称“块作用域”,例如C语言、C++等。但是在JS中{}并不能产生块作用域,JS中的作用域是依靠函数形成的。
在ECMAScript5中,JS只有两类作用域:全局作用域、函数作用域。

全局作用域:全局对象的作用域,在代码的任何地方都可访问,但有时会被函数作用域覆盖
函数作用域:作用于整个函数范围内,不管到底是在函数中的何处进行声明

// 全局变量
var i = 100;
// 函数声明,outer是一个外部函数
function outer(){
    // 访问全局变量
    console.log(i);  // 100
    // 函数声明,inner是一个内部函数
    function inner(){
        // 内部函数的内部进行了变量提升,也就是第二部分叙述的内容
        console.log(i);  // undefined
        // 这里的i是局部变量,作用域仅在函数内
        var i = 1;
        // 局部变量覆盖全局变量,或者说是函数作用域覆盖全局作用域
        console.log(i);  // 1
    }
    inner();
    // 这里的i是全局变量
    console.log(i);  // 100
}
outer();

定义变量时,如果不写var,那么就会相当于声明了一个全局变量,作用域为全局作用域;否则声明的是局部变量,作用域为函数作用域。在以上代码段中,第一行的var i = 0是全局变量,虽然它添加var,但是在全局范畴中声明,而且不在函数范围内,因此效果等同于i = 0。但是在JS编程中应该尽力避免不加var,即使真的需要全局变量,也应该在最外层作用域中使用var声明。

变量提升:
变量的声明会被自动移到函数或者全局代码的最顶上。移动的仅仅是declarations,变量的定义并不会随之提升。
因为变量提升非常的weird,所以很多代码的欺骗性非常强,尤其是在前端面试或者笔试中考官非常青睐于类似的题目,因此我建议理解代码的等价形式,也就是在纸上根据变量提升原则来书写新的等价代码从而找出正确答案。如以下代码段:

var date = new Date();
function fn(){
    console.log(date);
    if(true){
        var date = 'hello';
    }
}
fn();

结果并不是date的toString方法返回的结果,而是undefined,因为以上代码等价于:

// 变量声明提升
var date;
date = new Date();
function fn(){
    // 变量声明提升,但是此时未定义变量的值
    var date;
    console.log(date);
    if(true){
        date = "hello";
    }
}
fn();

但是在变量提升中还存在着一些特殊情况,因为在ES5中,变量声明、函数声明都会被提升,这就衍生出很多值得辨析的问题。
在ES6中,function *, let, class, const也会被提升,但是提升机制又与变量提升、函数提升有所区别。

情境1:重复声明
如以下代码,重复声明变量a:

var a = 10;
console.log(a);  // 10
if(true){
    var a = 20;
    console.log(a);  // 20
}
console.log(a);  // 20

分析这一段代码时,记住两点:JS变量只有全局作用域、函数作用域两种作用域形态,a只会在代码顶部声明一次,而var a = 20的作用仅是赋值,因此以上代码等价于:

var a;
var a;  // 是流程控制语句中的a,实际上在JS解析中这一句是不存在的,因为变量`a`已经声明过了
a = 10;
console.log(a);
if(true){
    a = 20;
    console.log(a);
}
console.log(a);

情境2:命名冲突
当console.log处于需要提升的变量与方法的下方时,如果在同一个作用域中定义了名字相同的变量与方法,那么无论顺序如何,变量的赋值都会覆盖掉方法的赋值。其实用正常思维方式就可以理解。

var fn = 3;
function fn(){};
console.log(fn);  // 3

以上代码等价于:

var fn;
function fn(){};
fn = 3;
console.log(fn);

可以明显看出:经过转换后是很容易被理解的。但是还需要考虑到当函数执行有命名冲突的时候,函数执行的载入顺序是变量、函数、参数,如以下代码:

function f(f){
    console.log(f);
    var f = 100;
    console.log(f);
}
f(20);

这是一段复杂的代码,但是结合载入顺序来理解,可以将代码等价转换为如下形式:

function f(f){
    var f;
    console.log(f);
    f = 100;
    console.log(f);
}

传入参数f = 20后,函数内部相当于虚拟形成:var f = 20,这个变量声明其实可以认为是覆盖了函数内部变量提升的var f,因此第一个console.log(f)的结果为20,接下来是第二个f = 100覆盖之前的变量值,那么第二个console.log(f)的结果为100,所以在执行这个很annoying的函数的时候,先提升变量,再执行函数体,接下来传入参数20,事实上也很好理解。

情况3: 函数与变量同时提升
如下代码示例:

console.log(f);
function f(){};
var f = 'text';

输出结果是:function f(){}
但是稍作转变成如下形式:

console.log(f);
var f = function(){};
var f = 'text';

输出结果是:undefined

这里涉及到的知识实际上和变量提升关系不大,而是和函数声明方式有关:
ECMAScript里面规定三种声明函数方式,常用的有以下两种:

// 第一种:函数声明
function f(){
    statement;
}
// 第二种:函数表达式
var f = function(){
    statement;
}

针对第一段代码,其中运用函数声明,函数声明的方式所能保证的是:即使函数写在最后也能在之前语句中进行调用,但是函数声明部分必须已经被下载至本地;而第二段代码,其中运用函数表达式,实质上是定义了一个变量f,然后把function(){}赋给变量,因此第二段代码实际上等价于:

var f;
console.log(f);
f = function(){};
f = 'text';

第一段代码,函数声明在提升的时候,实际上是会把整个函数提升上去,包括函数定义的部分,所以第一段代码的等价形式是:

var f;
function f(){};
console.log(f);
f = 'text';

但是再将代码进行转换,会得到不一样的结果:

console.log(foo);
var foo = 'text';
function foo(){};

返回的结果是:function foo(){}

情况4:函数与函数重复声明
当两个函数声明重复时,其原则是后者覆盖前者。以下代码段:

console.log(foo);
function foo(n){return n+2};
function foo(n){return n+1};

输出结果是:function foo(n){return n+1}
原理:在函数声明提升时,遵循先来后到的函数声明提升原则,之后后者会覆盖前者,因此以上代码等价于:

function foo(n){return n+2};
function foo(n){return n+1};
console.log(foo);

如果调换代码秩序,那么代码输出结果会变化:

function foo(n){return n+1};
function foo(n){return n+2};
console.log(foo);  // function foo(n){return n+2};

总结:

  • 所有声明都会被提升到对应作用域的顶上(as if they were declared at the top of the function)
  • 同一个变量声明只进行一次,其他重复声明会被JS解析忽略
  • 函数声明进行提升时会连带函数定义一起提升
  • 遵循前三项原则多多动手写等价转换,就一定不会出错

17.关于ajax提交表单与form表单的区别
Ajax提交是通过js来提交请求,请求与响应均由js引擎来处理,页面不会刷新,用户感觉不到实际上浏览器发出了请求。比如说我们希望网页总是显示最新的新闻,而又不想老是去点刷新按钮,我们就可以用Ajax机制来实现。网上的客服软件也是ajax请求的一个比较好的案例。传统的请求页面将实现刷新,因此局限性很大。

为什么用AJAX:
使用AJAX,用户对Web的体验会更“敏捷”:数据提交页面不会闪屏;页面局部更新速度快;网络带宽占用低。

AJAX开发相较传统模式的简单之处:
传统模式下,表单提交则整个页面重绘,为了维持页面用户对表单的状态改变,要多些不少代码。要在控制器和模板之间传递更多参数以保持页面状态。而AJAX不然,因为页面只是局部更新,不关心也不会影响页面其他部分的内容。

AJAX开发相较传统模式的难度:
需要了解、精通JavaScript,而JavaScript存在调试麻烦、浏览器兼容性等很多障碍。

有如下几种区别:

  • Ajax在提交、请求、接收时,都是异步进行的,网页不需要刷新;Form提交则是新建一个页面,哪怕是提交给自己本身的页面,也是需要刷新的;
  • A在提交时,是在后台新建一个请求;F却是放弃本页面,而后再请求;
  • A必须要使用JS来实现,不启用JS的浏览器,无法完成该操作;F却是浏览器的本能,无论是否开启JS,都可以提交表单;
  • A在提交、请求、接收时,整个过程都需要使用程序来对其数据进行处理;F提交时,却是根据你的表单结构自动完成,不需要代码干预。

18.Ajax和Flash的区别
Ajax的优势:
(1)可搜索性
普通的文本网页会更有利于SEO。文本内容是搜索引擎容易检索的,而繁琐的swf字节码却是搜索引擎不愿触及的。虽然Google等一些大型的搜索引擎可以检索SWF内部的内容,但是仍然有很多麻烦存在。

(2)开放性
Flash常年以来被Macromedia看的很死。包括Flex、FMS等辅佐技术一直都需要昂贵的安装、维护费用。而JS则没有这样的麻烦。没有人愿意承担法律和版权的风险。

(3)费用
Flash开发是很昂贵的,因为FlashIDE等环境都是要收费的.而Ajax则不同.虽然有一些便宜的生成swf的工具,但是他们的工能实在无法满足复杂需求。

(4)易用性
Ajax程序有更好的易用性。由于中间有一层Flashplayer代理层,因此许多辅助功能无法被Flash灵活利用。而且Flash在一些方面有着不好的口碑。比如弹出广告、比如恶意代码。
(awflasher.com个人认为这八成是乱上xx网站造成的)

(5)易于开发
人们开发复杂的Ajax和Flash应用程序时,都会借助一些高级的开发工具。普遍来说,Ajax的开发包比Flash简便、容易。

Ajax的劣势:
(1)AJAX大量的使用了javascript和ajax引擎,这些取决于浏览器的支持.在编写的时候考虑对浏览器的兼容性.
(2)AJAX只是局部刷新,所以页面的后退按钮是没有用的.
(3)对流媒体还有移动设备的支持不是太好

Flash的优势:
(1)多媒体处理
Flash在音频、视频等多媒体领域相比HTML有绝对的优势。现在几乎所有的网站都包含有Flash内容。

(2)兼容性
兼容性好,由于通过了唯一的FlashPlayer“代理”。人们不必像调试JS那样,在不同的浏览器中调试程序。

(3)矢量图形
这是Flash最大的优势,同样处在这一领域的SVG、Canvas element以及Direct完全不能与Flash相比。

(4)客户端资源调度
Flash能够更容易的调用浏览器以外的外部资源。比如摄像头、麦克风等。然而这是普通的HTML无法完成的,但是这也许是一个缺点。

Flash的劣势:
(1)二进制格式
(2)有效性交互差
(3)flash 文件经常会很大,用户第一次使用的时候需要忍耐较长的等待时间
(4)商用体验表现能力差
(5)成本高
(6)不适合企业网站
(7)必须插件支持
(8)更新维护复杂
(9)可扩展性差
(10)搜索引擎不友好

19.事件处理程序
事件是用户或浏览器自身执行的某种动作,如click,load和mouseover都是事件的名字。响应某个事件的函数就叫事件处理程序(也叫事件处理函数、事件句柄)。事件处理程序的名字以"on"开头,因此click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。

为事件指定事件处理程序的方法主要有3种。
(1)html事件处理程序
事件直接加在html元素上。
首先,这种方法已经过时了。因为动作(javascript代码)和内容(html代码)紧密耦合,修改时即要修改html也要修改js。但是写个小demo的时候还是可以使用的。
这种方式也有两种方法,都很简单:
第一种:直接在html中定义事件处理程序及包含的动作。

<input type="button" value="click me!" onclick="alert('clicked!')"/>

第二种:html中定义事件处理程序,执行的动作则调用其他地方定义的脚本。

<input type="button" value="click me!" onclick="showMessage()"/>
<script>
function showMessage(){
    alert("clicked!");
}
</script>

note:
1)通过event变量可以直接访问事件本身,比如οnclick="alert(event.type)"会弹出click事件。
2)this值等于事件的目标元素,这里目标元素是input。比如 οnclick="alert(this.value)"可以得到input元素的value值。

(2)DOM0级事件处理程序
把一个函数赋值给一个事件处理程序属性,这种方法简单而且跨浏览器,但是只能为一个元素添加一个事件处理函数。因为这种方法为元素添加多个事件处理函数,则后面的会覆盖前面的。
添加事件处理程序:

<input id="myBtn" type="button" value="click me!"/>
<script>
    var myBtn=document.getElementById("myBtn");
    myBtn.onclick=function(){
        alert("clicked!");
    }
</script>

分析:

/*
第一步:myBtn=document.getElementById("myBtn");取得btn对象
第二步:myBtn.onclick其实相当于给myBtn添加了一个onclick的属性。
第三步:myBtn.οnclick=function(){
    alert("clicked!");
}
把函数赋值给onclick事件处理程序属性。
*/

删除事件处理程序:
myBtn.οnclick=null;

(3)DOM2级事件处理程序
DOM2级事件处理程序可以为一个元素添加多个事件处理程序。其定义了两个方法用于添加和删除事件处理程序:addEventListener()和removeEventListener()。
所有的DOM节点都包含这两个方法。
这两个方法都需要3个参数:事件名,事件处理函数,布尔值。
这个布尔值为true,在捕获阶段处理事件,为false,在冒泡阶段处理事件,默认为false。
添加事件处理程序:现在为按钮添加两个事件处理函数,一个弹出“hello”,一个弹出“world”。

<input id="myBtn" type="button" value="click me!"/>
<script>
    var myBtn=document.getElementById("myBtn");
    myBtn.addEventListener("click",function(){
        alert("hello");
    },false);
    myBtn.addEventListener("click",function(){
        alert("world");
    },false);
</script>

删除事件处理程序:
通过addEventListener添加的事件处理程序必须通过removeEventListener删除,且参数一致。
note:
通过addEventListener添加的匿名函数将无法删除。下面这段代码将不起作用!

    myBtn.removeEventListener("click",function(){
        alert("world");
    },false);

看似该removeEventListener与上面的addEventListener参数一致,实则第二个参数中匿名函数是完全不同的。
所以为了能删除事件处理程序,代码可以这样写:

<input id="myBtn" type="button" value="click me!"/>
<script>
    var myBtn=document.getElementById("myBtn");
    var handler=function(){
        alert("hello");
    }
    myBtn.addEventListener("click",handler,false);
    myBtn.removeEventListener("click",handler,false);
</script>

(4)IE事件处理程序
1)实际应用场景
IE8及以下浏览器不支持addEventListener,在实际开发中如果要兼容到IE8及以下浏览器。如果用原生的绑定事件,需要做兼容处理,可利用jquery的bind代替
在这里插入图片描述
2)IE8事件绑定
IE8及以下版本浏览器实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。
这两个方法都需要两个参数:事件处理程序名称和事件处理程序函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。注意是事件处理程序名称而不是事件名称,所以要加上on,是onclick而不是click。

note:

  • IE11只支持addEventListener;
  • IE9,IE10对attachEvent和addEventListener都支持;
  • TE8及以下版本只支持attachEvent。

可以拿下面代码在IE各个版本浏览器中进行测试。

<input id="myBtn" type="button" value="click me!"/>
<script>
    var myBtn=document.getElementById("myBtn");
    var handlerIE=function(){
        alert("helloIE");
    }
    var handlerDOM= function () {
        alert("helloDOM");
    }
    myBtn.addEventListener("click",handlerDOM,false);
    myBtn.attachEvent("onclick",handlerIE);
</script>

添加事件处理程序:现在为按钮添加两个事件处理函数,一个弹出“hello”,一个弹出“world”。

<script>
    var myBtn=document.getElementById("myBtn");
    myBtn.attachEvent("onclick",function(){
        alert("hello");
    });
    myBtn.attachEvent("onclick",function(){
        alert("world");
    });
</script>

note:这里运行效果值得注意一下:
IE8以下浏览器中先弹出“world”,再弹出“hello”。和DOM中事件触发顺序相反。
IE9及以上浏览器先弹出“hello”,再弹出“world”。和DOM中事件触发顺序相同了。
删除事件处理程序:通过attachEvent添加的事件处理程序必须通过detachEvent方法删除,且参数一致。
和DOM事件一样,添加的匿名函数将无法删除。
所以为了能删除事件处理程序,代码可以这样写:

<input id="myBtn" type="button" value="click me!"/>
<script>
    var myBtn=document.getElementById("myBtn");
    var handler= function () {
        alert("hello");
    }
    myBtn.attachEvent("onclick",handler);
    myBtn.detachEvent("onclick",handler);
</script>

note:IE事件处理程序中还有一个地方需要注意:作用域。
使用attachEvent()方法,事件处理程序会在全局作用域中运行,因此this等于window。
而dom2或dom0级的方法作用域都是在元素内部,this值为目标元素。
下面例子会弹出true。

<input id="myBtn" type="button" value="click me!"/>
<script>
    var myBtn=document.getElementById("myBtn");
    myBtn.attachEvent("onclick",function(){
        alert(this===window);
    });
</script>

在编写跨浏览器的代码时,需牢记这点。
IE7\8检测:

 //判断IE7\8 兼容性检测
            var isIE=!!window.ActiveXObject;
            var isIE6=isIE&&!window.XMLHttpRequest;
            var isIE8=isIE&&!!document.documentMode;
            var isIE7=isIE&&!isIE6&&!isIE8;
             
            if(isIE8 || isIE7){
                li.attachEvent("onclick",function(){
                    _marker.openInfoWindow(_iw);
                })    
            }else{
                li.addEventListener("click",function(){
                    _marker.openInfoWindow(_iw);
                })
            }

20.AMD 和 CMD 的区别
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。Asynchronous Modules Definition异步模块定义,提供定义模块及异步加载该模块依赖的机制。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。Common Module Definition 通用模块定义,提供模块定义及按需执行模块。

区别:
1)对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
2)CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ... 
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})

虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。
3)AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值