javaScript基础(三)

317 阅读6分钟

· 同步和异步的区别?

  同步:指的是主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

  异步:不进入主线程,而进入‘任务队列’的任务,只有等主线程任务执行完毕,‘任务队列’开始通知主线程,请求执行任务,该任务才会进入主线程执行。

  异步实现的几种方式:

      - 回调函数callback。比如:发起ajax请求后,会回调success、error函数,对数据进行处理。发起请求后进程还是会正常运行,不必等到请求成功后才会执行后面的进程。

      - 事件驱动。为页面默认元素绑定事件。

      - promise模式。通常实现一种then方法,用来注册状态发生改变时作为对应的回调函数。

· 如何解决跨域问题?

  1. 在发起请求时,给请求头部(header)添加Access-Control-Allow-Origin属性,属性值是"*",如("Access-Control-Allow-Origin", "*"。

  2. JSONP方式。如下:

   $.ajax({
       url: url,
       type: 'get',
       dataType: 'jsonp', // 数据类型为jsonp
       jsonp: 'backFunction', // 服务器端用于接收callBack调用的函数名的参数
       success: function () {},
       error: function () {}
   })

  3. nginx反向代理。

· 页面编码和被请求的资源编码如果不一致如何处理?

  对于ajax请求传递的参数,如果是get请求,在有些浏览器会乱码。因为不同浏览器对参数编码的处理方式不一样。所以对于get请求的参数需要使用encodeURIComponent函数对参数进行编码处理。对于post请求则不需要进行编码。

· 模块化开发怎么做?

  模块化:理论上是一个功能对应一个模块,互不影响,在需要的时候载入。

  比如Vue.js里的模块/组件:就是将一段js、html、css组合在一起,形成有一定功能的代码片段。

· AMD(Modules/Asynchronous-Definition)、CMD(Common Module Definition)规范区别?

  1. AMD是RequireJS在推广过程中对模块定义的规范化产出;CMD是SeaJS在推广过程中对模块地定义的规范化产出。

  2. 对于依赖的模块,AMD是提前运行,CMD是延迟运行。不过RequireJS在2.0开始,也可以使用延迟加载。

· 谈一谈你对ECMAScript6的了解?

  是JavaScript语言的下一代标准。ECMAScript规定了JavaScript的规格。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

· ECMAScript6 怎么写class,为什么会出现class这种东西?

    /** ECMAScript6使用class关键词:如下 */
    class Study {
        constructor(x, y) {
            this.x = x
            this.y = y
        }
        fun () {
            console.log(this.x)
        }
    }
    
    /** class关键词的出现:由于生成实例的传统方法是通过构造函数,这种写法与传统的面向对象差异大,学习起来会比较困难。而ES6引入class(类),class可以看作只是一个语法糖,通过class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。 */

· 异步加载的方式有哪些?

  1. 通过创建DOM来加载script,如下:

(function () {
    var scrptDom = document.createElement('script')
    scriptDom.type = 'text/javascript'
    script.async = true
    script.src = 'http://www.url.com'
    var x = document.getElementsByTagName('head')[0]
    x.insertBefore(script, x.firstChild)
})()
/** 不过这种加载方式执行完之前还是会阻止onload事件的触发,而现在也有很多页面的代码都在onload时还执行额外的渲染工作,所以还是会阻塞部分页面的初始化处理 */

  2. onload,如下:

(function () {
    if (window.attachEvent) {
        window.attachEvent('load', asyncload)
    } else {
        window.addEventListener('load', asyncload)
    }
    var asyncload = function () {
        var scrptDom = document.createElement('script')
        scriptDom.type = 'text/javascript'
        script.async = true
        script.src = 'http://www.url.com'
        var x = document.getElementsByTagName('head')[0]
        x.insertBefore(script, x.firstChild)
    }
})()
/** 这种方法把插入script的方法放到一个函数里,然后放到onload函数中执行,这样就解决了onload阻塞的问题。(reload:是dom加载完成后执行,onload:是所有内容(包括图片、css、js等文件)都加载完成后执行) */

  3. jQuery的$(document).ready(function() {})相当于$(function (){}),如下:

$(document).ready(function () {
    console.log(11)
})
/** 或 */
$(function () {
    console.log(11)
})

  4. script标签的async='async'属性,如下:

/**
    <script type="text/javascript" src="xxx.js" async="async"></script>
    注意:1. 此方法不能保证脚本按顺序执行。 2. 它们将在onload事件之前完成
*/

  5. script标签的defer='defer'属性,如下:

/**
    <script type="text/javascript" src="xxx.js" defer="defer"></script>
    注意:1. 此方法保证脚本按顺序执行。 
          2. 它们将在onreload事件完成后。
*/

· documen.write 和 innerHTML的区别?

  documen.write是直接写入页面的内容流。每次写完关闭之后再重新调用该函数,会导致重绘整个页面。

  documen.innerHTML是写入某个DOM节点的内容,重新调用该函数,是不会导致重绘整个页面。实现局部刷新的效果。

· DOM操作——怎样添加、移除、替换、插入、复制和查找节点?

    // dom节点的添加
    var addDom = document.createElement('div')
    addDom.setAttribute('class', 'add-dom')
    addDom.innerHTML = 'DOM节点'
    document.getElementsByTagName('body')[0].append(addDom)

    // DOM节点移除
    var removeDom = document.getElementById('remove-dom')
    removeDom.addEventListener('click', function () {
        removeDom.remove()
    })

    // 替换DOM
    var moveDom = document.getElementById('move-dom')
    moveDom.addEventListener('click', function () {
        var newDom = document.getElementsByClassName('replace-dom')[0]
        console.log(document.getElementsByTagName('body')[0])
        document.getElementsByTagName('body')[0].replaceChild(newDom, moveDom)
    })

    // 插入节点
    var insertDom = document.getElementById('insert-dom')
    insertDom.addEventListener('click', function () {
        var newDom = document.createElement('div')
        newDom.innerHTML = '插入的新节点'
        document.getElementsByTagName('body')[0].insertBefore(newDom, insertDom.nextSibling) // 插在后面
        document.getElementsByTagName('body')[0].insertBefore(newDom, insertDom) // 插在前面
    })
    
    // 复制节点
    var copyDom = document.getElementsByClassName('copy-dom')[0]
    console.log(copyDom)
    copyClick(copyDom)
    function copyClick (dataDom) {
        dataDom.addEventListener('click', function () {
            var newDom = dataDom.cloneNode(true)
            var doom = document.getElementsByTagName('body')[0].insertBefore(newDom, dataDom.nextSibling)
            console.log(doom)
            copyClick(doom)
        })
    }

    // 查找节点
    var seek = document.getElementById('seek-dom')
    seek.addEventListener('click', function () {
        console.log(seek.children) // 包含节点的数组
        console.log(seek.childNodes) // 会计算空节点(换行)

        console.log(seek.firstChild) // 返回第一个节点,空节点(换行)
        console.log(seek.firstElementChild) // 返回第一个实节点

        console.log(seek.lastChild) // 返回最后一个节点,空节点(换行)
        console.log(seek.lastElementChild) // 返回最后一个实节点

        console.log(seek.nextSibling) // 某个节点的下一个节点(换行)
        console.log(seek.nextElementSibling) // 某个节点的下一个实体节点

        console.log(seek.previousSibling) // 某个节点的上一个节点(换行)
        console.log(seek.previousElementSibling) // 某个节点的上一个实节点

        console.log(seek.parentNode) // 某个节点的上一级父节点
        console.log(seek.offsetParent) // 返回第一个有定位属性的父节点,如果没有,直接返回body
    })

· .call() 和 .apply() 的含义和区别?

  call:调用某个对象的方法,用另一个对象替换调用的这个对象。如下:

    A.call(B, arg1, arg2)
    /** 上述的作用是:B对象应用A对象的方法 */

  apply:调用某个对象的方法,用另一个对象替换调用的这个对象。如下:

    A.apply(B, [arg1, arg2])
    /** 上述的作用是:B对象应用A对象的方法 */

  区别:

      apply:只有两个参数,第一个是this对象,第二个参数是给该方法传递的参数并且只能是数组。

      call:可以接受多个参数,传给该方法的参数只能一一列出来。

/** apply */
A.apply(B, [1, 2, 3, 4, 5])

/** call */
A.call(B, 1, 2, 3, 4, 5)

· 数组和对象有哪些原生方法,列举一下?

  1. 赋值方法:

      - pop和push:数组从最后添加删除元素。

      - shift和unshift:数组从前面添加删除元素。

      - splice:从数组中删除添加元素。

      - reverse:倒序。

      - sort:排序。

  2. 访问方法:

      - concat:用于连接两个或多个数组(返回一个新数组)。

      - join:用于把数组中所有元素放入字符串,然后通过制定字符隔开。

      - slice:根据制定的字符把字符串转成数组(返回一个新数组)。

      - toStrong:用于把一个逻辑值转成字符串并返回结果。

      - indexOf:可返回某个指定的字符串值在字符串中首次出现的位置。

      - lastIndexOf:可返回某个指定的字符串值在字符串中最后出现的位置。

  3. 迭代方法:

      - forEach:循环数组的每个元素并把元素传递给回调函数,返回三个参数,依次是元素、索引、数组本身。

      - map:使得数组的每个元素都会调用一个提供的函数,经过处理后返回其结果。

      - filter:创建一个新数组,新数组中的元素通过检查指定数组中符合条件的所有元素。

      - some:依次检测数组中的元素是否满足指定条件,如果有一个元素满足就返回true,如果没有元素满足就会返回false。

      - every:依次检测数组中的元素是否满足指定条件,如果有一个元素满足就返回false,如果没有元素满足就会返回true。

      - reduce:接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

      - reduceRight:接收一个函数作为累加器,数组中的每个值(从数组末尾向前)开始缩减,最终计算为一个值。

· JS 怎么实现一个类。怎么实例化这个类?如下

    /** 通过class创建一个类 */
    class Pant {
        constructor (name) {
            this.name = name
        }
        
        pantFun () {
            console.log(this.name)
        }
    }
    
    /** 通过new实例化这个类 */
    var obj = new Pant('hello')

· 那些操作会造成内存泄漏?

  内存泄漏:就是不再需要的对象仍然存在内存中,内存泄漏不断堆积的后果就是内存溢出,就是内存不够用。

  造成泄漏的操作:

      - setTimeout的第一个参数使用字符串而非函数的话,会造成内存泄漏。

      - 全部变量。

      - 闭包。

      - 循环。

      - dom清空或删除时,事件未清除导致内存泄漏。