JS面试题

作者: 天马行空_eaa7 | 来源:发表于2021-05-31 09:05 被阅读0次

    1.ES5和ES6的关于构造函数的区别

    Es6中的class语法就是Es5中构造函数的另一种写法,一种更高级的写法(语法糖),class语法的底层还是es5中的构造函数,只是把构造函数进行了一次封装而已。

    Es6出现的目的为了让我们的让对象原型的写法更加清晰、更像面向对象编程让JavaScript更加的符合通用编程规范,即大部分语言对于类和实例的写法。</pre>

    · ES5的构造函数Person,对应ES6的Person类的构造方法constructor

    · ES5的Person原型上的方法对应Es6的除了constructor以外的其他方法。

    图片2.png

    2.JS的类型转换

    类型转换分为:显示类型转换(强制类型转换);隐式类型转换(自动类型转换)

    • 显示类型转换:

    parseInt();
    parseFloat();
    String(123)---将数字转换为字符
    Number(‘3.14’)---将字符转换为数字
    Number(null)---->0
    Number(其他)--->NaN
    Number(false|true)--->0 | 1;

    • 自动类型转换:

    5+null-->5, null转为0;
    “5”+null-->“5null”,null 转为“null”;
    “5”+1--> ‘51’,1转为‘1’;
    “5”-1--> 4,”5”转为5;

    3.找出字符串中出现字符最多的字符

    使用reduce

    reduce使用方法:

    array.reduce( function(total,currentValue{xxx},initiaValue)

    • total :计算结果初始值或者每次计算后的结果
    • currentValue:当前item
    • initiaValue:返回结果的初始值
    • function:对元素的操作方法</pre>

    let str='aabbccc';

    let arr={}

    arr=str.split('').reduce((arr,item)=>{

    return arr

    },{})

    console.log(arr) // {a :2, b:2 ,c:3}

    3.变量提升(hoisting)

    注意

    • js只有声明的变量会提升,变量的初始化 不会提升

    • ES6的let也不会提升,至于为什么?推荐这篇文章 let为什么要先声明后使用

      js中,函数及变量的声明都将被提升到函数的最顶部。变量可以先使用后声明。

    以下两个例子将获得相同的结果:

    例子一:

    x=5;//初始化x

    elem=document.getElementById('demo');

    elem.innerHTML=x;

    var x;//定义x

    )

    例子二:

    var x;//定义x

    x=5;//初始化x

    elem=document.getElementById('demo');

    elem.innerHTML=x;

    js只有声明变量会提升,初始化不会提升

    实例一

    var x = 5; // 初始化 x var y = 7; // 初始化 y elem = document.getElementById("demo"); // 查找元素 elem.innerHTML = x + " " + y; // 显示 x 和 y

    实例二:

    var x = 5; // 初始化 x elem = document.getElementById("demo"); // 查找元素 elem.innerHTML = x + " " + y; // 显示 x 和 y var y = 7; // 初始化 y

    实例二 的y输出了undefined,这是因为声明变量y提升了,但是初始化这个步骤不会提升,所以在于x拼接时,y变量是一个未初始化的变量

    4.call()方法和apply()方法的区别

    4.1 call方法
    • 语法

      A.call(B,x,y)

    • call方法

      定义:调用一个对象的一个方法,用另一个对象替换当前的对象

      说明: call方法可以用来代替一个对象调用一个方法.call 方法可以将一个函数的对象上下文从初始化改为新的对象,也就是用B代替A,最终用B对象去执行A对象的A对象的方法。

      提示:由于this是指向调用者,而不是声明者,故通过call或者apply方法(通过改变调用者)可以改变this指向;

    • 例子一

      var person = {
      fullName: function() {
      return this.firstName + " " + this.lastName;
      }
      }
      var person1 = {
      firstName:"Bill",
      lastName: "Gates",
      }
      var person2 = {
      firstName:"Steve",
      lastName: "Jobs",
      }
      调用person的fullname方法,并用于person2
      person.fullName.call(person1); // 将返回 "Bill Gates"</pre>

    • 例子二(带参数的call方法)

      var person = {
      fullName: function(city, country) {
      return this.firstName + " " + this.lastName + "," + city + "," + country;
      }
      }
      var person1 = {
      firstName:"Bill",
      lastName: "Gates"
      }
      person.fullName.call(person1, "Seattle", "USA"); </pre>

      4.2 apply方法

      不同之处是:

      call() 方法分别接受参数。

      apply() 方法接受数组形式的参数。

      • obj.function.apply(obj2,[xxx,xxx])

        如果要使用数组而不是参数列表,则 apply() 方法非常方便。

      无参例子

      var person = {
      fullName: function() {
      return this.firstName + " " + this.lastName;
      }
      }
      var person1 = {
      firstName: "Bill",
      lastName: "Gates",
      }
      person.fullName.apply(person1); // 将返回 "Bill Gates"</pre>

      有参例子

      var person = {
      fullName: function(city, country) {
      return this.firstName + " " + this.lastName + "," + city + "," + country;
      } }
      var person1 = { firstName:"Bill", lastName: "Gates" }
      var x = person.fullName.apply(person1, ["Seatle", "USA"]); document.getElementById("demo").innerHTML = x;
      //Bill Gates,Seatle,USA

      apply的巧妙用法

      可以将一个数组默认的转换为一个参数列表([param1,param2,param3] 转换为 param1,param2,param3) 这个如果让我们用程序来实现将数组的每一个项,来装换为参数的列表,可能都得费一会功夫,借助apply的这点特性,所以就有了以下高效率的方法:

       1\. 求一个数组arr的最大和最小值
         Math.max.apply(null,arr)
         Math.min.apply(null,arr)
         因为Math.max 参数里面不支持Math.max([param1,param2]) 也就是数组
        
        但是它支持Math.max(param1,param2,param3…),所以可以根据刚才apply的那个特点来解决 var max=Math.max.apply(null,array),这样轻易的可以得到一个数组中最大的一项(apply会将一个数组装换为一个参数接一个参数的传递给方法)
        这块在调用的时候第一个参数给了一个null,这个是因为没有对象去调用这个方法,我只需要用这个方法帮我运算,得到返回的结果就行,.所以直接传递了一个null过去
    
        2\. 合并两个数组
        同样push方法没有提供push一个数组,但是它提供了push(param1,param,…paramN) 所以同样也可以通过apply来装换一下这个数组,即:
         var arr1=new Array("1","2","3");
         var arr2=new Array("4","5","6");
         arr1.push.apply(arr1,arr2) 
         // 注意,参数不能放反了,arr1在前
         // arr1.push.apply(arr2,arr1) ["1","2","3"]
        也可以这样理解,arr1调用了push方法,参数是通过apply将数组装换为参数列表的集合.
      
    

    什么是类数组对象

    而对于一个普通的对象来说,如果它的所有property名均为正整数,同事也有相应的length属性,那么虽然该对象并不是由Array构造函数所创建的,它依然呈现出数组的行为,在这种情况下,这些对象被称为"类数组对象"
    如:
    var o ={0:42,1:52,2:63,length:3}
    与普通对象不同的是,类数组对象拥有一个特性:可以在类数组对象上应用数组的操作方法。
    但是,不要把类型化数组与正常数组混淆,因为在类型数组上调用 Array.isArray() 会返回false。此外,并不是所有可用于正常数组的方法都能被类型化数组所支持(如 push 和 pop)。

    5.js中的this

    注意:ES6中的箭头函数中的this是函数定义的对象,而不是使用函数的对象(和正常相反)

    正常的this指向是根据this被调用的对象,而不是被怎么定义的

    先看一段代码;

    var fullname = 'John Doe';

    var obj = {

    fullname: 'Colin Ihrig',

    prop: {

    fullname: 'Aurelio De Rosa',

    getFullname: function() {

    return this.fullname;

    }

    }

    };

    console.log(obj.prop.getFullname());

    var test = obj.prop.getFullname;

    console.log(test());

    答案:

    这段代码打印结果是:Aurelio De Rosa 和 John Doe 。原因是,JavaScript中关键字this所引用的是函数上下文,this的指向,取决于this是如何被调用的,而不是如何被定义的。

    在第一个console.log(),getFullname()是作为obj.prop对象的函数被调用。因此,当前的上下文指代后者,并且函数返回这个对象的fullname属性。相反,当getFullname()被赋值给test变量时,当前的上下文是全局对象window,这是因为test被隐式地作为全局对象的属性。基于这一点,函数返回window的fullname,在本例中即为第一行代码设置的。

    如何让最后一个console.log()打印输出Aurelio De Rosa?

    答案

    可以通过call()或者apply()方法强制转换上下文环境。

    console.log(test.call(obj.prop)) //将this改为obj.prop

    6.js立即执行函数

    • 形式

      1. (function foo(){console.log('hello');}() ),括号包括了全部

      2. (function foo(){console.log('hello');}) (),括号只包含了函数

    • 作用

      1. 不必为函数命名,避免了污染全局变量

      2. 立即执行函数内部形成了一个单独的作用域,可以封装一下外部无法读取的 私有变量

      3. 封装变量
        总结:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。

    比如上面的代码,如果没有被包裹在立即执行函数中,那么临时变量todaydom,days,today,year,month,date,day,msg都将成为全局变量(初始化代码遗留的产物)。用立即执行函数之后,这些变量都不会在全局变量中存在,以后也不会其他地方使用,有效的避免了污染全局变量。

    • 例子

      //立即执行函数可以形成独立的作用域

      for(var i=0;i<liList.length;i++) {
      (function(ii) {
      liList[ii].onclick=function(){
      console.log(ii);
      }
      })(i)
      };//这里用立即执行函数给每个i创建独立函数解决for循环问题,这里把‘i’传给了“ii”

      //现在ES6可以用let 代替i 解决该问题

      改变变量i的作用域,把全局变量i以参数的形式传递到立即执行函数中,在立即执行函数中定义变量i的形参变量j,变量j就是在立即执行函数的作用域中。(给每个li创建了一个作用域块,点击的时候寻找自由变量j,在立即执行块中找到)

      当然,立即执行函数也可以返回数组,对象等类型的数据

      题目:

      var f = function() {
      var c = "ccc";
      return {
      a: function() {
      return c;
      },
      b: function(d) {
      c = d;
      }
      }
      }()

      console.warn(f.a()) // ccc
      console.warn(f.c) // undefined
      console.warn(f.b("www")) // undefined
      console.warn(f.a()) // www</pre>

      这题主要考查的是执行上下文中的作用域链。我们要注意到函数表达式后的那个函数执行符——(),它是个立即执行函数,也就是说f是个包含a、b属性的对象。

      console.warn(f.a()) </pre>

      当a()的执行上下文被激活时,作用域和变量对象被确定,c是个自由变量,需要在作用域链中向上查找,然受在父级作用域中找到,所以输出“ccc”。

      console.warn(f.c)</pre>

      这个就不用说啦,f中没有c这个属性,取不到当然返回undefined

      console.warn(f.b("www"))</pre>

      同第一行一样,修改的是父级作用域中的c,但由于没有返回值所以输出的是undefined。

      函数会顺着作用域链查找当前执行环境不存在的变量,对象则从原型链上查找!!!

    7.什么是闭包?

    我们知道在js的作用域环境中访问变量的权力是 由内向外的;也就是说外层作用域无法获取到内层作用域下的变量。同意在不同的函数作用域中也是不能相互访问多彼此变量的。*如果我们想在一个函数内部也有权访问另一个函数内部的变量怎么办?*****闭包就是解决这个问题的。

    闭包的本质就是在一个函数内部创建另一个函数。常见的就是 返回一个函数

    js闭包.png

    解释: 上述例子两次调用 fn1(),但是由于函数fn1()用完即销毁,因此,调用一次fn1(),里面的n就会创建一次,但是这个闭包应用的变量num 却不会被销毁。所以结果是如上所示。

    • 首先我们知道闭包的3个特性
      1. 函数内部包含函数
      2. 函数内部可以引用函数外部的变量
      3. 被引用的变量会长期的存在内存中,不会被垃圾回收机制回收

    • 深入理解


      闭包.PNG

    8.js的垃圾回收机制

    像 C 这样的编程语言,具有低级内存管理原语,如 malloc()和 free()。开发人员使用这些原语显式地对操作系统的内存进行分配和释放。</pre>

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

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

      1. 分配内存---内存是由操作系统分配的,它允许您的程序使用它。在低级语言(例如 C 语言)中,这是一个开发人员需要自己处理的显式执行的操作。然而,在高级语言中,系统会自动为你分配内在。

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

      3. 释放内存---释放所有不再使用的内存,使之成为自由内存,并可以被重利用。与分配内存操作一样,这一操作在低级语言中也是需要显式地执行。

      • 四种常见的内存泄漏

        1. js闭包
    1. 意外全局变量

      JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window

      function foo(arg) {

      bar = "this is a hidden global variable";

      }

      真相是:

         function foo(arg) { 
             window.bar = "this is an explicit global variable"; 
         }
    

    函数 foo 内部忘记使用 var ,意外创建了一个全局变量。此例泄露了一个简单的字符串,无伤大雅,但是有更糟的情况。

      1. 另一种意外的全局变量可能由this创建:

      function foo() {

      this.variable = "potential accidental global";

      }

      // Foo 调用自己,this 指向了全局对象(window)*

      // 而不是 undefined

      foo();

      在 JavaScript 文件头部加上 'use strict',可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。

    1. 未清除的计时器和回调函数
      计时器:比如setInterval.计时器需要停止了才会被回收
    2. 没有清除的DOM元素引用

    9.js的严格模式( use strict )

    严格模式是一种将更好的错误检查引入代码中的方法。 在使用严格模式时,无法使用隐式声明(变量没有定义变量类型)的变量、将值赋给只读属性或将属性添加到不可扩展的对象等

    • 严格模式的目的

      1. 消除JS 语法的一些不合理的地方
      2. 消除代码运行的一些不安全之处,保证代码运行的安全
      3. 提高编译器效率,增加运行速度
      4. 为未来新版本的JS做好铺垫
    • 如何声明严格模式
      严格模式的声明有两种情况:
      1. 如果在全局上下文(函数的范围之外,文件开头处)中声明严格模式,则程序中的所有代码都处于严格模式。
      2. 如果在*函数中声明*严格模式,则函数中的所有代码都处于严格模式。</pre>

    10.什么是作用域链?

    1. 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象为止,作用域链向下访问是不允许的。

    2.简单的说,作用域就是变量与函数的可访问范围。

    3. 通俗来说,一般情况下,变量取值是到创建这个变量的函数和作用域中去取值的。但是,如果当前作用域中没有查到,就会向上级作用域中查找,直到查到全局作用域,这个查找过程形成的链条就叫作用域链。

    11.JS的数据类型有哪些?

    1. 基本数据类型

      String, boolean, Number, undefined,null,symbol, bigint

      注意:

      NaN 代表 非数字值的特殊值,该属性表示某个值不是数字(Not a Number)

      typeOf的返回值有:

      String,number,boolean,undefined,function,Object,symbol

    2. 引用数据类型
      Object(包含:Array,Function,Date,RegExp)

    3. 对symbol的理解

      这个ES6中新添加的数据类型,他本质上是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值,symbol的特点是唯一性,即使是用同一个变量生成的值也不相等。

    12.如何做到点击节点时响应的?

    <button id="btn" onclick="keke()">点我</button>

    <script type="text/javascript">

    let btn = document.getElementById("btn");

    // 第一种 绑定点击事件

    btn.onclick = function(){ alert("这是第一种点击方式"); }

    // 第二种 监听点击事件

    btn.addEventListener('click', function(){ alert("这是第二种点击方式"); })

    // 第三种 通过方法响应点击事件(标签中的响应事件)

    function keke(){ alert("这是第三种点击方式"); }

    13.原生JS的window.onload与Jquery的$(document).ready(function(){})的区别

    window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行。
    $(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕。
    

    14.instanceof的作用

    • typeOf的返回值有:string,number,boolean,undefined,function,object,(ES6)symbol
    • js中的数据类型有以下几种:Number Boolean undefined Object Function String Null
    • 基本类型:Number Boolean String undefined null
    • 引用类型:Object Function
      基本类型的数据是存放在栈内存中的,而引用类型的数据是存放在堆内存中的
      在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”。这就需要用到instanceof来检测某个对象是不是另一个对象的实例。看下面的例子:
    function Foo(){}
    
    var f1=new Foo();
    
    console.log(f1 instanceof Foo);//true
    
    console.log(f1 instanceof Object);//true
    

    分析:Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个构造函数,暂时称为B。
    写法:A instanceof B ---> true| false

    Instanceof的判断队则是:沿着A(一般是对象的实例)的proto这条线来找,同时沿着B(一般是一个数据类型))的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。这就很好地解释了上述代码的输出结果啦。

    15.JS 如何去重?

    方法一

    使用ES6的Set方法

    let arr=[1,2,3,1,2,3]

    let ret=[...new Set(arr)]

    方法二

    使用reduce方法

    let arr=[1,2,3,1,2,3]

    let ret=arr.reduce((arr1,item)=>{

    arr1.indexof(item)===-1&&arr1.push(item)

    //arr1.indexof(item)===-1?arr1.push(item):arr

    return arr1

    },[])

    16.JS的事件委托(依靠事件冒泡机制实现)

    事件冒泡

    JS中当触发某些具有冒泡性质的事件是,首先在触发元素寻找是否有相应的注册事件,如果没有再继续向上级父元素寻找是否有相应的注册事件作出相应,这就是事件冒泡。

    2.事件委托

    利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应。这样做的优势有:1.减少DOM操作,提高性能。2.随时可以添加子元素,添加的子元素会自动有相应的处理事件。

    17.DOM事件有哪些阶段?

    捕获(事件)阶段---(寻找)目标阶段--冒泡阶段

    18.同步和异步

    单线程:
    在JS引擎中负责解释和执行JavaScript代码的线程只有一个,一般称它为主线程。但是实际上还存在其他的线程,可以称之为工作线程。JS的单线程能够提高工作效率。JavaScript的主要用途是与用户互动,以及操作DOM,这就决定了它只能是单线程。单线程意味着前一个任务结束,才会执行后一个任务。

    • 同步:
      在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。如果在函数A返回的时候,调用者就能够得到预期的结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。

    • 异步:
      不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。如果在函数A返回的时候,调用者还不能马上得到预期的结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的 ,我们可以简单的理解为,可以改变程序正常执行顺序的操作就叫做异步操作

    • 异步的过程:
      主线程发一起一个异步请求,相应的工作线程接收请求并告知主线程已收到通知(异步函数返回);主线程可以继续执行后面的代码,同时工作线程执行异步任务;工作线程完成工作后,通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)。

    19.Attribute和property的区别

    我们知道Attribute就是DOM节点自带属性,例如我们在HTML中常用的id,class,src,title,alt等。
    而Property则是这个DOM元素作为对象,其附加的属性或者内容,例如childNodes,firstChild等。

    20.cookies,sessionStorage和localStorage的区别

    基本概念

    • cookies
      cookie英文饼干,顾名思义,大小应该非常小,cookie非常小,它的大小限制在4kb左右,是网景公司的前雇员在1993年发明。它的主要用于保存登陆信息,比如你登陆某个网站市场可以看到'记住密码’,这就是通过在cookie中存入一段辨别用户身份的数据来实现的。
    • localStorage
      客户端的本地存储

    • sessionStorage
      sessionStorage与localStorage的接口类似,但保存数据的生命周期与localStorage不同,做过后端的同学都知道Session这个词,翻译过来就是会话。而sessionStorage是前端的一个概念。它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但是页面关闭后,sessionStorage中的数据就会被清空。

    三者的区别

    • 数据的生命周期不同
      cookie一般有服务器生成,可设置失效时间,如果在 浏览器端生成cookie,默认是关闭后失效。
      localStorage
      除非被清除,否则永久保存
      sessionStorage
      仅当前会话有效,关闭当前页面或者浏览器后被清除

    • 存放数据的大小不同
      cookie一般为4KB
      localStorage和sessionStorage一般都是 5MB

    • 与服务器端通信不同
      cookies每次都会携带在HTTP请求头中,如果使用cookie保存数据过多,会带来性能问题
      localStorage和sessionStorage不参与服务器通信,仅仅在客户端(浏览器)中保存

    • 易用性
      Cookie 需要程序员自己来封装,原生的cookie接口不够友好
      localStorage 和 sessionStorage 原生接口可以接受,可以封装来对Object和Array有更好的支持。

    • 应用场景
      因为考虑到每个HTTP请求都会带着Cookie的信息,所以Cookie当然能是精简就精简力,比较常用的一个应用场景就是判断用户是否登陆,针对登陆过的用户服务端就在它登陆时往Cookie中祝福一段加密过的唯一识别单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登陆。曾经还使用Cookie来保存用户在电商网站上的购物车信息,如今有来localStorage,这一切问题变得越来越轻松。

    21.js代码放在< head>中 和放在< body>中的区别

    • 区别一:
      放<head>中的情况:脚本会优先加载,但加载过程中,<body>还没加载完,会使脚本访问不到<body>中的元素。
      放<body>底部:脚本在<body>加载后加载,能够保证脚本有效地访问<body>的元素。
      例外情况:脚本中没有访问<body>元素的情况下,两种加载方式效果一致。

    • 区别二:
      script放在< head>,会阻塞HTML代码的解析和渲染,而放在< body>底部时,不会阻塞HTML代码的解析和渲染。这样用户可以更快的看到页面。

    22.实现js加载延迟的方法

    • 动态创建DOM

    • 在文档的最后加载

    • 给js脚本添加的defer属性

    <script src='xxx.js' defer>

    • 给js脚本添加async属性

    <script src='xxx.js' async>

    1. defer和async的区别
      默认情况下js的脚本执行是同步和阻塞的,但是 <script> 标签有 defer 和 async 属性, 这可以改变脚本的执行方式,这些都是布尔类型了,没有值,只需要出现在 <script> 标签里即可。
      还要注意一点,html5说这些属性只在和src属性联合使用时才有效。
      如果同时指定了两个属性,则会遵从async属性而忽略defer属性。
    • 作用
      defer 属性标注的脚本是延迟脚本,使得浏览器延迟脚本的执行,也就是说,脚本会被异步下载但是不会被执行,直到文档的载入和解析完成,并可以操作,脚本才会被执行。
      async 属性标注的脚本是异步脚本,即异步下载脚本时,不会阻塞文档解析,但是一旦下载完成后,立即执行,阻塞文档解析。

    • 区别
      延迟脚本会按他们在文档里的出现顺序执行
      异步脚本在它们载入后执行,但是不能保证执行顺序。
      现在呢基本上都是在文档的最后写脚本,那么这和 defer 的区别在哪里呢?
      第一点当然是异步下载脚本了,第二点就是 使用async或defer任何一个都意味着在脚本里不能出现 document.write。

    23. 需要发起http请求的操作

    加载css
    外联 :css单独一个文件
    require
    加载图片,有src的都会

    24. 模块化及其优点

    • 模块是什么
      模块时实现一个功能的一组方法。

    • 优点
      避免数据污染
      维护方便
      可分单元测试

    • 缺点
      性能损耗:
      系统分层,调用链长
      模块间通信,模块间发送消息很耗性能

    按下enter到浏览器显示页面发生了什么?

    • 简化
    1. 检查缓存(如果有,则可以不用DNS解析)
    2. DNS解析
    3. TCP连接
    4. 发送http请求
    5. 服务器处理请求并返回报文
    6. 浏览器解析渲染页面
    7. 连接结束
    • 详细过程

    在输入 URL 后,首先需要找到这个 URL 域名的服务器 IP,为了寻找这个 IP,浏览器首先会寻找缓存,查看缓存中是否有记录,缓存的查找记录为:浏览器缓存 ->系统缓存 -> 路由器缓存,缓存中没有则查找系统的 hosts 文件中是否有记录,如果没有则查询 DNS 服务器,得到服务器的 IP 地址后,浏览器根据这个 IP 以及相应的端口号,构造一个 HTTP 请求,这个请求报文会包括这次请求的信息,主要是请求方法、请求说明和请求附带的数据,并将这个 HTTP 请求封装在一个 TCP 包中,这个 TCP 包会依次经过传输层、网络层、数据链路层、物理层到达服务器,服务器解析这个请求来作出响应,返回相应的 HTML 给浏览器,因为 HTML 是一个树形结构,浏览器根据这个 HTML 来构建 DOM 树,在 DOM 树的构建过程中如果遇到 JS 脚本和外部 JS 链接,则会停止构建 DOM 树来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐 JS 代码应该放在 HTML 代码的后面,之后根据外部样式,内部样式,内联样式构建一个 CSS 对象模型树 CSSOM 树,构建完成后和 DOM 树合并为渲染树,这里主要做的是排除非视觉节点,比如 script,meta 标签和排除 display 为 none 的节点,之后进行布局,布局主要是确定各个元素的位置和尺寸,之后是渲染页面,因为 HTML 文件中会含有图片,视频,音频等资源,在解析 DOM 的过程中,遇到这些都会进行并行下载,浏览器对每个域的并行下载数量有一定的限制,一般是 4-6 个,当然在这些所有的请求中我们还需要关注的就是缓存,缓存一般通过 Cache-Control、Last-Modify、Expires 等首部字段控制。 Cache-Control 和 Expires 的区别在于 Cache-Control 使用相对时间,Expires 使用的是基于服务器端的绝对时间,因为存在时差问题,一般采用 Cache-Control,在请求这些有设置了缓存的数据时,会先查看是否过期,如果没有过期则直接使用本地缓存,过期则请求并在服务器校验文件是否修改,如果上一次响应设置了 ETag 值会在这次请求的时候作为 If-None-Match 的值交给服务器校验,如果一致,继续校验 Last-Modified,没有设置 ETag 则直接验证 Last-Modified,再决定是否返回 304.

    创建对象的方法

    • 使用对象字面量的方式
    var Cat  = {};//JSON
     Cat.name="kity";//添加属性并赋值
     Cat.age=2;
     Cat.sayHello=function(){
      alert("hello "+Cat.name+",今年"+Cat["age"]+"岁了");//可以使用“.”的方式访问属性,也可以使用HashMap的方式访问
     }
     Cat.sayHello();//调用对象的(方法)函数
    
    • 使用function来模拟class[无参]
    function Person(){
     
    }
    var personOne=new Person();//定义一个function,如果有new关键字去"实例化",那么该function可以看作是一个类
    personOne.name="dylan";
    personOne.hobby="coding";
    personOne.work=function(){
    alert(personOne.name+" is coding now...");
    }
     
    personOne.work();
    
    • function有参构造函数
    function Pet(name,age,hobby){
       this.name=name;//this作用域:当前对象
       this.age=age;
       this.hobby=hobby;
       this.eat=function(){
          alert("我叫"+this.name+",我喜欢"+this.hobby+",也是个吃货");
       }
    }
    var maidou =new Pet("麦兜",5,"睡觉");//实例化/创建对象
     
     maidou.eat();//调用eat方法(函数)
    
    • 使用object关键字
    var wcDog =new Object();
     wcDog.name="旺财";
     wcDog.age=3;
     wcDog.work=function(){
       alert("我是"+wcDog.name+",汪汪汪......");
     }
     
     wcDog.work();
    
    • 使用原型对象的方式
    function Dog(){
     
     }
     Dog.prototype.name="旺财";
     Dog.prototype.eat=function(){
     alert(this.name+"是个吃货");
     }
     var wangcai =new Dog();
     wangcai.eat();
    
    • 使用class (ES6)

    相关文章

      网友评论

        本文标题:JS面试题

        本文链接:https://www.haomeiwen.com/subject/oakzjltx.html