复习JavaScript

96 阅读6分钟

常见的值类型

  • undefined
  • string
  • number
  • boolean
  • Symbol

常见引用类型

  • 对象
  • 数组
  • null 特殊引用类型,指针指向空地址
  • 函数

typeof运算符

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可在细分)

手写深拷贝

<script>
    const obj1 = {
      name: 'stone',
      age: 20,
      address: {
        city: 'beijing'
      },
      arr: ['a', 'b', 'c']
    }

    const obj2 = deepClone(obj1);
    obj2.address.city = 'shanghai';
    obj2.arr[0] = 'a1';
    console.log(obj1.address.city);//beijing
    console.log(obj2.address.city);//shanghai
    console.log(obj1.arr[0]);//a
    console.log(obj2.arr[0]);//a1

    //深拷贝
    function deepClone(obj = {}) {
      if(typeof obj !== 'object' || obj == null) {
        return obj;
      }

      // 初始化返回结构
      let result;
      if(obj instanceof Array) {
        result = [];
      }else {
        result = {};
      }

      for(let key in obj) {
        //保证key不是原型的属性
        if(obj.hasOwnProperty(key)) {
          //递归调用
          result[key] = deepClone(obj[key]);
        }
      }
      return result
    }

  </script>

原型

- 隐式原型 __proto__
- 显示原型 prototype

原型关系:

  • 每个class都有显示原型prototype
  • 每个实例都有隐式原型__proto__
  • 实例的__proto__指向对应class的prototype
  • instanceof 实则式通过原型链一层一层去寻找是否有这样的东西从而实现的

列如:判断一个一个变量是不是数组 a instanceof Arra

原型.png

原型链(class的原型本质).png

手写jquery

/** HTML内容
 *  <P>文字</P>
    <P>文字</P>
    <P>文字</P>
*/



class jQuery {
  constructor(selector) {
    const result = document.querySelectorAll(selector)
    const length = result.length
    for(let i = 0; i < length; i++) {
      this[i] = result[i]
    }
    this.length = length

    //类似于数组
  }
  get(index) {
    return this[index]
  }
  each(fn) {
    for(let i = 0; i < this.length; i++) {
      const elem = this[i]  
      fn(elem)
    }
  }
  on(type, fn) {
    return this.each(elem => {
      elem.addEventListener(type, fn, false)
    })
  }
  //扩展很多DOM API
}

//插件
jQuery.prototype.dialog = function (info) {
  alert(info)
}

//造轮子
class myJQuery extends jQuery {
  constructor(selector) {
    super(selector)
  }
  //扩展自己的方法
  addClass(className) {

  }
  style(data) {
    
  }
}


//const $p = new jQuery('P')
//$P.get(1)
//$p.each((elem) => console.log(elem.nodeName))
//$p.on('click', () => alert('clicked'))

字面量new出来的对象和 Object.create(null)创建出来的对象有什么区别

  • 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,
  • 而 Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性

作用域和闭包

什么是作用域,什么是作用域链?

  • 规定变量和函数的可使用范围称作作用域

  • 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链

作用域:

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6) 例如: if for while

自由变量

  • 一个变量再当前作用域没有定义,但被使用了
  • 向上级作用域(父级),一层一层依次寻找,知道找到为止
  • 如果全局作用域中都没有找到,则报错 xx is not defined

什么是闭包?

函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护保存的作用

作用:

保护:避免命名冲突

保存:解决循环绑定引发的索引问题

变量不会销毁:可以使用函数内部的变量,使变量不会被垃圾回收机制回收

闭包: 作用域应用的特殊情况,有两种表现: 1.函数作为参数被传递 2.函数作为返回值被返回

闭包.png

<script>
    //函数作为返回值---------------------------------
     function create() {
       let a = 100
       return function() {
         console.log(a)
       }
     }

     const fn = create();
     const a = 200
     fn()//100


    //函数作为参数被传递---------------------------------------
    function print(fn) {
      const a = 200
      fn()
    }
    const a = 100
    function fn() {
      console.log(a)
    }
    print(fn)//100
  </script>

总结:闭包自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方查找

this

场景

作为普通函数被调用		返回window
使用 call apply bind 被调用	传入什么返回什么
作为对象方法被调用		返回对象本身
在class方法中调用		返回当前实例的这个本身
箭头函数中调用		        永远会去找上级作用域的this来确定

this.png

this1.png

this2.png this的不同应用场景,如何取值?

this取什么值是在函数执行的时候确定的 不是在函数确定的时候确定的

手写bind函数

<script>
    function fn1(a, b, c) {
      console.log('this:', this)
      console.log(a, b, c)
      return 'this is fn1'
    }
    const fn2 = fn1.bind({x: 100}, 10, 20, 30)
    const res = fn2()
    console.log(res)


    //模拟bind
    Function.prototype.bind1 = function() {
      //将参数拆解为数组
      const args = Array.prototype.slice.call(arguments)

      //获取this(数组第一项)
      const t = args.shift()

      //fn1.bind(...)中的fn1
      const self = this

      //返回一个函数
      return function() {
        return self.apply(t, args)
      }
    }
  </script>

实际开发中闭包的应用:

隐藏数据 做一个简单的cache工具

<script>
    //闭包隐藏数据,只提供API
    function createCache() {
      const data = {} //闭包中的数据,被隐藏,不被外界访问
      return {
        set: function(key, value) {
          data[key] = value
        },
        get: function(key) {
          return data[key]
        }
      }
    }

    const c = createCache()
    c.set('a', 100)
    console.log(c.get('a'))
  </script>

单线程和异步

  • JS是单线程语言,只能同时做一件事
  • 浏览器和NODEJS已支持JS启动进程,如Web Worker
  • JS和DOM渲染共用同一个线程,因为JS可修改DOM结构

异步和同步 同步和异步的区别是什么?

  • 基于JS是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行

异步应用场景(等待情况)

  • 网络请求,如AJAX图片加载
  • 定时任务,如setTimeout

手写Promise 加载一张图片

<script>
    const url = 'http://........................'

    function loadingImg(src) {
      return new Promise((resolve, reject) =>{
        const img = document.createElement('img');
        img.onload = () => {
          resolve(img)
        }
        img.onerror = () => {
          const err = new Error(`图片加载失败 ${src} `)
          reject(err)
        }
        img.src = src
      })
    }

    loadingImg(url).then(res => {
      console.log(res.width)
      return res
    }).then(res => {
      console.log(res.height)
    }).catch(err => {
      console.log(err)
    })
  </script>

Promise.png

Promise1.png

DOM

文档对象模型 这样的一个集合 DOM操作(Document Object Model)

properrty和attribute

  • property:修改对象属性,不会体现到HTML结构中
  • attribute:修改html属性,改变html结构
  • 两者都有可能引起DOM重新渲染

DOM性能

  • DOM操作非常"昂贵",应避免频繁的DOM操作
  • 对DOM查询做缓存
  • 将频繁操作改为一次性操作

BOM操作 (Browser Object Model)

如何识别浏览器的类型

  • navigator 识别浏览器的信息 简称UA
  • screen 一般查看浏览器的宽高
  • location 一般查看网址
  • history 前进后退等

事件

事件绑定

事件绑定.png

事件冒泡

  • 基于DOM树形结构
  • 事件会顺着出发元素往上冒泡
  • 应场场景:代理

事件冒泡.png

事件代理(瀑布流)

  • 代码简洁
  • 减少浏览器内存占用
  • 但是不要滥用
  • 事件代理是基于事件冒泡的

事件代理.png

通用事件绑定函数.png

Ajax

AJAXget请求.png

AJAXpost请求.png

手写简易ajax.png

手写AJAX

<script>
    function ajax(url) {
      const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4) {
            if(xhr.status === 200) {
              resolve(
                JSON.parse(xhr.responseText)
              )
            }else if(xhr === 404) {
              reject(new Error('404 not found'))
            }
          }
        }
        xhr.send(null)
      })
      return p
    }

    const url = 'http://xxxx'
    ajax(url).then(res => console.log(res)).catch(err => console.log(err))
  </script>

跨域

什么是跨域(同源策略)

  • ajax请求时,浏览器要求当前网页和server必须同源(安全)
  • 同源:协议、域名、端口,三者必须一

跨域

  • 所有的跨域,都必须经过server端允许和配合
  • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号

JSONP

访问xxx.com/ ,服务端一定返回一个html文件吗?

服务器可以任意动态拼接数据返回,只要符合html格式要求

JSONP.png

jQuery实现JSONP

jQuery实现JSONP.png

<script>可绕过跨域限制
服务器可以任意动态拼接数据返回
所以,<script>就可以获得跨域的数据,只要服务端愿意返回 <img src=""/>等等

CORS(服务端支持)

纯服务端去做的

服务端cors.png

存储

描述cookie localStorage sessionStorage区别

cookie

  • 本身用于浏览器和server通讯
  • 被“借用”到本地存储来
  • 可用document.cookie=“...”来修改
  • 缺点:最大存储4KB。http请求时需要发送到服务端,增加请求数据量只能用document.cookie=“...”来修改,太过简陋

localStorage和sessionStorage(一般用loaclStorage会更多一些)

  • HTML5专门为存储而设计,最大可存5M
  • API简单易用setItem getItem
  • 不会随着http请求被发送出去

localStorage数据会永远存储,除非代码或手动删除

sessionStorage数据只存在于当前会话,浏览器关闭则清空

防抖debounce

场景:

  • 监听一个输入框,文字变化后触发change事件
  • 直接用keyuo事件,则会频繁触发change事件
  • 防抖:用户输入结束或暂停时,才会触发change事件
<body>
    <input type="text" id="input1">

<script>
  const input1 = document.getElementById("input1");
  //--------------------案例---------------------------防止频繁触发 频繁请求有损性能
  // let timer = null
  // input1.addEventListener('keyup', function() {
  //   if(timer) {
  //     clearTimeout(timer)
  //   }
  //   timer = setTimeout(() => {
  //     console.log(input1.value) //模拟触发change事件
  //     //清空定时器
  //     timer = null
  //   }, 500)
    
  // })

  //手写防抖
  function debounce(fn, delay = 500) {
    //timer是在闭包中
    let timer = null

    return function() {
      if(timer) {
        clearTimeout(timer)//清空上一次输入的时间
      }
      timer = setTimeout(() => {
        fn.apply(this, arguments)
        timer = null
      }, delay)
    }
  }

  input1.addEventListener('keyup', debounce(function() {
    console.log(input1.value)
  }), 600)
</script>
<body/>

节流throttle

  • 拖拽一个元素时,要随时拿到该元素被拖拽的位置
  • 直接用drag事件,则会频繁触发,很容易导致卡顿
  • 节流:无论拖拽速度多块,都会每隔100ms触发一次
<body>
  <div id="div1" draggable="true">可拖拽</div>
  <script>
    //--------------------案例---------------------------
    const div1 = document.getElementById("div1");
    // let timer = null
    // div1.addEventListener('drag', function(e) {
    //   if(timer) {
    //     return
    //   }
    //   timer = setTimeout(() => {
    //     console.log(e.offsetX, e.offsetY);
    //     timer = null
    //   }, 100)
      
    // })


    //手写节流
        function throttle(fn, delay = 100) {
          let timer = null
          return function() {
            if(timer) {
              return
            }
            timer = setTimeout(() => {
              fn.apply(this, arguments)
              timer = null
            }, delay)
          }
        }
        div1.addEventListener('drag', throttle(function(e) {
          console.log(e.offsetX, e.offsetY);
        },200))
  </script>
</body>

基础算法

冒泡算法排序

// 冒泡排序
    /* 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。

   2.第一轮的时候最后一个元素应该是最大的一个。

   3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 */
    function bubbleSort(arr) {
      for (var i = 0; i < arr.length; i++) {
        for (var j = 0; j < arr.length; j++) {
          if (arr[j] > arr[j + 1]) {
            var temp = arr[j]
            arr[j] = arr[j + 1]
            arr[j + 1] = temp
          }
        }
      }
    }

    var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
    console.log(Arr, "before");
    bubbleSort(Arr)
    console.log(Arr, "after");

快速排序

  /*
    快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。
    然后递归调用,在两边都实行快速排序。  
    */
    
    function quickSort(arr) {
      if (arr.length <= 1) {
        return arr
      }
      var middle = Math.floor(arr.length / 2)
      var middleData = arr.splice(middle, 1)[0]

      var left = []
      var right = []
      
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] < middleData) {
          left.push(arr[i])
        } else {
          right.push(arr[i])
        }
      }

      return quickSort(left).concat([middleData], quickSort(right))
    }

    var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
    console.log(Arr, "before");
    var newArr = quickSort(Arr)
    console.log(newArr, "after");

插入排序

    function insertSort(arr) {
      // 默认第一个排好序了
      for (var i = 1; i < arr.length; i++) {
        // 如果后面的小于前面的直接把后面的插到前边正确的位置
        if (arr[i] < arr[i - 1]) {
          var el = arr[i]
          arr[i] = arr[i - 1]
          var j = i - 1
          while (j >= 0 && arr[j] > el) {
            arr[j+1] = arr[j]
            j--
          }
          arr[j+1] = el
        }
      }
    }

    var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
    console.log(Arr, "before");
    insertSort(Arr)
    console.log(Arr, "after");