前端面试总结——基础javascript篇

1. get请求传参长度的误区

误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源于浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

  • HTTP 协议 未规定 GET 和POST的长度限制
  • GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
  • 不同的浏览器和WEB服务器,限制的最大长度不一样
  • 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte

URI 是统一资源标识符,而 URL 是统一资源定位符。因此,笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN)。

1字=2字节(1 word = 2 byte)
1字节=8位(1 byte = 8bit)

2. 补充get和post请求在缓存方面的区别

post/get的请求区别

①get把请求的数据放在url上,即HTTP协议头上,其格式为:
以?分割URL和传输数据,参数之间以&相连。数据如果是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,及“%”加上“字符串的16进制ASCII码”。

②post请求提交的数据是存放在HTTP请求的请求体中(requrest body)

③ Get请求上传的数据不能大于2KB(是因为浏览器对于URL的长度有限制),而Post请求对于传输的数据理论上来说是没有限制的

④GET请求只能进行url编码,而POST支持多种编码方式。

⑤GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

补充补充一个get和post在缓存方面的区别

  • get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
  • post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

3. 闭包

一句话可以概括:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。闭包是定义在一个函数内部可访问该函数内部局部变量的函数,作用就是让函数外部可以访问函数内部局部变量。

这里面确实有闭包,local 变量和 bar 函数就组成了一个闭包(Closure)。 

function foo(){
  var local = 1
  function bar(){
    local++
    return local
  }
  return bar
}

var func = foo()
func()

 

4. 类的创建和继承

使用构造函数声明的对象要实例化后才可以进行遍历

原型:是利用prototype添加属性和方法

原型链:js在创建对象(无论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype

原型链实现过程

(1)类的创建(es5):new一个function,在这个function的prototype里面增加属性和方法。

下面来创建一个Animal类:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

原型链继承

  • 介绍:在这里我们可以看到new了一个空对象,这个空对象指向Animal并且Cat.prototype指向了这个空对象,这种就是基于原型链的继承。
  • 特点:基于原型链,既是父类的实例,也是子类的实例
  • 缺点:无法实现多继承
// --原型链继承
        function Cat() { }
        Cat.prototype = new Animal();
        Cat.prototype.name = 'cat';
        // Test Code
        var cat = new Cat();
        console.log(cat.name);//cat
        cat.eat('fish');//cat正在吃:fish
        cat.sleep();//cat正在睡觉!
        console.log(cat instanceof Animal); //true 
        console.log(cat instanceof Cat); //true

构造继承

使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

  • 特点:可以实现多继承
  • 缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。
 // //构造继承
        function Cat(name) {
            Animal.call(this);
            this.name = name || 'Tom';
        }
        // Test Code
        var cat = new Cat();
        console.log(cat.name);//Tom
        cat.sleep();//Tom正在睡觉!
        console.log(cat instanceof Animal); // false
        console.log(cat instanceof Cat); // true

组合继承

相当于构造继承和原型链继承的组合体。通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

  • 特点:可以继承实例属性/方法,也可以继承原型属性/方法
  • 缺点:调用了两次父类构造函数,生成了两份实例
//组合继承
        function Cat(name) {
            Animal.call(this);
            this.name = name || 'Tom';
        }
        Cat.prototype = new Animal();
        Cat.prototype.constructor = Cat;
        // Test Code
        var cat = new Cat();
        console.log(cat.name);//Tom
        cat.sleep();//Tom正在睡觉!
        console.log(cat instanceof Animal); // true
        console.log(cat instanceof Cat); // true

寄生组合继承

通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

 //寄生组合继承
        function Cat(name) {
            Animal.call(this);
            this.name = name || 'Tom';
        }
        (function () {
            // 创建一个没有实例方法的类
            var Super = function () { };
            Super.prototype = Animal.prototype;
            //将实例作为子类的原型
            Cat.prototype = new Super();
        })();
        // Test Code
        var cat = new Cat();
        console.log(cat.name);//Tom
        cat.sleep();//Tom正在睡觉!
        console.log(cat instanceof Animal); // true
        console.log(cat instanceof Cat); //true

for..in与for..of的区别 

for…in是遍历数组、对象的key 

let arr = [1, 2, 3];
for (let i in arr) {
	console.log(i)
}
let obj = {
	name: 'wuxiaodi',
	age: 18,
};
for (let i in obj) {
	console.log(i)
}

输出结果:

如果想用for…in遍历值那就把JS代码改成这样:

let arr = [1, 2, 3];
for (let i in arr) {
	console.log(arr[i])
}
let obj = {
	name: 'wuxiaodi',
	age: 18,
};
for (let i in obj) {
	console.log(obj[i])
}

输出结果:

for of 循环遍历

for(let value of target){}循环遍历

  1. 遍历数组

  2. 遍历Set

  3. 遍历Map

  4. 遍历字符串

  5. 遍历伪数组

 

let arr = [1,2,3,4,5];
    for(let num of arr){
        console.log(num);
    }
    let set = new Set([1,2,3,4,5]);
    for(let num of set){
        console.log(num);
    }
    let str = 'abcdefg';
    for(let num of str){
        console.log(num);
    }
    let btns = document.getElementsByTagName('button');
    for(let btn of btns){
        console.log(btn.innerHTML);
    }

 for…of是遍历数组的value

let arr = [1, 2, 3];
for (let i of arr) {
	console.log(i)
}

输出结果:

 

Iterator接口

概念: iterator是一种接口机制,为各种不同的数据结构提供统一的访问机制

    作用:

      1、为各种数据结构,提供一个统一的、简便的访问接口;

      2、使得数据结构的成员能够按某种次序排列

      3、ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
每调用next方法返回的是一个包含value和done的对象,{value: 当前成员的值,done: 布尔值}

value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束。

                                       当遍历结束的时候返回的value值是undefined,done值为true

原生具备iterator接口的数据(可用for of遍历)

      1、Array

      2、arguments

      3、set容器

      4、map容器

      5、String

 注意对象没有iterator

 //模拟指针对象(遍历器对象)
   function myIterator(arr){
     let nextIndex = 0
     return {
       next : function(){
         return nextIndex<arr.length?{value:arr[nextIndex++],done:false}:{value:undefined,done:true}
         //在这里实现了闭包,首先有函数嵌套,其次,内部函数引用了外部函数变量,并且内部的方法在外部去调用
       }
     }
   }
   //准备一个数据
   let arr = [1,4,2,'abd']
   let iteratorObj = myIterator(arr)
   console.log(iteratorObj.next())
   console.log(iteratorObj.next())
   console.log(iteratorObj.next())
   console.log(iteratorObj.next())
   console.log(iteratorObj.next())
 
 
   //将iterator接口部署到指定的数据类型上,可以使用for of 去遍历循环
   //数组、字符串、arguments、set容器、map容器
   for (let i of arr){
     console.log(i)
   }
   let str = 'ppqq will be forever'
   for (let i of str){
     console.log(i)
   }
   function fun(){
     for(let i of arguments){
       console.log(i)
     }
   }
   fun(1,3,4,'ppqq')

 使用三点运算符,解构赋值,默认去调用iterator接口

 如何解决异步回调地狱

promise、generator、async/await

promise对象


1. 理解:

  Promise对象: 代表了未来某个将要发生的事件(通常是一个异步操作)

  有了promise对象, 可以将异步操作以同步的流程表达出来, 避免了层层嵌套的回调函数(俗称'回调地狱')

   ES6的Promise是一个构造函数, 用来生成promise实例
2. 使用promise基本步骤(2步):

结果很明显:111 222 333 成功啦

let promise = new Promise((resolve,reject) => {
   console.log('111')
   setTimeout(() => {
     console.log('333')
     resolve();
     //reject()
   },2000)
 })
 console.log('222')
 promise
      .then(() =>{
        console.log('成功啦')
      },() => {
        console.log('失败啦')
      })

还可以传参数,data,error是对应的

let promise = new Promise((resolve,reject) => {
   console.log('111')
   setTimeout(() => {
     console.log('333')
     //resolve('哈哈哈');
     reject('呜呜呜')
   },2000)
 })
 console.log('222')
 promise
      .then((data) =>{
        console.log(data,'成功啦')
      },(error) => {
        console.log(error,'失败啦')
      })

3. promise对象的3个状态

   pending: 初始化状态、fullfilled: 成功状态、 rejected: 失败状态

4. 应用:

  使用promise实现超时处理

  使用promise封装处理ajax请求

//定义获取新闻的功能函数
function getNews(url){
 
  let promise = new Promise((resolve,reject) => {
    //状态:初始化
    //执行异步任务
    //创建xmlHttp实例对象
    let xmlHttp = new XMLHttpRequest()
    //绑定监听 readyState
    xmlHttp.onreadystatechange = function(){
      if(xmlHttp.readyState === 4 ){//状态码为4代表响应已经完成,可以获取并使用服务器的响应
        if( xmlHttp.status === 200){
        console.log(xmlHttp.responseText)
        resolve(xmlHttp.responseText) //修改promise的状态为成功的状态
      }else{ //请求失败
        reject('暂时没有新闻内容')
      }
    }
  };
    //open 设置请求的方式以及url
    xmlHttp.open('GET',url);
    //send
    xmlHttp.send()
  })
  return promise;
}
getNews('http://127.0.0.1:3000/news?id=2')
      .then((data) => {
      console.log(data)
      let commentsUrl = JSON.parse(data).commentsUrl
      let Url ='http://127.0.0.1:3000'+commentsUrl
      //发送请求
       return getNews(Url)
      },(error) => {
      console.log(error)
      })
      .then((data) => {
        console.log(data)
      },() => {
        
      })

promise过程分析

Generator函数 

Generator函数是一个状态机,内部封装了不同状态的数据

用来生成遍历器对象

可暂停函数(惰性求值), yield可暂停,next方法可启动。每次返回的是yield后的表达式结果

yield语句返回结果通常为undefined, 当调用next方法时传参内容会作为启动时yield语句的返回值。

 function* myGenerator() {
            console.log('开始执行')
            //yield 'hello'
            let result = yield 'hello'
            console.log(result)
            // yield console.log('aha')
            console.log('暂停后,再次执行')
            yield 'generator'
            console.log('遍历完毕')
            return '返回的结果' //没有这句的话,返回的是undefined,因为consolelog返回的是undefined
        }
        let MG = myGenerator()
        
        console.log(MG.next())
        
        console.log(MG.next('aaa可以指定参数作为返回值'))//这里就相当于给yield hello这句话的返回值---
        console.log(MG.next())

 结果为:

每执行一个yield, 会返回value,done  。然后就暂停了。下一次的next()启动。

对象的Symbol.iterator属性  指向遍历器对象。输出结果为1 2 3 

let obj = { username: 'pppp', age: 19 }
        obj[Symbol.iterator] = function* myTest() {
            yield 1
            yield 2
            yield 3
        }
        for (let i of obj) {
            console.log(i)
        }

* 需求:

    * 1、发送ajax请求获取新闻内容

    * 2、新闻内容获取成功后再次发送请求,获取对应的新闻评论内容

    * 3、新闻内容获取失败则不需要再次发送请求。

 function getNews(url){
      $.get(url,function(data){
        console.log(data)
        let commentsUrl = data.commentsUrl;
        let url = 'http://127.0.0.1:3000' + commentsUrl;
        // 当获取新闻内容成功,发送请求获取对应的评论内容
        // 调用next传参会作为上次暂停是yield的返回值
        SX.next(url);
      })
    }
    function* sendxml(){
        let url=yield getNews('http://127.0.0.1:3000/news?id=3')
        yield getNews(url)
    }
    //获取遍历器对象
    let SX = sendxml()
    SX.next()

 

 async函数

概念: 真正意义上去解决异步回调的问题,同步流程表达异步操作

本质: Generator的语法糖

语法:

      async function foo(){

        await 异步操作;

        await 异步操作;

      }

  特点:

    1、不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行

    2、返回的总是Promise对象,可以用then方法进行下一步操作

    3、async取代Generator函数的星号*,await取代Generator的yield

    4、语意上更为明确,使用简单,经临床验证,暂时没有任何副作用 

async function foo(){
  return new Promise(resolve => {
    // setTimeout(function(){
    //   resolve()
    // },2000)
    setTimeout(resolve,2000)//resolve就是一个函数
  })
}
async function test(){
  console.log('开始执行',new Date().toTimeString())
  await foo()
  console.log('执行完毕',new Date().toTimeString())
}
 test()  

执行结果:可以发现时间相差两秒

async  的await返回值问题

function test(){
  return 'hehehe'
}
async function asyncPrint(){
  // let result = await test()
  // console.log(result)//打印hehehe
  // let result = await Promise.resolve('promise')
  // console.log(result)//打印promise
  let result = await Promise.reject('失败')
  console.log(result)//报错,并打印 失败
 
}
asyncPrint()

 获取新闻内容

//获取新闻内容
async function getNews(url){
  return new Promise((resolve,reject) => {
    $.ajax({
      method:'GET',
      url,//同名属性可以不写后面的
      success:data => resolve(data),
      error: error=> reject()
      
    })
  })
}
async function sendXml(){
  let result=await getNews('http://127.0.0.1:3000/news?id=6')
  console.log(result)
  result=await getNews('http://127.0.0.1:3000'+result.commentsUrl)
  console.log(result)
}
sendXml()

 

深度复制


 1、数据类型:

    * 数据分为基本的数据类型(String, Number, boolean, Null, Undefined)和对象数据类型

    - 基本数据类型:

      特点: 存储的是该对象的实际数据

    - 对象数据类型:

      特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存里

2、复制数据

    - 基本数据类型存放的就是实际的数据,可直接复制

      let number2 = 2;

      let number1 = number2;

    - 克隆数据:对象/数组

      1、区别: 浅拷贝/深度拷贝

         判断: 拷贝是否产生了新的数据还是拷贝的是数据的引用

         知识点:对象数据存放的是对象在栈内存的引用,直接复制的是对象的引用

         let obj = {username: 'kobe'}

         let obj1 = obj; // obj1 复制了obj在栈内存的引用

      2、常用的拷贝技术

        1). arr.concat(): 数组浅拷贝

        2). arr.slice(): 数组浅拷贝

        3). JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据

        4). 浅拷贝包含函数数据的对象/数组

             特点:拷贝的引用,修改拷贝以后的数据会修改原数据,使得原数据不安全

        5). 深拷贝包含函数数据的对象/数组

              特点:拷贝的时候会生成新数据,修改拷贝以后的数据不会改变原数据

思考:如何实现深度拷贝

拷贝的数据里有对象和数组

拷贝的数据里不能有对象/数组,即使有对象/数组可以继续遍历对象、数组,拿到里边每一项值,一直到拿到是基本数据类型,然后再去复制,就是深度拷贝

注意:

1、typeof 返回的数据类型: String,Number,Boolean,Undefined,Object,Function

2、Object.prototype.toString.call(obj)    这个可以返回 [object Array]等,这是个例子。所以用这个可以很好的判断类型
 

// 深度克隆(复制)
 
  function getObjClass(obj) {
    let result = Object.prototype.toString.call(obj).slice(8, -1);
    if(result === 'Null'){
      return 'Null';
    }else if(result === 'Undefined'){
      return 'Undefined';
    }else {
      return result;
    }
  }
  // for in 遍历数组的时候遍历的是下标
  let testArr = [1,2,3,4];
  for(let i in testArr){
    console.log(i); // 对应的下标索引
  }
 
  // 深度克隆
  function deepClone(obj) {
    let result, objClass = getObjClass(obj);
    if(objClass === 'Object'){
      result = {};
    }else if(objClass === 'Array'){
      result = [];
    }else {
      return obj; // 如果是其他数据类型不复制,直接将数据返回
    }
    // 遍历目标对象
    for(let key in obj){
      let value = obj[key];
      if(getObjClass(value) === "Object" || 'Array'){
        result[key] = deepClone(value);
      }else {
        result[key] = obj[key];
      }
    }
    return result;
  }
  
  
  let obj3 = {username: 'kobe',age: 39, sex: {option1: '男', option2: '女'}};
  let obj4 = deepClone(obj3);
  console.log(obj4);
  obj4.sex.option1 = '不男不女'; // 修改复制后的对象不会影响原对象
  console.log(obj4, obj3);

6. 说说前端中的事件流

https://segmentfault.com/a/1190000015719043

事件流是浏览器中页面接收到事件的顺序,从最外层的元素到最里层的元素为事件捕获阶段,从最里层元素传到最外层元素是事件冒泡阶段。

DOM2级事件流规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件作出响应。

IE只支持事件冒泡

addEventListeneraddEventListener 是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

事件冒泡

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <div class="t3">document
        <div class="t2">html
            <div class="t1">body
                <div class="t0">div</div>
            </div>
        </div>
    </div>

    <script>
        var $t0 = document.getElementsByClassName('t0')[0];
        var $t1 = document.getElementsByClassName('t1')[0];
        var $t2 = document.getElementsByClassName('t2')[0];
        var $t3 = document.getElementsByClassName('t3')[0];

        $t0.addEventListener("click", function () {
            alert("click div")
        }, false);

        $t1.addEventListener("click", function () {
            alert("click body")
        }, false);

        $t2.addEventListener("click", function () {
            alert("click html")
        }, false);

        $t3.addEventListener("click", function () {
            alert("click document")
        }, false);
    </script>

</body>

</html>

点击div,根据冒泡事件流模型由内向外的规则,会依次弹出:click div -> click body -> click html -> click docuement

点击body,根据冒泡事件流模型由内向外的规则,会依次弹出: click body -> click html -> click docuement

依次类推。

 

事件捕获

将上一段代码中的 false 都改为 ture,则变为捕获方式:

点击div,根据捕获事件流模型由外向内的规则,会依次弹出:click document -> click html -> click body -> click div

点击body,根据捕获事件流模型由外向内的规则,会依次弹出:click document -> click html -> click body 

依次类推

事件冒泡&事件捕获同时存在

原则:

  • 从外向内,捕获前进,遇到捕获事件立即执行
  • 非 target 节点,先捕获再冒泡
  • target 节点,按代码书写顺序执行(无论冒泡还是捕获)

点击div,因此会依次弹出:click document -> click html -> click div -> click body

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <div class="t3">document
        <div class="t2">html
            <div class="t1">body
                <div class="t0">div</div>
            </div>
        </div>
    </div>

    <script>
        var $t0 = document.getElementsByClassName('t0')[0];
        var $t1 = document.getElementsByClassName('t1')[0];
        var $t2 = document.getElementsByClassName('t2')[0];
        var $t3 = document.getElementsByClassName('t3')[0];

        $t0.addEventListener("click", function () {
            alert("click div")
        }, false);

        $t1.addEventListener("click", function () {
            alert("click body")
        }, false);

        $t2.addEventListener("click", function () {
            alert("click html")
        }, true);

        $t3.addEventListener("click", function () {
            alert("click document")
        }, true);
    </script>

</body>

</html>

事件流模型的应用:事件委托

事件委托 又叫 事件代理,指的是利用事件冒泡原理,只需给外层父容器添加事件,若内层子元素有点击事件,则会冒泡到父容器上,

通过判断事件发生元素DOM的类型,来做出不同的响应。这就是事件委托,简单说就是:子元素委托它们的父级代为执行事件。

事件流模型在业务开发中有哪些应用场景呢?

例如,一个播放列表有成千上万首歌曲,如果遍历播放列表,给每个 item 添加点击事件,而这样无疑会多次访问 DOM,每次 DOM 操作都会引起浏览器的重绘与重排,非常不利于性能优化。

这时候我们可以利用事件委托,只给最外层的容器添加响应事件,这样只需一次 DOM 操作,就能达到目的。

  • 好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <ul id="music">
        <li>青花瓷</li>
        <li>东风破</li>
        <li>双节棍</li>
    </ul>

    <script>
        var $music = document.getElementById('music');

        $music.addEventListener('click', function (e) {
            if (e.target.nodeName.toLowerCase() === 'li') { // 判断目标元素target是否为li元素
                var content = e.target.innerHTML;
                console.log(content);
            }
        }, false)
    </script>

</body>

</html>

9. 图片的懒加载和预加载

  • 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。以提供给用户更好的体验,减少等待的时间。

  • 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。

两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
 

懒加载懒加载,就是看到图片的时候再去进行加载。

懒加载对应原理

先给图片 src 不赋值,等视图区域滚动到它的时候,给它的 src 赋值为正确的 url 以达到当下被加载。

场景

一个相册页面,滑到对应相片区的时候才去加载这些照片

预加载, 就是图片可能会被用户查看,先给它加载了,等用户查看的时候,能够很快显示。

预加载原理

图片刚开始是不被显示的,通过获取 img 元素的 src, js new img 赋值 src 实现加载图片,这样等用户查看的时候,图片被显示了,能够直接显示。

场景

一个缩略图点击,展示大图。 预先加载大图,实现预览流畅

具体实现待补(好像有vue)

10、JS事件mouseover ,mouseout ,mouseenter,mouseleave的区别

mouseover: 只要鼠标指针移入事件所绑定的元素或其子元素,都会触发该事件
mouseenter: 只有鼠标指针移入事件所绑定的元素时,才会触发该事件

mouseout: 只要鼠标指针移出事件所绑定的元素或其子元素,都会触发该事件
mouseleave: 只有鼠标指针移出事件所绑定的元素时,才会触发该事件

总结:

① 只要鼠标指针移入(或移出)事件所绑定的元素或其子元素,都会触发mouseover(或mouseout)事件
② 只有鼠标指针移入(或移出)事件所绑定的元素时,才会触发mouseenter(或mouseleave)事件

11. js的new操作符做了哪些事情

new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。

var obj = new A();中一共实现了三步操作:

①创建一个空对象obj    var obj= {}
②将构造函数的prototype赋给对象的__proto__   obj.__proto__ = A.prototype
③构造函数对象的this指针指向obj   A.call(obj)


12.改变函数内部this指针的指向函数(bind,apply,call的区别)

  • 通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。

  • 通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。

1、call(object,arg1,arg2) ,call方法的第一个参数是函数中this重新指向的对象,剩下的参数是传入该函数的形参

不传,或者传null,undefined, 函数中的 this 指向 window 对象,传递另一个函数的函数名,函数中的 this 指向这个函数的引用,传递字符串、数值或布尔类型等基础类型,函数中的 this 指向其对应的包装对象,如 String、Number、Boolean,传递一个对象,函数中的 this 指向这个对象。
 

      function say(){
         console.log(this);
      }
      function eat(){
      }
      var obj={
         name:'Bob',
      };
 
      say.call();             //this指向window
      say.call(null);         //this指向window
      say.call(undefined);    //this指向window
      say.call(eat);          //this指向函数得引用 function eat(){}
      say.call(1);            //this指向数值基本类型得包装对象Number
      say.call("str");        //this指向数值基本类型得包装对象String
      say.call(true);         //this指向数值基本类型得包装对象Boolean
      say.call(obj);          //this指向这个对象obj

call的作用是允许在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性,如下

      var a={
         name:'Bob',
         food:'fish',
         say:function(){
            console.log('HI,this is a.say!!');
         }
      }
 
      function b(name){                      //b.call(a,'Tom');使得a对象能调用其他函数方法
         console.log("post Params:"+name);   //a对象使用了b('Tom')方法, 输出post Params: Tom
         console.log('I am '+this.name);     //a对象获取了自己的属性 ,输出 I am Bob
         this.say();                         //a对象使用自己的方法, 输出 HI,this is a.say!!
      }
 
      b.call(a,'Tom');

2、apply(object,[arg1,arg2]),apply方法的第一个参数是函数中this重新指向的对象,第二个参数数组是传入该函数的形参;和call方法唯一区别是第二参数的传递形式是数组。使用如下

      function b(x,y,z){                      
        console.log(x,y,z);   //会将多个参数拼接输出                      
      }
      //apply() 方法接收的参数是数组形式,但是传递给调用的函数时是用参数列别的形式传递的
      b.apply(null,[1,2,3]);  //输出 1 2 3 ,这里等同于window对象调用b方法并使用参数[1,2,3]

3、bind(object,arg1,arg2) ,bind方法是ES5 新增的一个方法,传参和call方法一致。与call、apply方法的区别是,call和apply方法会对目标函数进行自动执行,会返回一个新的函数。call和apply无法在事件绑定函数中使用。而bind弥补了这个缺陷,在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题,


使用场景:

      function Animal(name,weight){
         this.name=name;
         this.weight=weight;
      }
      function Cat(){
         Animal.apply(this,['cat','10kg']);  //理解为在Cat函数对象创建时会使用Animal()方法
         //Animal.call(this,'cat','10kg');
         this.say=function(){
            console.log('I am '+this.name+' , my weight is '+this.weight);
         }
      }
      var cat = new Cat();
      cat.say();  //输出 I am cat , my weight is 10kg

2、移花接木

ArrayLike 对象在JS中被广泛使用,比如DOM 中的NodeList, 函数中的arguments 都是类数组对象,这些对象像数组一样存储着每一个元素,但它没有操作数组的方法,而我们可以通过call 将数组的某些方法`移接`到ArrayLike 对象,从而达到操作其元素的目的。比如我们可以这样遍历函数中的arguments:
 

      function test(){
         console.log(typeof(arguments));     //输出Object ,ArrayLike是类数组对象
 
         //检测arguments是否是Array的实例
         console.log(arguments instanceof Array);  //输出 false
         console.log(Array.isArray(arguments));    //输出 false
 
         //判断arguments是否有forEach的方法
         console.log(arguments.forEach);          //输出 undefined
 
         //将数组中的forEach方法应用到arguments上
         Array.prototype.forEach.call(arguments,
            function(item){console.log(item);  //输出 1 2 3 4 5
         });
      }
      test(1,2,3,4,5);

除此之外,对于apply 而言,我们上面提到了它独有的一个特性,即apply 接收的是数组,在传递给调用函数的时候是以参数列表传递的。 这个特性让apply 看起来比call 略胜一筹,比如有这样一个场景:给定一个数组[1,3,4,7],然后求数组中的最大元素,而我们知道,数组中并没有获取最大值的方法,一般情况下,你需要通过编写代码来实现。而我们知道,Math 对象中有一个获取最大值的方法,即Math.max(), max方法需要传递一个参数列表,然后返回这些参数中的最大值。而apply 不仅可以将Math 对象的max 方法应用到其他对象上,还可以将一个数组转化为参数列表传递给max,看代码就能一目了然:
 

var arr = [-1,2, 3, 1, 5, 4, 223, 11];
var a=Math.max.apply(null, arr); // 223
console.log(a);

ES5中自己笔记

1. Function.prototype.bind(obj) :

  * 作用: 将函数内的this绑定为obj, 并将函数返回

2. 面试题: 区别bind()与call()和apply()?

  * 都能指定函数中的this

  * call()/apply()是立即调用函数

  * bind()是将函数返回

        var obj = { username: 'ppqq' }
        function foo(data) {
            console.log(this, data);
        }
        foo()
        //传入参数的形式
        foo.call(obj, 33)//直接从第二个参数开始,依次传入
        foo.apply(obj, [33])//第二参数必须是数组,传入放在数组里
        //bind的特点:绑定完this不会立即调用当前的函数,而是将函数返回
        //bind传参的方式与call一致
        foo.bind(obj, 33)()
        //bind用于回调函数,不立即调用
        setTimeout(function () {
            console.log(this)
        }.bind(obj), 1000)

结果:

13. js的各种位置,比如clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别?

https://www.imooc.com/article/17571

15. 异步加载js的方法

js的加载默认是同步的,因为js是单线程执行,只能完成一件再执行下一件.

一些外部引入的js文件可以因为文件太大,在加载资源的过程中会影响dom元素的加载,影响了用户体验,因此会使用异步加载技术加载文件.

异步加载就相当于是添加了一条线程,与HTML和CSS并行加载,不会阻塞

  • defer:只支持IE如果您的脚本不会改变文档的内容,可将 defer 属性加入到<script>标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。

  • <script type="text/javascript" src="demo.js" defer="defer"></script>

     

  • async,HTML5属性仅适用于外部脚本,并且如果在IE中,同时存在defer和async,那么defer的优先级比较高,脚本将在页面完成时执行。

    IE9以上都能执行,比较普遍

    注意,这个在script标签里面不能写代码

    只能引入外部文件

  •  <script type="text/javascript" src="demo.js" async="async"></script>

     

  • 创建script标签,插入到DOM中

<script type="text/javascript" >
    var script=document.createElement("script");
    script.type="text/javascript";//设置Type
    script.src="toos.js";//设置src
    document.head.appendChild(script);//异步加载
    script.onload=function(){
        test();
    }
    </script>
 
function test(){
    console.log("a");
}

当我们把它加入到head标签里面的时候就是异步加载,但是我们不能紧接着直接调用test,因为可能还没有加载完这个js文件,所以我们要

script.onload=function(){
        test();
    }

当他加载完以后再执行

这种形式的异步加载除了IE浏览器无法执行,其他都是可以的

那么IE的方法是什么呢:

IE提供了自己的方法:

readyState:这是一个状态码

当我们的readyState变成complete或者是loaded的时候表示加载完成我们才能调用这个函数

当然我们需要一个方法来监听这个状态码是否发生变化:onreadystatechange

var script=document.createElement("script");
script.type="text/javascript";//设置Type
script.src="toos.js";//加载文件
if(script.readyState){
        script.onreadystatechange=function(){
            if(script.readyState=="complete"||script.readyState=="loaded"){
                test();
            }
        }
    }

但是有一种情况,先说明一下,onreadychange是一个监听变化过程的函数,也就是说必须要有一个变化过程出现才能调用下面的test函数,但是如果因为一些外部条件,让toos.js加载的非常快,在监听之前,readyState就已经变成了completed,事件都还没来得及绑定,那么就触发不了这个函数了,所以我们把加载文件放在绑定事件的后面,先绑定事件:

var script=document.createElement("script");
script.type="text/javascript";//设置Type
if(script.readyState){
        script.onreadystatechange=function(){
            if(script.readyState=="complete"||script.readyState=="loaded"){
                test();
            }
        }
    }
script.src="toos.js";//加载文件

16. Ajax 解决浏览器缓存问题

  • 在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。

  • 在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。

  • 在URL后面加上一个随机数: "fresh=" + Math.random()。

  • 在URL后面加上时间搓:"nowtime=" + new Date().getTime()。

  • 如果是使用jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

18. JS中的垃圾回收机制

必要性由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

垃圾回收的方法:标记清除、计数引用。

原理

找到不再被使用的变量,然后释放其占用的内存,但这个过程不是时时的,因为其开销比较大,

所以垃圾回收器会按照固定时间间隔周期性的执行

回收方式

a.标记清除

当变量进入环境时,将这个变量标记为“进入环境”;当变量离开环境时,则将其标记为“离开环境”。

标记“离开环境”的就回收内存

b.引入计数(低级浏览器)

当变量声明,第一次赋值时记为1,然后当这个变量值改变时,记录为0,将计数为0的回收

内存泄露

a.意外的全局变量引起的内存泄露

原因: 全局变量不会被回收

解决:使用严格模式避免

b.闭包引起的

原因: 活动对象被引用,使闭包内的变量不会被释放

解决: 将活动对象赋值为null

c.被清理的DOM元素的引用

原因: 虽然DOM被删掉了,但对象中还存在对DOM的引用

解决: 将对象赋值为null

d.被遗忘的定时器或回调

原因: 定时器内部实现闭包,回调也是闭包

解决: 清理定时器clearInterval、null

19.eval是做什么的

eval()的作用

把字符串参数解析成JS代码并运行,并返回执行的结果;

eval("2+3");//执行加运算,并返回运算值。
eval("var age=10");//声明一个age变量

注意事项

应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。

其它作用

由JSON字符串转换为JSON对象的时候可以用eval,例如:

var json = "{name:'pp',age:20}";
var jsonObj = eval("(" + json + ")");
console.log(jsonObj);

20.如何理解前端模块化

前端模块化就是复杂的文件编成一个一个独立的模块,块的内部数据是私有的,不过是向外暴露一些借口(方法)与外部其他模块通信。这样会引来模块之间相互依赖的问题,所以有了commonJS规范,AMD,CMD规范,ES6规范等等,以及用于js打包(编译等处理)的工具webpack

好处:

①避免命名冲突、②更好的分离,按需加载③更高复用性④高可维护性

21.Commonjs、 AMD和CMD、ES6规范

https://blog.csdn.net/zlppassion/article/details/105589210

https://www.cnblogs.com/yinyiwang/articles/12740339.html

CommonJs

AMD_RequireJS

CMD

使用seajs:

  1. 引入sea.js库

  2. 如何定义导出模块 :

    define()

    exports

    module.exports

  3. 如何依赖模块:

    require()

  4. 如何使用模块:

    seajs.use()

ES6规范

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

ES6 模块与 CommonJS 模块的差异

它们有两个重大差异:

1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

 

23.实现一个once函数,传入函数参数只执行一次

function ones(func){
    var tag=true;
    return function(){
      if(tag==true){
        func.apply(null,arguments);
        tag=false;
      }
      return undefined
    }
}

 24.将原生的ajax封装成promise

1. open(method, url, async) 方法需要三个参数:
  method:发送请求所使用的方法(GET或POST);与POST相比,GET更简单也更快,并且在大部分情况下都能用;然而,在以下情况中,请使用POST请求:
无法使用缓存文件(更新服务器上的文件或数据库)
向服务器发送大量数据(POST 没有数据量限制)
发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

 url:规定服务器端脚本的 URL(该文件可以是任何类型的文件,比如 .txt 和 .xml,或者服务器脚本文件,比如 .asp 和 .php (在传回响应之前,能够在服务器上执行任务));

 async:规定应当对请求进行异步(true)或同步(false)处理;true是在等待服务器响应时执行其他脚本,当响应就绪后对响应进行处理;false是等待服务器响应再执行。

2. send() 方法可将请求送往服务器。

3. onreadystatechange:存有处理服务器响应的函数,每当 readyState 改变时,onreadystatechange 函数就会被执行。

4. readyState:存有服务器响应的状态信息。

0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
1: 服务器连接已建立(open方法已经被调用)
2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
3: 请求处理中(下载中,responseText 属性已经包含部分数据)
4: 请求已完成,且响应已就绪(下载操作已完成)
5: responseText:获得字符串形式的响应数据。

6. setRequestHeader():POST传数据时,用来添加 HTTP 头,然后send(data),注意data格式;GET发送信息时直接加参数到url上就可以,比如url?a=a1&b=b1。

PS:Fetch polyfill 的基本原理是探测是否存在window.fetch方法,如果没有则用 XHR 实现。
 

var Ajax={
  get: function(url, fn) {
    // XMLHttpRequest对象用于在后台与服务器交换数据   
    var xhr = new XMLHttpRequest();            
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function() {
      // readyState == 4说明请求已完成
      if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { 
        // 从服务器获得数据 
        fn.call(this, xhr.responseText);  
      }
    };
    xhr.send();
  },
  // datat应为'a=a1&b=b1'这种字符串格式,在jq里如果data为对象会自动将对象转成这种字符串格式
  post: function (url, data, fn) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
    // 添加http头,发送信息至服务器时内容编码类型
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
        fn.call(this, xhr.responseText);
      }
    };
    xhr.send(data);
  }
}

//  promise 封装
var jsGetAjaxPromise = function(param){
        return new Promise(function(resolve, reject ){
            var xhr = new XMLHttpRequest();
            xhr.open('get', param.url, true);
            xhr.onload = resolve;
            xhr.onerror = reject;
            xhr.send();
        })
    }

25.js监听对象属性的改变

https://www.jianshu.com/p/fce3a6a9f920

1. 基于ES5的getter和setter

在ES5中新增了一个Object.defineProperty, 直接在一个对象上定义一个新属性, 或者修改一个已存在的属性,并返回这个对象。

Object.defineProperty(obj, prop, descriptor)

参数
obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptor
将被定义或修改的属性描述符。

var obj={};
Object.defineProperty(obj,'data',{
    get:function(){
        return data;
    },
    set:function(newValue){
        data=newValue;
        console.log('set :',newValue);
        //需要触发的渲染函数写在这...
    }
})

当我们给obj的data赋值的时候,就会触发set 的方法

    obj.data=5;//set: 5

如果要一下子定义多个变量的getter和setter,你可以使用Object.defineProperties(obj,props)

参数
obj
要在其上定义属性的对象。
props
要定义其可枚举属性或修改的属性描述符的对象。

 var obj = {};
Object.defineProperties(obj, {
    a: {
        configurable: true, //表示该属性描述符可以被改变(默认为false)
        get: function() {
            console.log('get: ',a)
            return a
        },
        set: function(newValue) {
            a = newValue;
            console.log('set: ',a)
        }
    },
    b: {
        configurable: true,
        get: function() {
            console.log('get: ',b)
            return b;
        },
        set: function(newValue) {
            b = newValue;
            console.log('set: ',b)
        }
    }
})

2. 脏值检测

脏值检测原理就是比较新值和旧值, 当值真的发生改变时再去更改DOM,目前Angular使用脏值检测

缺点是如果不注意,每次脏值检测会检测大量的数据, 而很多数据是没有检测的必要的,容易影响性能。

3. ES6的Proxy

类似HTTP中的代理

let p = new Proxy(target, handler);

target
用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个对象,其属性是当执行一个操作时定义代理的行为的函数。

示例:当对象中不存在属性名时,缺省返回数为37

let handler = {
    get: function(target, name){
        return name in target ? target[name] : 37;
    }
};

let p = new Proxy({}, handler);

p.a = 1;
p.b = undefined;

console.log(p.a, p.b);    // 1, undefined

console.log('c' in p, p.c);    // false, 37

示例:通过代理,验证向一个对象的传值

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // The default behavior to store the value
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

console.log(person.age); 
// 100

person.age = 'young'; 
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300; 
// 抛出异常: Uncaught RangeError: The age seems invalid

27. ==和===、以及Object.is的区别



==:等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较; ===:恒等,严格比较运算符,不做类型转换,类型不同就是不等; Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与===的行为基本一致

(1) ==

主要存在:强制转换成number,null==undefined

" "==0  //true
"0"==0  //true
" " !="0" //true
123=="123" //true
null==undefined //true

(2)Object.js

主要的区别就是+0!=-0 而NaN==NaN
(相对比===和==的改进)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值