JavaScript

78 阅读10分钟

JavaScript知识点

JavaScript的数据类型

基本数据类型

  • 基础数据类型有numberstringbooleannullundefined
  • 基本数据类型放在栈中,存储的是值。
  • 基本数据类型赋值时会开辟一个新空间赋值给新值。

复杂数据类型

  • 复杂数据类型有objectarrayfunction
  • 复杂数据类型的值放在堆中,栈中存储的是内容对应的内存地址。
  • 复杂数据类型赋值时会赋值对应的地址,也就是两个变量指向堆内存中同一个对象。

JavaScript的类型转换

显示转换

  • Number()

    会把括号内的变量转为数字型。只要有一个字符无法转成数值,整个字符串就会被转为NaN

    Number(324) // 324
    
    // 字符串:如果可以被解析为数值,则转换为相应的数值
    Number('324') // 324
    
    // 字符串:如果不可以被解析为数值,返回 NaN
    Number('324abc') // NaN
    
    // 空字符串转为0
    Number('') // 0
    
    // 布尔值:true 转成 1,false 转成 0
    Number(true) // 1
    Number(false) // 0
    
    // undefined:转成 NaN
    Number(undefined) // NaN
    
    // null:转成0
    Number(null) // 0
    
    // 对象:通常转换成NaN(除了只包含单个数值的数组)
    Number({a: 1}) // NaN
    Number([1, 2, 3]) // NaN
    Number([5]) // 5
    
  • parentInt()

    与Number()相比没那么严格,转换碰到第一个无法转换的字符才停止。

    parseInt('32a3') //32
    
  • String()

    转为字符串的形式。

    // 数值:转为相应的字符串
    String(1) // "1"
    
    //字符串:转换后还是原来的值
    String("a") // "a"
    
    //布尔值:true转为字符串"true",false转为字符串"false"
    String(true) // "true"
    
    //undefined:转为字符串"undefined"
    String(undefined) // "undefined"
    
    //null:转为字符串"null"
    String(null) // "null"
    
    //对象
    String({a: 1}) // "[object Object]"
    String([1, 2, 3]) // "1,2,3"
    
  • Boolean()

    可以将任意类型的值转为布尔值,转换规则如下:

    实践一下:

    Boolean(undefined) // false
    Boolean(null) // false
    Boolean(0) // false
    Boolean(NaN) // false
    Boolean('') // false
    Boolean({}) // true
    Boolean([]) // true
    Boolean(new Boolean(false)) // true
    

隐式转换

  • 字符串拼接

    '5' + 1 // '51'
    '5' + true // "5true"
    '5' + false // "5false"
    '5' + {} // "5[object Object]"
    '5' + [] // "5"
    '5' + function (){} // "5function (){}"
    '5' + undefined // "5undefined"
    '5' + null // "5null"
    
  • 数值运算

    '5' - '2' // 3
    '5' * '2' // 10
    true - 1  // 0
    false - 1 // -1
    '1' - 1   // 0
    '5' * []    // 0
    false / '5' // 0
    'abc' - 1   // NaN
    null + 1 // 1
    undefined + 1 // NaN
    
  • 判断 在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数

    • undefined
    • null
    • false
    • 0
    • NaN
    • ""

    除了上面几种会被转化成false,其他都换被转化成true

深拷贝与浅拷贝

浅拷贝

将值复制一份,如果是基本类型数据就拷贝的就是基本类型的值,互不影响,但是如果有引用类型的数据则拷贝的是其地址,改变一个,另一个随之改变。常见的浅拷贝有:

  • Object.assign()
  • slice()、concat()
  • 扩展运算符 {...obj, ...obj1}

深拷贝

就是内存中开辟一块新地址,完整拷贝一份到新地址中,如果修改一个另一个则不会受到影响。常见的深拷贝有:

  • jQuery的extend方法:

    const $ = require('jquery');
    const obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    const obj2 = $.extend(true, {}, obj1);
    console.log(obj1.b.f === obj2.b.f); // false
    
  • JSON.stringify()

    const obj2=JSON.parse(JSON.stringify(obj1));
    

    但是这种方式存在弊端,会忽略undefinedsymbol函数

    const obj = {
        name: 'A',
        name1: undefined,
        name3: function() {},
        name4:  Symbol('A')
    }
    const obj2 = JSON.parse(JSON.stringify(obj));
    console.log(obj2); // {name: "A"}
    
  • 循环递归

    function deepClone(obj, hash = new WeakMap()) {
      if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
      if (obj instanceof Date) return new Date(obj);
      if (obj instanceof RegExp) return new RegExp(obj);
      // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
      if (typeof obj !== "object") return obj;
      // 是对象的话就要进行深拷贝
      if (hash.get(obj)) return hash.get(obj);
      let cloneObj = new obj.constructor();
      // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
      hash.set(obj, cloneObj);
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          // 实现一个递归拷贝
          cloneObj[key] = deepClone(obj[key], hash);
        }
      }
      return cloneObj;
    }
    

小结

前提为拷贝类型为引用类型的情况下:

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

闭包

本质上是在函数内部和函数外部搭建一座桥梁,让子函数可以访问父函数中的局部变量;

  • 好处就是可以保护变量不受外界污染
  • 缺点就是常驻内存,消耗性能,所以开发中很少使用闭包

作用域

  • 全局作用域 任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

    // 全局变量
    var greeting = 'Hello World!';
    function greet() {
      console.log(greeting);
    }
    // 打印 'Hello World!'
    greet();
    
  • 函数作用域 函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

    function greet() {
      var greeting = 'Hello World!';
      console.log(greeting);
    }
    // 打印 'Hello World!'
    greet();
    // 报错: Uncaught ReferenceError: greeting is not defined
    console.log(greeting);
    

    可见上述代码中在函数内部声明的变量或函数,在函数外部是无法访问的,这说明在函数内部定义的变量或者方法只是函数作用域

  • 块级作用域 ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

{
  // 块级作用域中的变量
  let greeting = 'Hello World!';
  var lang = 'English';
  console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);

this

  • 函数形式调用指向window
  • 方法形式调用指向调用对象
  • 构造函数调用指向构造函数的实例对象
  • 箭头函数看外层是否有函数,如果有外层函数的this就是箭头函数的,如果没有就是window

事件模型

on+事件名

  1. 不支持事件铺货,只支持事件冒泡

  2. 同一个事件类型只能绑定一次

  3. 绑定速度快

    DOM0级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行

addEventListener

  1. 可以在一个DOM元素上绑定多个事件处理器,各自并不会冲突

  2. 执行时机

    当第三个参数(useCapture)设置为true就在捕获过程中执行,反之在冒泡过程中执行处理函数

typeof和instanceof的区别

typeof

  1. typeof会返回一个变量的基本类型
  2. 虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'

instanceof

  1. instanceof返回的是一个布尔值
  2. instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型

构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数生成的对象

// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false

事件代理

事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素

当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数

适合事件委托的事件有:clickmousedownmouseupkeydownkeyupkeypress

使用事件委托存在两大优点:

  • 减少整个页面所需的内存,提升整体性能
  • 动态绑定,减少重复工作

但是使用事件委托也是存在局限性:

  • focusblur这些事件没有事件冒泡机制,所以无法进行委托绑定事件
  • mousemovemouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件

new

  1. 创建一个新的obj
  2. 通过构造函数的原型链把对象绑定过去
  3. 修改this的指向

ajax原理

是什么

是JavaScript和xml创建异步交互的网页开发技术,可以在不重复刷新的情况下局部刷新页面。通过XmlHttpRequest对象实现

过程

  1. 创建一个XmlHttpRequest对象
  2. 调用open方法,括号内填入请求方法和路径
  3. 调用send方法发送数据
  4. 通过onreadystatechange方法监听通信状态
  5. 接受结果,渲染到html页面中

apply、bind、call

  1. 都是修改this的指向问题
  2. 第一个参数都是要修改的对象,如果不传或者是undefined或null,默认为指向全局window
  3. apply第二个参数传递一个数组,bind和call传递值队列
  4. apply和call立即执行一次,bind返回的是绑定后的函数
  5. bind可以多次传数据,app和call只能传一次

事件循环

同步任务与异步任务

同步任务与异步任务的概念大家都不陌生,但是蛋蛋这么划分是不够的,先看一段代码。

console.log(1)

setTimeout(()=>{
    console.log(2)
}, 0)

new Promise((resolve, reject)=>{
    console.log('new Promise')
    resolve()
}).then(()=>{
    console.log('then')
})

console.log(3)

如果划分同步任务与异步任务,则最终结构是:1,new Promise,3,2,then。但结果是1,new Promise,3,then,2。因为异步任务又分为微任务和宏任务。

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

async与await

async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行。

正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值

不管await后面跟着的是什么,await都会阻塞后面的代码

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')

最后的结果是:script startasync1 startasync2promise1script endasync1 endpromise2settimeout

DOM的操作

创建节点

  1. createElement创建一个新元素
  2. createAttribute创建一个自定义属性节点

获取节点

  1. querySelector,获取第一个符合条件的元素
  2. querySelectorAll,返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表

更新节点

  1. innerHTML、innerText:更新文本内容
  2. style:更新css样式

添加节点

  1. appendChild:在父元素最后追加新的子节点
  2. insetBefore:在父元素最开始添加新的子节点
  3. setAttribute:在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值

删除节点

删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉

本地存储

  1. cookie大小4kb,localStrong和session大小5mb
  2. cokie在时间内有效,local永久有效,session在浏览器关闭前有效
  3. cookie会默认携带给服务器,另外两个保存在本地