var、let、const
变量提升
变量虽然在用之后再声明,但是能用,这是因为变量会提升
console.log(a) // undefined
var a = 1
=》等同于
var a
console.log(a) // undefined
a = 1
var a = 10
var a
console.log(a)
=》
var a
var a
a = 10
console.log(a)
函数也会:var
声明的会提升到顶部,函数会把整个挪到上面
console.log(a) // ƒ a() {}
function a() {}
var a = 1
=》
var a
function a() {}
console.log(a)
a=1
- var声明的变量会挂载在window里,let/const不会
- let/const会产生暂时性死区,在声明前使用会报referenceError
- let 和 const 作用基本一致,但是后者声明的变量不能再次赋值
- let和const不允许重复声明 为什么要有变量提升?
为了解决函数间互相调用的情况
function test1() {
test2()
}
function test2() {
test1()
}
test1()
因为不可能存在 test1 在 test2 前面然后 test2 又在 test1 前面。
我自己总结的let和const实现
babel在let定义的变量前加了道下划线,避免在块级作用域外访问到该变量,也可用自执行函数来模拟块级作用域
利用Object.defineProperty()来代理这个值,令其writable为false,使其不可被修改,不过代理对象属性值是可以被修改的哈
继承
原型链继承
缺点:引用属性被改所有实例受影响;创建实例不能传参
function Person(){}
functoin Child(){}
Child.prototype = new Person()
Child.prototype.constructor = Child
构造函数继承
构造函数内重复创建⽅法,导致内存占⽤过多。
function Child(){Parent.apply(this,argument)}
function Child(id){
Parent.apply(this,Array.form(argument).slice(1);this.id = id;)}
new Child(id,'xx')
组合继承
上面两种的组合 调用了两次构造函数,有重复操作
在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费
Parent.prototype.eat = function () {
console.log(`${this.name} - eat`);
};
function Child(){Parent.apply(this,argument)}
Child.prototype = new Parent()
Child.prototype.constructor = Child
const child = new Child(1)
child.eat() // 1
child instanceof Parent // true
寄生组合继承
组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。
以上继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数
function Child() {
Parent.apply(this, arguments);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child
class继承
class Parent {
constructor(value) {
this.val = value
}
getValue() {
console.log(this.val)
}
}
class Child extends Parent {
constructor(value) {
super(value)
}
}
class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)。
在 JS 中并不存在类,class 只是语法糖,本质还是函数。
class Person {}
Person instanceof Function // true
模块化
好处:
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
早期
在早期,用的是立即执行函数来避免命名冲突和污染全局变量
(function(globalVariable){
globalVariable.test = function() {}
// ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable)
AMD和CMD
通过require(['moduleA', 'moduleB'], function(moduleA, moduleB) {}使用, define定义
需要引入支持规范的脚本文件require.js
不能直接运行在node端
// AMD
define(['./a', './b'], function(a, b) {
// 加载模块完毕可以使用
a.do()
b.do()
})
// CMD
define(function(require, exports, module) {
// 加载模块
// 可以把 require 写在函数体的任意地方实现延迟加载
var a = require('./a')
a.doSomething()
})
//UMD
同构,同时运⾏在浏览器端和 Node.js ⾥⾯
检测module和define是否存在确定是什么环境,如何用对应的方式去定义模块
CJS
- 使用
// a.js
module.exports = {
a: 1
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
module.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 require = function (module) {
// 导出的东西
var a = 1
module.exports = a
return module.exports
};
// 然后当我 require 的时候去找到独特的id,
// 然后将要使用的东西用立即执行函数包装下,返回导出的对象
//对于目前的我来说,更认为是这样的
map = {
./a.js:'1'
}
list = {
1:'a=1'
}
var require = fucntion(module){
//module 为 ./a.js
//return list[map[module]]
return module.exports
}
exports
和 module.exports
享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports
赋值就会导致两者不再指向同一个内存地址,修改并不会对module.exports
起效
ES module
ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别
- CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
- 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() {}
更全的区别: mp.weixin.qq.com/s/TbwDzQcW8…
1.加载方式不同
使用 `require()` 和 `module.exports`或`exports`的属性赋值;
ESM 使用 `import` 和 `export (defalut)`,可以按需加载;
2.运行机制不同
es6中,js引擎对脚本静态分析时,把模块加载换成只读引用,read-only特性,不能被赋值;等待脚本真正执行的时候去异步加载模块,通过引用去模块中获取值,由于引用导入,模块内部值变化会影响外部值的导入
cjs是运行时同步加载,require会从硬盘中读文件,然后立刻执行代码,输出的是⼀个值的拷⻉,cjs会缓存模块结果
3.运行环境不同
cjs主要运行在服务端,cjs是node模块化规范
esm主要运行在浏览器端,js-core层面的
4.两文件互相依赖时
esm是中断执行,报引用错误;cjs暂停执行,无错误;
cjs this指向当前模块,es6指向undefind
CJS 使用 `require()` 和 `module.exports`或`exports`的属性赋值;ESM 使用 `import` 和 `export`
CommonJS 模块输出的是⼀个值的拷⻉,ES6 模块输出的是值的引⽤
CJS是运行时加载的,require会从硬盘中进入读操作,然后立刻执行代码,module.exports在脚本运行时生成。
ESM模块加载是异步的,编译时输出接口,也就是说js引擎对脚本静态分析时获取到的是一个只读的引用,执行时会异步下载代码,并行下载代码,但是是按顺序执行的。
ESM是js core层面的,其他是运行环境上的规范
proxy
get读取,set设置,has属性in对象,delete删除对象, ownKeys拦截Object.getOwnPropertyNames(proxy),Object.keys(proxy)、for...in循环 还有其他拦截Object原型上的方法,拦截 Proxy 实例作为函数调用和构造函数调用的操作,调用call,apply和new
优点: 性能和效果更好,兼容性差
作用:
自定义对象的操作,Vue3.0 使用来进行数据响应
vue3怎么设的
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
get(target, property, receiver) {
getLogger(target, property)
// 这句判断代码是新增的
if (typeof target[property] === 'object' && target[property] !== null) {
return new Proxy(target[property], handler);
} else {
return Reflect.get(target, property);
}
}
map, filter, reduce
map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。
[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]
另外 map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组
['1','2','3'].map(parseInt)
parseInt(string, radix) 解析的字符,
radix:几进制,介于 2 ~ 36 之间。
如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。 parseInt('11', 2)=>3
如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
第一轮遍历 parseInt('1', 0) -> 1
第二轮遍历 parseInt('2', 1) -> NaN
第三轮遍历 parseInt('3', 2) -> NaN
filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素
let array = [1, 2, 4, 6]
let newArray = array.filter(item => item !== 6)
console.log(newArray) // [1, 2, 4]
和 map 一样,filter 的回调函数也接受三个参数,用处也相同。
- reduce juejin.cn/post/684490…
如果我们想实现一个功能将函数里的元素全部相加得到一个值
const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)
对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程
首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入
所以在第二次执行回调函数时,相加的值就分别是 1 和 2,以此类推,循环结束后得到结果 6
想必通过以上的解析大家应该明白 reduce 是如何通过回调函数将所有元素最终转换为一个值的,当然 reduce 还可以实现很多功能,接下来我们就通过 reduce 来实现 map 函数
const arr = [1, 2, 3]
const mapArray = arr.map(value => value * 2)
const reduceArray = arr.reduce((acc, current) => {
acc.push(current * 2)
return acc
}, [])
console.log(mapArray, reduceArray) // [2, 4, 6]
- 本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情