变量

  • 值类型: string, number, boolean, undefined, null, symbol

  • 引用类型:object, array, function

  • typeof 操作符: 识别值类型,能判断 function,能识别引用类型,但不能识别具体(细分)的引用类型。

    • 识别值类型: string, number, boolean, undefined, symbol
    • 识别函数: function
    • 识别引用类型: object
  • instanceof 操作符:识别引用类型,能识别具体(细分)的引用类型,不能识别值类型。

// js 类型判断
function getType(value) {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}

// 深拷贝 JSON.parse(JSON.stringify())
// 不足:无法拷贝函数,无法拷贝 undefined,无法拷贝循环引用
// JSON.stringify() 方法无法处理某些特殊的 JavaScript 对象,如 RegExp、Error、Date 等,这些对象会被转换成空对象。
// 对象的原型链上的属性会丢失

// 深拷贝
function deepClone(value) {
  const type = getType(value)
  let result
  if (type === 'object') {
    result = {}
  } else if (type === 'array') {
    result = []
  } else {
    // 值类型直接返回
    return value
  }
  for (let key in value) {
    if (value.hasOwnProperty(key)) {
      result[key] = deepClone(value[key])
    }
  }
  return result
}

原型与原型链

  • __proto__:隐式原型,每个对象都有 __proto__ 属性,指向创建它的构造函数的原型对象。

  • prototype:显式原型,每个函数都有 prototype 属性,指向一个对象,这个对象包含可以由特定类型的所有实例共享的属性和方法。

  • constructor:构造函数,每个原型都有 constructor 属性,指向关联的构造函数。

  • 原型链:当访问一个对象的属性时,会先在这个对象本身上查找,如果没有找到,就会去它的 __proto__ 隐式原型上查找,如果还没有找到,就会去 __proto____proto__ 上查找,一直找到 Object__proto__ 为止。

  • Object.prototype:所有对象的原型,Object.prototype.__proto__null

  • Function.prototype:所有函数的原型,Function.prototype.__proto__Object.prototype

  • hasOwnProperty:判断一个对象是否有自己的属性,而不是继承自原型链。

  • class 本质:class 只是一个语法糖,本质还是原型链(函数)。

  • new 操作符:创建一个新对象,将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象),执行构造函数中的代码,返回新对象。

  • instanceof 操作符:判断一个对象是否是另一个对象的实例,判断原理是判断对象的原型链上是否有这个构造函数的 prototype

// 简单手写 jQuery

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
    this.selector = selector
  }
  // 获取 DOM 元素
  get(index) {
    return this[index]
  }

  // 遍历 DOM 元素
  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'))

作用域与闭包

  • 作用域:变量和函数的可访问范围,分为全局作用域和局部作用域。

  • 作用域链:当访问一个变量时,会先在当前作用域查找,如果没有找到,就会去父级作用域查找,一直找到全局作用域为止。

  • 闭包:函数执行时,会创建一个执行上下文,当函数执行完毕,执行上下文会被销毁,但是闭包可以使函数执行完毕后,执行上下文不被销毁,从而使函数内部的变量一直被访问。简单来说,闭包就是能够读取其他函数内部变量的函数。

  • 闭包的作用:1. 读取函数内部的变量;2. 让这些变量的值始终保持在内存中。

  • 闭包的缺点:1. 会造成内存泄漏;2. 会造成性能问题。

  • this 指向问题:

    • 作为普通函数调用,指向全局对象 window

    • 作为对象方法调用,指向调用对象。

    • 作为构造函数调用,指向实例对象。

    • 作为 class 的方法调用,指向实例对象。

    • 使用 call, apply, bind 调用,指向绑定的对象。

    • 箭头函数,指向定义时所在的对象。

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

const c = createCache()
c.set('a', 100)
console.log(c.get('a'))

// 函数作为返回值
// function create() {
//     const 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

// 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
// 不是在执行的地方!!!

// 模拟 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)
  }
}

function fn1(a, b, c) {
  console.log('this', this)
  console.log(a, b, c)
  return 'this is fn1'
}

const fn2 = fn1.bind1({ x: 100 }, 10, 20, 30)
const res = fn2()
console.log(res) // this is fn1

异步

  • 异步:不等待结果,直接执行后面的代码。

  • 同步:等待结果,再执行后面的代码。

  • JS 中的异步:

    • 宏任务:setTimeout, setInterval, I/O, UI rendering

    • 微任务:Promise, process.nextTick, Object.observe, MutationObserver

  • 事件循环:

    • JS 是单线程的,同一时间只能做一件事。

    • 事件循环:不断从任务队列中取出任务,放到执行栈中执行。

    • 宏任务队列:每次执行栈执行完毕,就会从宏任务队列中取出一个任务执行。

    • 微任务队列:每次执行栈执行完毕,就会从微任务队列中取出所有任务执行。

    • 执行栈:同步任务会放到执行栈中执行。

    • 宏任务队列和微任务队列是在执行栈执行完毕后,才会执行。

    • 微任务队列的优先级高于宏任务队列。

    • setTimeout 会将回调函数放到宏任务队列中。

    • Promise 会将回调函数放到微任务队列中。

    • process.nextTick 会将回调函数放到微任务队列中。

    • Object.observe 会将回调函数放到微任务队列中。

    • MutationObserver 会将回调函数放到微任务队列中。

    • setImmediate 会将回调函数放到宏任务队列中。

    • I/O 会将回调函数放到宏任务队列中。

    • UI rendering 会将回调函数放到宏任务队列中。

    • setInterval 会将回调函数放到宏任务队列中。

    • requestAnimationFrame 会将回调函数放到宏任务队列中。

    • process.nextTick 会将回调函数放到微任务队列中。

    • Object.observe 会将回调函数放到微任务队列中。

    • MutationObserver 会将回调函数放到微任务队列中。

    • Promise 会将回调函数放到微任务队列中。

    • setTimeout 会将回调函数放到宏任务队列中。

    • setImmediate 会将回调函数放到宏任务队列中。

  • Promise 的状态:

    • pending:进行中。

    • fulfilled:已成功。

    • rejected:已失败。

  • Promise 的特点: 1. 对象的状态不受外界影响;2. 一旦状态改变,就不会再变。

  • Promise 的缺点:1. 无法取消 Promise,一旦新建它就会立即执行,无法中途取消;2. 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部;3. 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

  • async/awaitPromise 的语法糖。

  • for...of: 用于遍历可迭代对象的元素。

// 手写用promise加载图片
function loadImageAsync(url) {
  return new Promise(function (resolve, reject) {
    // 创建一个image对象
    const image = new Image()
    // 绑定事件
    image.onload = function () {
      resolve(image)
    }
    // 绑定错误事件
    image.onerror = function () {
      reject(new Error('Could not load image at' + url))
    }
    // 设置图片地址
    image.src = url
  })
}

// 调用
loadImageAsync('https://www.baidu.com/img/bd_logo1.png')
  .then(function (image) {
    console.log(image)
  })
  .catch(function (err) {
    console.log(err)
  })

// setTimeout 试题
console.log(1)
setTimeout(function () {
  console.log(2)
}, 1000)
console.log(3)
setTimeout(function () {
  console.log(4)
}, 0)
console.log(5)

// Output: 1 3 5 4 2

// promise then 和 catch
Promise.resolve()
  .then(() => {
    console.log(1)
  })
  .catch(() => {
    console.log(2)
  })
  .then(() => {
    console.log(3)
  })

// output: 1 3

Promise.resolve()
  .then(() => {
    console.log(1)
    throw new Error('error1')
  })
  .catch(() => {
    console.log(2)
  })
  .then(() => {
    console.log(3)
  })

// output: 1 2 3

Promise.resolve()
  .then(() => {
    console.log(1)
    throw new Error('error1')
  })
  .catch(() => {
    console.log(2)
  })
  .catch(() => {
    console.log(3)
  })

// output: 1 2

// async/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')
}, 0)

async1()

new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promise2')
})

console.log('script end')

// ounput : script start async1 start async2 promise1 script end async1 end promise2 setTimeout

webApi-dom

  • dom: 文档对象模型,是 HTMLXML 的应用程序接口。

  • dom 的作用:1. 使 JavaScript 能够读取和操作 HTML 文档的内容;2. 使 JavaScript 能够操作 CSS 样式。

  • dom 常用 api:

    • document.getElementById(): 通过 id 获取元素。
    • document.getElementsByTagName(): 通过标签名获取元素。
    • document.getElementsByClassName(): 通过类名获取元素。
    • document.querySelector(): 通过选择器获取元素。
    • document.querySelectorAll(): 通过选择器获取元素。
    • document.createElement(): 创建元素。
    • document.createTextNode(): 创建文本节点。
    • document.createAttribute(): 创建属性节点。
    • document.appendChild(): 添加子节点。
    • document.removeChild(): 删除子节点。
    • document.replaceChild(): 替换子节点。
  • attrproperty 区别

    • 相同: attrproperty 都能获取到元素的属性值。

    • 不同: attr 获取的是 html 属性值,property 获取的是 dom 对象的属性值。

  • 减少dom 操作: 1. 缓存 dom 查询结果;2. 使用 documentFragment;3. 使用 cloneNode

  • dom 本质 : domjs 操作 html 的接口,dom 本质是一个树形结构。

  • 重排与重绘:

      1. 重排:当 dom 的变化影响了元素的几何属性(宽和高)时,浏览器需要重新计算元素的几何属性,重新构建 dom 树,这个过程称为重-排;
      1. 重绘:当元素的几何属性(宽和高)发生变化时,浏览器会根据元素的新属性重新绘制元素,这个过程称为重绘。
    • 重排一定会引起重绘,重绘不一定会引起重排。

    • 减少重排与重绘:1. 使用 class 一次性修改样式;2. 将需要多次重排的元素,设置为绝对定位,使其脱离文档流,重排的时候不会影响到其他元素。

  • dom 事件流:事件捕获阶段 -> 事件目标阶段 -> 事件冒泡阶段。

  • dom 事件流的三个阶段:

    • 事件捕获阶段:事件从 window 开始,向下传播到事件的目标元素。
    • 事件目标阶段:事件到达目标元素。
    • 事件冒泡阶段:事件从目标元素开始,向上冒泡到 window
// dom 属性

// const div1 = document.getElementById('div1')
// console.log('div1', div1)

// const divList = document.getElementsByTagName('div') // 集合
// console.log('divList.length', divList.length)
// console.log('divList[1]', divList[1])

// const containerList = document.getElementsByClassName('container') // 集合
// console.log('containerList.length', containerList.length)
// console.log('containerList[1]', containerList[1])

// const pList = document.querySelectorAll('p')
// console.log('pList', pList)

// const pList = document.querySelectorAll('p')
// const p1 = pList[0]

// // property 形式
// p1.style.width = '100px'
// console.log( p1.style.width )
// p1.className = 'red'
// console.log( p1.className )
// console.log(p1.nodeName)
// console.log(p1.nodeType) // 1

// // attribute
// p1.setAttribute('data-name', 'imooc')
// console.log( p1.getAttribute('data-name') )
// p1.setAttribute('style', 'font-size: 50px;')
// console.log( p1.getAttribute('style') )

// dom 操作
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')

// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)

// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)

// 获取父元素
console.log(p1.parentNode)

// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log(div1.childNodes)
const div1ChildNodesP = Array.prototype.slice
  .call(div1.childNodes)
  .filter((child) => {
    if (child.nodeType === 1) {
      return true
    }
    return false
  })
console.log('div1ChildNodesP', div1ChildNodesP)

div1.removeChild(div1ChildNodesP[0])

//  事件
// 通用的事件绑定函数
// function bindEvent(elem, type, fn) {
//     elem.addEventListener(type, fn)
// }
function bindEvent(elem, type, selector, fn) {
  if (fn == null) {
    fn = selector
    selector = null
  }
  elem.addEventListener(type, (event) => {
    const target = event.target
    if (selector) {
      // 代理绑定
      if (target.matches(selector)) {
        fn.call(target, event)
      }
    } else {
      // 普通绑定
      fn.call(target, event)
    }
  })
}

// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
  // console.log(event.target) // 获取触发的元素
  event.preventDefault() // 阻止默认行为
  alert(this.innerHTML)
})

// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
  event.preventDefault()
  alert(this.innerHTML)
})

// const p1 = document.getElementById('p1')
// bindEvent(p1, 'click', event => {
//     event.stopPropagation() // 阻止冒泡
//     console.log('激活')
// })
// const body = document.body
// bindEvent(body, 'click', event => {
//     console.log('取消')
//     // console.log(event.target)
// })
// const div2 = document.getElementById('div2')
// bindEvent(div2, 'click', event => {
//     console.log('div2 clicked')
//     console.log(event.target)
// })

// ajax 封装
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.status === 404 || xhr.status === 500) {
          reject(new Error('404 not found'))
        }
      }
    }
    xhr.send(null)
  })
  return p
}

const url = '/data/test.json'
ajax(url)
  .then((res) => console.log(res))
  .catch((err) => console.error(err))

webApi-bom

  • BOMBrowser Object Model,浏览器对象模型,提供了独立于内容而与浏览器窗口进行交互的对象结构,BOM 由多个对象组成,如 windowlocationnavigatorscreenhistory 等。

  • navigator:浏览器信息

    • navigator.userAgent:浏览器信息字符串
  • screen: 屏幕信息

    • screen.width
    • screen.height
    • screen.colorDepth
  • location:地址信息

    • location.href: 整个地址栏
    • location.protocol: 协议
    • location.host: 主机
    • location.pathname: 路径
    • location.search: 查询字符串
    • location.hash: 锚点
    • location.reload(): 刷新
    • location.assign(): 跳转
    • location.replace(): 替换
  • history:历史记录

    • history.back(): 后退
    • history.forward(): 前进
    • history.go(): 前进或后退

安全

  • XSS:跨站脚本攻击,指的是攻击者在网站上注入恶意的客户端代码,当用户浏览网页时,嵌入其中 Web 页面中 JavaScript 会被执行,从而达到攻击的目的。

  • XSS 防范

    • 转义字符
    • CSP:内容安全策略,是一种额外的安全层,用于检测并削弱某些特定类型的攻击,包括 XSS 攻击。
    • HttpOnly:设置 cookieHttpOnlyHttpOnly 是微软对 cookie 提出的一个安全的改进,使用 HttpOnly 可以禁止 Javascript 访问带有 HttpOnly 属性的 cookie,可以防止 XSS 攻击窃取 cookie
    • 输入检测:对用户输入的内容进行检测,对于不符合要求的输入,直接过滤或者提示用户重新输入。
  • CSRF:跨站请求伪造,攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息、购买商品、虚拟货币转账等。

  • CSRF 防范

    • Cookie 验证
    • Referer 验证
    • Token 验证

网站性能优化

  • 让加载更快:

    • 减少资源体积: 压缩代码
    • 减少访问次数: 合并代码,ssr 服务器端渲染,缓存
    • 使用更快的网络,CDN
  • 让渲染更快:

    • css 放在 head, js 放在 body 最下面
    • 尽早开始执行 js,用 DOMContentLoaded 触发
    • 图片懒加载
    • 对 DOM 查询进行缓存
    • 频繁 DOM 操作,合并到一起插入 DOM 结构

面试题

// 数组方法中 forEach、map、filter、reduce 的区别
// forEach:没有返回值,不会改变原数组
// map:返回一个新数组,不会改变原数组
// filter:返回一个新数组,不会改变原数组
// reduce:返回一个新数组,不会改变原数组

//  数组中纯函数的方法: map、filter、reduce、some、every、forEach
//  数组中非纯函数的方法:push、pop、shift、unshift、splice、sort、reverse

// 什么是纯函数: 相同的输入永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态


// 将url 参数解析为js 对象
function queryToObj() {
  const obj = {};
  // 获取url 参数 去掉?
  const search = location.search.substr(1);
  search.split('&').forEach((item) => {
    const [key, value] = item.split('=');
    obj[key] = value;
  }
  return obj;
}

// 手写数组 flatern 考虑多层级 拍平
function flatern(arr) {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatern(cur) : cur);
  }, []);
}

// 手写数组 map
Array.prototype.map = function (fn) {
  const arr = [];
  for (let i = 0; i < this.length; i++) {
    arr.push(fn(this[i], i, this));
  }
  return arr;
};

// 手写数组 filter
Array.prototype.filter = function (fn) {
  const arr = [];
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) {
      arr.push(this[i]);
    }
  }
  return arr;
};

// 手写数组 reduce
Array.prototype.reduce = function (fn, init) {
  let pre = init;
  let i = 0;
  if (init === undefined) {
    pre = this[0];
    i = 1;
  }
  for (; i < this.length; i++) {
    pre = fn(pre, this[i], i, this);
  }
  return pre;
};

// 手写数组 forEach
Array.prototype.forEach = function (fn) {
  for (let i = 0; i < this.length; i++) {
    fn(this[i], i, this);
  }
};

// 手写数组 some
Array.prototype.some = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) {
      return true;
    }
  }
  return false;
};

// reduce 用法
// 1. 数组求和
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((pre, cur) => {
  return pre + cur;
}, 0);

// 2. 数组去重
const arr = [1, 2, 3, 4, 5, 1, 2, 3];
const newArr = arr.reduce((pre, cur) => {
  if (!pre.includes(cur)) {
    return pre.concat(cur);
  }
  return pre;
}, []);

// 3. 数组对象属性求和
const arr = [
  { name: '张三', age: 18 },
  { name: '李四', age: 19 },
  { name: '王五', age: 20 },
];

const sum = arr.reduce((pre, cur) => {
  return pre + cur.age;
}, 0);

// 4. 数组对象属性分组
const arr = [
  { name: '张三', age: 18 },
  { name: '李四', age: 19 },
  { name: '王五', age: 20 },
];

const group = arr.reduce((pre, cur) => {
  if (pre[cur.age]) {
    pre[cur.age].push(cur);
  } else {
    pre[cur.age] = [cur];
  }
  return pre;
}, {});

// 5. 数组拍平
const arr = [1, 2, [3, 4], [5, 6, [7, 8]]];

const flatern = arr.reduce((pre, cur) => {
  return pre.concat(Array.isArray(cur) ? flatern(cur) : cur);
}, []);


// 节流: 一段时间内只执行一次,如果在这段时间内再次触发,则不执行
function throttle(fn, delay) {
  let timer = null;
  return function () {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        timer = null;
      }, delay);
    }
  };
}

// 防抖: 一段时间内只执行一次,如果在这段时间内再次触发,则重新计时
function debounce(fn, delay) {
  let timer = null;
  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
}

// 手写 new
function myNew(fn, ...args) {
  const obj = Object.create(fn.prototype);
  const result = fn.apply(obj, args);
  return result instanceof Object ? result : obj;
}

// 手写 instanceof
function myInstanceof(left, right) {
  let proto = left.__proto__;
  while (true) {
    if (proto === null) {
      return false;
    }
    if (proto === right.prototype) {
      return true;
    }
    proto = proto.__proto__;
  }
}

// 手写 call
Function.prototype.myCall = function (context, ...args) {
  context = context || window;
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
};

// 手写 apply
Function.prototype.myApply = function (context, args) {
  context = context || window;
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
};


// 手写 bind
Function.prototype.myBind = function (context, ...args) {
  const self = this;
  return function (...args2) {
    return self.apply(context, args.concat(args2));
  };
};

正则图文

regex

思维笔记

参考资料