JS基础总结

484 阅读5分钟

事件代理

  • 根据事件冒泡或事件捕获的机制来实现的
事件代理就是利用事件冒泡或事件捕获的机制把一系列的内层元素事件绑定到外层元素
1、代码简洁
2、减少浏览器内容占用
3、不要滥用

js类型

  • 常见的值类型
1、未定义undefined
2、字符串String
3、数字Number
4、布尔Boolean
5Symbol
  • 引用类型
1、对象 Object
2、数组 Array
3null // 特殊引用类型,指针指向为空地址
4、函数 function(){}
 //特殊引用类型,但不用于存储数据,所以没有“拷贝复制函数”这一说

typeof 能判断哪些类型

/**
* 1、识别所有值类型
* 2、识别函数
* 3、判断是否时引用类型(不可再细分)
* 4、
*/
let a; 
const str = 'abc';
const n = 100
const b = true
const s = Symbol('s')

typeof a // undefined
typeof str // string
typeof n // number
typeof b // boolean
typeof s // symbol

// 判断函数
typeof console.log // function
typeof function () {} // function

// 能识别引用类型(不能再继续识别)
typeof null // object
typeof ['a', 'b'] // object
typeof { x: 100 } // object

何时使用 === 何时使用 ==

值类型和引用类型的区别

值类型定义之后时各论各的,值不会相会干扰
引用类型,a赋值了b,b改变了a也会该变了

// 值类型
let a = 100
let b = a
a = 200
console.log(b) // 100

// 引用类型
let a = { age: 24 }
let b = a
b.age = 25
console.log(a.age) // 25

手写js深拷贝

/**
 * 深拷贝
 * @param {*} obj 
 */
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
}

类型转换

// 字符串拼接
const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
// == 运算符

100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
/**
* 除了 == null之外,其他都一律用 ===,例如
*/
const obj = {x: 100}
if(obj.a == null) {}
// 相等于 if(obj.a === null || obj.a === undefined) {}

var 和 let const 的区别

  • var 是ES5语法,let const 是ES6语法;var有变量提升
// 在执行console.log(a)之前,先把a变量定义为undefined
console.log(a) // undefined
var a = 100

console.log(b) // 报错
let b = 10
  • var 和 let 是变量,可修改;const是常量,不可修改
  • let const 有块级作用域,var 没有
for(var i = 0; i < 10; i++) {
	var j = i + 1
}
console.log(j) // 10
for(let k = 0; K < 10; k++) {
	let z = k + 1
}
console.log(z) // 报错

什么是作用域

概念:一个变量在一个合里使用范围区域,如果不在合法区域就会报错
  • 全局作用域:一个变量没有受到函数的约束和块级的约束,可以全局使用:如 window,document

  • 函数作用域:只能在当前函数中使用

  • 块级作用域(ES6新增)

// es6 块级作用域
if(true) {
  let x = 100
} 
console.log(x) // 报错

自由变量

  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用域,一层一层一次寻找,直到找到为止
  • 如果到全局作用域都没有找到,则报错 xx is not defined
let a = 0
function fn1() {
  let a1 = 100
  function fn2() {
    let a2 = 200    
    function fn3() {
      let a3 = 300
      // a一层一层向上查找,知道找到为止
      return a + a1 + a2 + a3
    }
    fn3()
  }
  fn2()
}
fn1()

闭包

  • 作用域应用的特殊情况,分别有两种情况的表现形式
// 函数作为返回值被返回
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
// 闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找不是在执行的地方

// 闭包隐藏数据,只提供API
function createCache() {
  const data = {} // 闭包中的数据,被隐藏,不被外界访问
  return {
    set: function(key, val) {
      data[key] = val
    },
    get: function(key) {
      return data[key]
    }
  }
}
// 直接修改data会报错
const c = createCache();
c.set('a', 100)
console.log(c.get('a'))

this

  • 作为普通函数
  • 使用 call apply bind
  • 作为对象方法被调用
  • 在class方法中调用
  • 在箭头函数
this取什么值是在函数执行的时候确认的,不是在函数定义时确认的
function fn1(){
	console.log(this)
}
fn1() // window
fn1.call({x: 100}) // {x: 100}
const fn2 = fn1.bind({x: 200})
fn2() // {x: 200} 

const guyue = {
	name: '古月',
	hi() {
		// this 即当前对象
		console.log(this)
	},
	hei() {
		setTimeout(function() {
			// this 指向window
			console.log(this)
		})
        // 箭头函数的this取值,是取上级作用域的值
		setTimeout(() => {
			console.log(this)
		})
	}
}
class People {
  constructor(name) {
    this.name = name
    this.age = 20
  }
  sayHi() {
    console.log(this)
  }
}
const zhangsan = new People('张三')
zhangsan.sayHi() // zhangsan 对象

手写一个bind

Function.prototype.bind1 = function() {
  // arguments 是个类数组对象,其包含一个 length 属性
  // 解析参数成为数组
  const arg = Array.prototype.slice.call(arguments);

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

  // this 调用这个方法的函数
  const self = this;
  // 返回一个函数
  return function() {
      return self.apply(t, arg)
  }
}

异步和同步

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

同源策略

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

event loop(事件循环/事件轮询)

  • JS是单线程运行的
1、从前到后,一行一行执行
2、如果某一行执行报错,则停止下面代码的执行
3、先把同步代码执行完,在执行异步
  • 异步要基于回调来实现
  • event loop 就是异步回调的实现原理
1、同步代码,一行一行放在Call Stack执行
2、遇到余部,会先“记录”下,等待时机(定时、网络请求等)
3、时机到了,就移动到Callback Queue
4、如Call stack为空(即同步代码执行完)Event Loop开始工作
5、轮询查找Callback Queue,如有则移动到Call Stack执行
6、然后继续轮询查找(永动机一样)

DOM 事件和event loop

  • JS是单线程
  • 异步(setTimeout, ajax)等使用回调,基于event loop
  • DOM事件也使用回调,基于event loop,什么时候用户点击,再执行,dom事件不是异步

宏任务 marcoTask 和 微任务 microTask

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件
  • 微任务:Promise async/await
  • 微任务执行时机比宏任务要早
// event loop 和 DOM渲染
1、js是单线程的,而且和DOM渲染公用一个线程
2、js执行的时候,得留一些时机供DOM渲染
3Call Stack(调用栈) 空闲,尝试DOM的渲染,渲染完之后,再触发Event Loop
console.log(100)
setTimeout(() => {
  console.log(200)
})
Promise.resolve().then(() => {
  console.log(300)
})
console.log(400)
/*
* 打印顺序
* 100, 400, 300, 200
*/

宏任务和微任务的区别

  • 宏任务:DOM渲染后触发,如setTimeout
  • 微任务:DOM渲染前触发,如Promise
  • 本质的区别,微任务是ES6语法规定,宏任务是浏览器规定的
  • 1、Call Stack清空之后, 2、执行当前的micro task queue(微任务队列), 3、尝试DOM渲染, 4、触发Event Loop(宏任务)
let dom 
for(let i = 0; i < 3; i++){
	const  p1=document.createElement('p')
	p1.innerText = `这是一文字${i}`
	dom = document.getElementById('ida')
	dom.appendChild(p1)
}
Promise.resolve().then(() => {
	console.log(dom.children.length)
	alert('Promise')
})
setTimeout(() => {
	console.log(dom.children.length)
	alert('setTimeout')
}, 0)

前端性能优化

  • 加载更快
1、减少资源体积:压缩代码
  静态资源加hash后缀,根据文件内容计算 hash
  文件内容不变,则hash不变,则url不变
  url和文件不变,则会自动触发http缓存机制,返回304
2、减少访问次数:合并代码,SSR服务端渲染,缓存
3、使用更快的网络:CDN
  • 让渲染更快
- CSS 放在 head,JS放在 body 最下面
- 尽早开始执行JS,用 DOMContentLoaded 触发
- 懒加载(图片懒加载,上滑加载更多)
- 对 DOM 查询进行缓存
- 频繁DOM操作,合并到一起插入 DOM 结构
- 节流 throttle 防抖 debounce

手写数组平铺

function flat(arr) {
  const isDeep = arr.some(item => item instanceof Array)
  if(!isDeep) {
    return arr
  }
  const res = Array.prototype.concat.apply([], arr)
  return flat(res)
}

数组去重

function unique(arr) {
  const res = []
  arr.forEach(item => {
    if(res.indexOf(item) < 0) {
      res.push(item)
    }
  });
  return res
}
// 使用 Set (无序,不能重复)
function unique2(arr) {
  const res = new Set(arr)
  return [...res]
}