前端知识总结(二)

33、闭包

闭包的概念

上一节代码中的f2函数,就是闭包。

各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。

function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());//The Window
var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());//My Object

34、this指向

首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁实际上this的最终指向的是那个调用它的对象

例子1:

这里的函数a实际是被Window对象所点出来的

function a(){
    var user = "追梦子";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();//window.a()

例子2:

var o = {
    user:"追梦子",
    fn:function(){
        console.log(this.user);  //追梦子
    }
}
o.fn();

这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
o.b.fn();

这里同样也是对象o点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

情况3:如果一个函数中有this,**这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,**例子3可以证明,如果不相信,那么接下来我们继续看几个例子。

var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

还有一种比较特殊的情况,例子4:

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子4中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子3是不一样的,例子3是直接执行了fn。

构造函数版this:

function Fn(){
    this.user = "追梦子";
}
var a = new Fn();
console.log(a.user); //追梦子

这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,为什么我说a是对象,因为用了new关键字就是创建一个对象实例,理解这句话可以想想我们的例子3,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

更新一个小问题当this碰到return时

function fn()  
{  
    this.user = '追梦子';  
    return {};  
}
var a = new fn;  
console.log(a.user); //undefined

再看一个

function fn()  
{  
    this.user = '追梦子';  
    return function(){};
}
var a = new fn;  
console.log(a.user); //undefined

再来

function fn()  
{  
    this.user = '追梦子';  
    return 1;
}
var a = new fn;  
console.log(a.user); //追梦子
function fn()  
{  
    this.user = '追梦子';  
    return undefined;
}
var a = new fn;  
console.log(a.user); //追梦子

什么意思呢?

如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

function fn()  
{  
    this.user = '追梦子';  
    return undefined;
}
var a = new fn;  
console.log(a); //fn {user: "追梦子"}

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

function fn()  
{  
    this.user = '追梦子';  
    return null;
}
var a = new fn;  
console.log(a.user); //追梦子

相关面试题

let n=1
function A() { this.n = 0; } 
A.prototype.callMe = function () { console.log(this);  }; 
let a = new A(); 
document.addEventListener("click", a.callMe);  //undefined 这里调用callMe的实际是document,加监听器时没有调用函数
document.addEventListener("click", () => { a.callMe(); });  //0 
document.addEventListener("click", function () { a.callMe(); });  //0 

35、从URL输入到页面展现发生了什么?

1、域名解析。通过域名查找IP地址。可以从浏览器缓存,操作系统缓存,路由缓存,ISP的DNS服务器,根服务器中进行查找。
2、tcp三次握手
3、发送HTTP请求
4、服务器处理请求并返回HTTP报文
5、浏览器解析渲染页面。解析HTML生成DOM树,解析CSS生成CSSOM,结合DOM树和CSSOM生成Render树,重绘,重排。
6、tcp四次挥手

36、JS垃圾回收

标记清除

js中最常用的垃圾收集方式是标记清楚。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。而当变量离开环境时,则将其标记为“离开环境”。

可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生变化。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存除工作,销毁那些带标记的值并回收他们所占用的内存空间。

引用计数(不常见)

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1.相反,如果包含对这个值引用的变量又取的了另一个值,则这个值的引用次数减1.当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

用引用计数法会存在内存泄露,下面来看原因:

function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
  • 设置undefined不会被垃圾回收,设置null会

什么是?

1作用:声明文档的解析类型(document.compatMode),避免浏览器的怪异模式。

document.compatMode:
BackCompat:怪异模式,浏览器使用自己的怪异模式解析渲染页面。
CSS1Compat:标准模式,浏览器使用W3C的标准解析渲染页面。

BackCompat:标准兼容模式关闭。浏览器客户区宽度是document.body.clientWidth;CSS1Compat:标准兼容模式开启。 浏览器客户区宽度是document.documentElement.clientWidth。

​ 这个属性会被浏览器识别并使用,但是如果你的页面没有DOCTYPE的声明,那么compatMode默认就是BackCompat,

浏览器按照自己的方式解析渲染页面,那么,在不同的浏览器就会显示不同的样式。

如果你的页面添加了那么,那么就等同于开启了标准模式,那么浏览器就得老老实实的按照W3C的

标准解析渲染页面,这样一来,你的页面在所有的浏览器里显示的就都是一个样子了。

这就是的作用。

2 使用:

2.1 使用也很简单,就是在你的html页面的第一行添加""一行代码就可以了

2.2 jsp的话,添加在<%@ page %>的下一行。

2.3 不用区分大小写

37、盒模型

IE盒模型

定义的width=content实际宽度+padding+border

可以通过box-sizing属性设置为border-box使用这种IE盒模型

W3C标准盒模型

盒模型的总宽度为:margin+padding+border+定义的width

此处定义width,就是实际的content的宽度。box-sizing的默认属性content-box,即设置为W3C标准盒模型;

IE盒模型和W3C标准盒模型的兼容性

对于盒模型的优先如何选用?前面有提到,除了一些非常特殊的场合之外,我们很少使用box-sizing:border-box去调用IE盒模型,而是默认设置box-sizing:content-box使用W3C标准盒模型,这样可以避免多个浏览器对同一页面的不兼容。

38、跨域

什么是跨域?

跨域是指从一个域名去请求另一个域名的资源,严格来说,只要域名,协议,端口任何一个不同,就视为跨域。

同源策略限制的具体表现是什么?

(1)coikie、LocalStorage和Index DB 无法读取
(2)Dom 无法获得
(3)AJAX请求不能发送

JS中自带跨域技能的标签是什么 ?

JS中有两个神奇的标签,从出生起就自带跨域技能,就是和

跨域解决方法

【1】设置document.domain解决无法读取非同源网页的 Cookie问题

因为浏览器是通过document.domain属性来检查两个页面是否同源,因此只要通过设置相同的document.domain,两个页面就可以共享Cookie(此方案仅限主域相同,子域不同的跨域应用场景。)

【2】跨文档通信 API:window.postMessage()

调用postMessage方法实现父窗口http://test1.com向子窗口http://test2.com发消息(子窗口同样可以通过该方法发送消息给父窗口)

它可用于解决以下方面的问题:

页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递

// 父窗口打开一个子窗口
var openWindow = window.open('http://test2.com', 'title');
 
// 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url)
openWindow.postMessage('Nice to meet you!', 'http://test2.com');

调用message事件,监听对方发送的消息

// 监听 message 消息
window.addEventListener('message', function (e) {
  console.log(e.source); // e.source 发送消息的窗口
  console.log(e.origin); // e.origin 消息发向的网址
  console.log(e.data);   // e.data   发送的消息
},false);
【3】JSONP

JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。

核心思想:网页通过添加一个

①原生实现:

<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
 
// 处理服务器返回回调函数的数据
<script type="text/javascript">
    function dosomething(res){
        // 处理获得的数据
        console.log(res.data)
    }
</script>

② jQuery ajax:

$.ajax({
    url: 'http://www.test.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "handleCallback",    // 自定义回调函数名
    data: {}
});

③ Vue.js:

this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

【4】CORS

CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。

1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin

2、带cookie跨域请求:前后端都需要进行设置

【前端设置】根据xhr.withCredentials字段判断是否带有cookie

①原生ajax

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
 
// 前端设置是否带cookie
xhr.withCredentials = true;
 
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
 
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
};

② jQuery ajax

$.ajax({
   url: 'http://www.test.com:8080/login',
   type: 'get',
   data: {},
   xhrFields: {
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
});

39、requestAnimationFrame和setTimeout()作动画的区别

动画原理

根据上面的原理我们知道,你眼前所看到图像正在以每秒60次的频率刷新,由于刷新频率很高,因此你感觉不到它在刷新。而动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢?

刷新频率为60Hz的屏幕每16.7ms刷新一次,我们在屏幕每次刷新前,将图像的位置向左移动一个像素,即1px。这样一来,屏幕每次刷出来的图像位置都比前一个要差1px,因此你会看到图像在移动;由于我们人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此你才会看到图像在流畅的移动,这就是视觉效果上形成的动画。

setTimeout

理解了上面的概念以后,我们不难发现,setTimeout 其实就是通过设置一个间隔时间来不断的改变图像的位置,从而达到动画效果的。但我们会发现,利用seTimeout实现的动画在某些低端机上会出现卡顿、抖动的现象。 这种现象的产生有两个原因:

  • setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。
  • 刷新频率受屏幕分辨率屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
requestAnimationFrame

window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。

与setTimeout相比,requestAnimationFrame最大的优势是**由系统来决定回调函数的执行时机。**具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。

这个API的调用很简单,如下所示:

var progress = 0;
//回调函数
function render() {
    progress += 1; //修改图像的位置
    if (progress < 100) {
           //在动画没有结束前,递归渲染
           window.requestAnimationFrame(render);
    }
}
//第一帧渲染
window.requestAnimationFrame(render);

除此之外,requestAnimationFrame还有以下两个优势:

  • CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。

  • 函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。

40、JS 中对变量类型的五种判断方法

方法一:使用typeof检测

当需要变量是否是number,string,boolean

,function,undefined,json类型时,可以使用typeof进行判断;其他变量是判断不出类型的,包括null。

typeof是区分不出array和json类型的,因为使用typeof这个变量时,array和json类型输出的都是object

方法二:使用instance检测

instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型

方法三:使用constructor检测

constructor本来是原型对象上的属性,指向构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法,就去原型链上寻找,因此,实例对象也是能使用constructor属性的

因为undefined和null没有constructor属性,所以不能使用constructor判断

方法四:使用Object.prototype.toString.call

Object.prototype.toString.call(变量)输出的是一个字符串,字符串里有一个数组,第一个参数是Object,第二个参数就是这个变量的类型,而且,所有变量的类型都检测出来了,我们只需要取出第二个参数即可。或者可以使用Object.prototype.toString.call(arr)=="object Array"来检测变量arr是不是数组。

41、对promise中resolve()的理解

可以这样理解,在新建 promise 的时候就传入了两个参数

这两个参数用来标记 promise的状态的,这两个参数是两个方法,并且这两个参数可以随意命名,我这里的使用的是omg 也不影响使用

用于表示 promise 的状态

到执行到 resolve()这个方法的时候,就改变promise的状态为

fullfiled ,当状态为 fuulfiled的时候就可以执行.then()

当执行到 reject() 这个方法的时候,就改变 promise 的状态为

reject,当 promise 为reject 就可以.catch() 这个promise了

然后这两个方法可以带上参数,用于.then() 或者 .catch() 中使用。

所以这两个方法不是替代,或者是执行什么,他们的作用就是 用于改变

promise 的状态。

然后,因为状态改变了,所以才可以执行相应的 .then() 和 .catch()操作。

42、DNS请求顺序

当一个用户在地址栏输入www.taobao.com时,DNS解析有大致十个过程,如下:

  1. 浏览器先检查自身缓存中有没有被解析过的这个域名对应的ip地址,如果有,解析结束。同时域名被缓存的时间也可通过TTL属性来设置。

  2. 如果浏览器缓存中没有(专业点叫还没命中),浏览器会检查操作系统缓存中有没有对应的已解析过的结果。而操作系统也有一个域名解析的过程。在windows中可通过c盘里一个叫hosts的文件来设置,如果你在这里指定了一个域名对应的ip地址,那浏览器会首先使用这个ip地址。

但是这种操作系统级别的域名解析规程也被很多黑客利用,通过修改你的hosts文件里的内容把特定的域名解析到他指定的ip地址上,造成所谓的域名劫持。所以在windows7中将hosts文件设置成了readonly,防止被恶意篡改。

  1. 如果至此还没有命中域名,才会真正的请求本地域名服务器(LDNS)来解析这个域名,这台服务器一般在你的城市的某个角落,距离你不会很远,并且这台服务器的性能都很好,一般都会缓存域名解析结果,大约80%的域名解析到这里就完成了。

  2. 如果LDNS仍然没有命中,就直接跳到Root Server 域名服务器请求解析

  3. 根域名服务器返回给LDNS一个所查询域的主域名服务器(gTLD Server,国际顶尖域名服务器,如.com .cn .org等)地址

  4. 此时LDNS再发送请求给上一步返回的gTLD

  5. 接受请求的gTLD查找并返回这个域名对应的Name Server的地址,这个Name Server就是网站注册的域名服务器

  6. Name Server根据映射关系表找到目标ip,返回给LDNS

  7. LDNS缓存这个域名和对应的ip

  8. LDNS把解析的结果返回给用户,用户根据TTL值缓存到本地系统缓存中,域名解析过程至此结束

43、document.write和innerHTML的区别

document.write只能重绘整个页面

innerHTML可以重绘页面的一部分。

44、HTMLCollection vs. NodeList

HTMLCollectionNodeList都是DOM节点的集合,两者都属于Collections范畴,两者的区别在于:

方法略有差异HTMLCollectionNodeList多了一个namedItem方法,其他方法保持一致

包含节点类型不同NodeList可以包含任何节点类型,HTMLCollection只包含元素节点(ElementNode)

当返回多个节点(如:getElementByTagName)或者得到所有子元素(如:element.childNodes)时,Collections就会出现,这时候就有可能返回HTMLCollection或者NodeList

1、HTMLCollection

HTMLCollection是以节点为元素的列表,可以凭借索引、节点名称、节点属性来对独立的节点进行访问。HTML DOM中的Collections是实时变动的,当原始文件变化,Collections也会随之发生变化。

  • 属性:length(返回的是列表的长度)

  • 方法1:item(通过序号索引来获取节点,参数是索引值,超过索引值返回null)

    方法2:namedItem(用名字来返回一个节点,首先搜寻是否有匹配的id属性,如果没有就寻找是否有匹配的name属性,如果都没有,返回null)

2、NodeList

NodeList返回节点的有序集合,DOM中的NodeList也是实时变动的

  • 属性:length(列表中节点的数量)

  • 方法:item(返回集合中的元素,如果超过范围返回null)

HTMLCollectionNodeList的共同点显而易见:

  1. 都是类数组对象,都有length属性
  2. 都有共同的方法:item,可以通过item(index)或者item(id)来访问返回结果中的元素
  3. 都是实时变动的(live),document上的更改会反映到相关对象上(例外:document.querySelectorAll返回的NodeList不是实时的)

HTMLCollectionNodeList的区别是:

  1. NodeList可以包含任何节点类型,HTMLCollection只包含元素节点(elementNode),elementNode就是HTML中的标签
  2. HTMLCollectionNodeList多一项方法:namedItem,可以通过传递id或name属性来获取节点信息

45、Node vs Element

简单的说就是Node是一个基类,DOM中的ElementTextComment都继承于它。
换句话说,ElementTextComment是三种特殊的Node,它们分别叫做ELEMENT_NODE,
TEXT_NODECOMMENT_NODE

*所以我们平时使用的html上的元素,即Element,是类型为ELEMENT_NODENode。*

46、=区别(两个等号与三个等号)

1、对于string,number等基础类型,=是有区别的
1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等
2)同类型比较,直接进行“值”比较,两者结果一样

2、对于Array,Object等高级类型,=是没有区别的
进行“指针地址”比较

3、基础类型与高级类型,=是有区别的
1)对于==,将高级转化为基础类型,进行“值”比较
2)因为类型不同,===结果为false

47、使一个函数只调用一次

export function once (fn) {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}

48、生成/获取缓存了的函数

export function cached (fn) {
  const cache = Object.create(null)
  return (function cachedFn (str) {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  })
}

49、单页应用和多页应用

多页应用

每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。

img

为什么多页应用的首屏时间快?

首屏时间叫做页面首个屏幕的内容展现的时间,当我们访问页面的时候,服务器返回一个html,页面就会展示出来,这个过程只经历了一个HTTP请求,所以页面展示的速度非常快。

为什么搜索引擎优化效果好(SEO)?

搜索引擎在做网页排名的时候,要根据网页内容才能给网页权重,来进行网页的排名。搜索引擎是可以识别html内容的,而我们每个页面所有的内容都放在Html中,所以这种多页应用,seo排名效果好。

但是它也有缺点,就是切换慢

因为每次跳转都需要发出一个http请求,如果网络比较慢,在页面之间来回跳转时,就会发现明显的卡顿。

单页应用

第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了。
原理是:JS会感知到url的变化,通过这一点,可以用js动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求html文件了。

img

我是单页应用

为什么页面切换快?

页面每次切换跳转时,并不需要做html文件的请求,这样就节约了很多http发送时延,我们在切换页面的时候速度很快。

缺点:首屏时间慢,SEO差

单页应用的首屏时间慢,首屏时需要请求一次html,同时还要发送一次js请求,两次请求回来了,首屏才会展示出来。相对于多页应用,首屏时间慢。
SEO效果差,因为搜索引擎只认识html里的内容,不认识js的内容,而单页应用的内容都是靠js渲染生成出来的,搜索引擎不识别这部分内容,也就不会给一个好的排名,会导致单页应用做出来的网页在百度和谷歌上的排名差。

有这些缺点,为什么还要使用Vue呢?

Vue还提供了一些其它的技术来解决这些缺点,比如说服务器端渲染技术(我是SSR),通过这些技术可以完美解决这些缺点,解决完这些问题,实际上单页面应用对于前端来说是非常完美的页面开发解决方案。

50、JS模块化

模块化进化史

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、commonJS规范

基本语法:

//暴露模块
module.exports=value
exports.xxx=value
//引入模块
require(xx)

在服务器端node.js可以直接使用

但在浏览器端必须用webpack/Browserify打包编译

2、AMD规范(Asynchronous Module Definition(异步模块定义))

专门用于浏览器端, 模块的加载是异步的

基本语法:

//定义没有依赖的模块
define(function(){
    return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
	return 模块
})
//导入使用模块
require(['module1', 'module2'], function(m1, m2){
	使用m1/m2
})

用require.js实现

export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;//分别暴露
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;//整体暴露
export {firstName, lastName, year};

这种情况下import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

import { lastName as surname } from './profile.js';

使用export default命令,可以为模块指定默认输出。

export default function () {//默认暴露
  console.log('foo');
}

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

import customName from './export-default';

要想在浏览器端实现,要先用Babel将ES6编译成ES5的CommomJS规范的代码,然后再用Browserify编译

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值