var、let和const

一、var

变量提升

例一

console.log(a)
var a=1;var a;
console.log(a) // undefined
a = 1;

例二

var a=10;
var a;
console.log(a)var a;
var a;
var a=10;
console.log(a) // 10

函数声名提升

console.log(a(5))
function a(x) {
    return x*x
}function a(x) {
    return x*x
}
console.log(a(5)) //25

var的特点

  • var声名的变量会被提升到作用域的顶部
  • 在全局作用域下的var定义的白能量会被挂载到windows上,而letconst不会

二、let和const

暂时性死区

function test(){
    console.log(a)
    let a = 10
}
test() // 报错

原因:暂时性死区
解释:对于let和const定义的变量,不能在声明前使用。虽然变量在编译的环节中被告知这块作用域可以访问(提升),但是访问是受限的。

三、区别总结

  • 函数提升优于变量提升,函数提升会把整个函数挪到作用域顶部。而变量提升只会把变量声名放到作用域顶端,变量的定义依旧会放后
  • let和const因为暂时性死区,访问受限。不能在声明前使用
  • 在全局作用域声名,var会挂载到windows对象上,而let,const不会
  • let和const的区别是const不能再次赋值

原型继承和Class继承

原型继承

普通继承
function Person() {}
Person.prototype.name = "awsl"
person1 = new Person();
console.log(person1.name) // awsl
组合继承

在子类的构造函数中通过Parent.call(this) 继承父类的属性,然后改变子类的原型为 new Parent()来继承父类 的函数。

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = new Parent()

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true
继承组合继承

这种继承方式对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。

function parent(value) {
    this.val = value
}
parent.prototype.getValue = function() {
    console.log(this.value)
}
function child(value) {
    parent.call(this,value)
}
child.prototype = Object.create(parent.prototype,{
    c constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

Class继承

class parent {
    constructor(value){
        this.val = value
    }
    getValue(){
        console.log(this.val)
    }
}
class child extends parent {
    constructor(value){
        super(value) // 类似 parent.call(this,value)
        this.val = value
    }
}
let test = new child(1)
test.getValue() // 1
child instanceof parent //true

class 实现继承的核心在于使用 extends表明继承自哪个父类,并且在子类构造函数中必须调用super,因为这段代码可以看成Parent.call(this,value)

当然了,之前也说了在 JS 中并不存在类,class 的本质就是函数

模块化

为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?

使用模块化的原因

解决命名冲突
提供复用性
提高代码可维护性

一、立即执行函数

(function(g){
g.test = function(){}
  // 可以各种声名变量,函数都不会污染全局作用域
})

二、AMD 和 CMD

// AMD
define(['./a', './b'], function(a, b) {
  // 加载模块完毕可以使用
  a.do()
  b.do()
})
// CMD
define(function(require, exports, module) {
  // 加载模块
  // 可以把 require 写在函数体的任意地方实现延迟加载
  var a = require('./a')
  a.doSomething()
})

三、CommonJS

// a.js 中
moudule.exports = {
    a:1
}
//or
exports.a = 1

//b.js文件中
var moudule = require('./a.js')
moudule.a // ->log 1
require
var module = require('./a.js')
module.a 
// 这里其实就是包装了一层立即执行函数,这样就不会污染全局变量了,
// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {
    a: 1
}
// module 基本实现
var module = {
  id: 'xxxx', // 我总得知道怎么去找到他吧
  exports: {} // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports 
var load = function (module) {
    // 导出的东西
    var a = 1
    module.exports = a
    return module.exports
};
// 然后当我 require 的时候去找到独特的
// id,然后将要使用的东西用立即执行函数包装下,over

ES Module

ES Module 和 CommonJS的区别

  • CommonJS 支持动态导入,后者不支持
  • CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。后者是异步导入,因为用于浏览器,需要下载文件,固采取异步
  • CommonJS在导出时都是值拷贝,互不影响。如果想更新值必须重新导入一次。ES Module采取实时绑定的方式,导入导出的值都指向同一个内存地址,导入值会随着导出值变化
  • ES Module 会编译成require/exports来执行
// 引入模块 API
import XXX from './a.js'
import {XXX} from './a.js'
// 导出模块 API
export function a() {}
export default function() {}

Proxy

Proxy可以实现什么功能

Proxy是ES6中新增的功能,可以用来自定义对象中的操作

在 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式

let p = new Proxy(target,handler)
// target : 代表添加代理的对象
// handler: 用来自定义对象中的操作,比如自定义`set`或者`get`函数

通过Proxy来实现一个数据响应式

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}

let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2

map ,filter和reduce

map

生成一个新数组,遍历原数组,将每个元素拿出来做一些变换后放入新数组

let arr = [1,2,3].map(v => v+1) // -> [2,3,4]

map的回调函数接受三个参数:当前索引元素,索引,原数组

filter

新建一个数组,通过回调函数设置条件,将返回值为True的元素放入新数组。我们可以利用这个来过滤数组元素

let arr = [1,2,3,4,5,6].filter(item => item!==6)
console.log(arr) // [1,2,3,4,5]

回调函数接受三个参数:当前索引元素,索引,原数组

reduce

reduce可以通过回调函数,把数组中的元素转化为一个值

// 累加
let arr=[1,2,3],sum=0;
for(let i=0;i<arr.length;i++){
    sum += arr[i]
}let sum = arr.reduce((acc,cur) => pre+cur,0)

reduce有两个参数,回调函数和初始值
回调函数的参数:累计值(从初始值开始的每一轮运算结果),当前元素,当前索引,原数组