var,let以及const区别
在全局作用域下使用let,const声明变量,变量是不会被挂载到window上的;这一点跟var不同。
let,const声明的变量,不能在声明前使用,存在暂时性死区;var可以进行变量提升。
暂时性死区:虽然变量在编译的环节中被告知在这块作用域中可以访问,但是访问是受限制的。
函数提升:函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部
let
和 const
作用基本一致,但是后者声明的变量不能再次赋值。
原型继承和class继承
class继承:js中并不存在类,class是语法糖,本质上还是函数。
class
实现继承的核心在于使用 extends
表明继承自哪个父类,并且在子类构造函数中必须调用 super
,因为这段代码可以看成 Parent.call(this, value)
。
class Parent { constructor(value) { this.val = value } getValue() { console.log(this.val) } } class Child extends Parent { constructor(value) { super(value) } } let child = new Child(1) child.getValue() // 1 child instanceof Parent // true
组合继承:最常见的继承方式,核心是在子类的构造函数中通过Parent.call(this)来继承父类的属性,改变子类的原型为new Parent()来继承父类函数。
unction 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.val) } function Child(value) { Parent.call(this, value) } Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }) const child = new Child(1) child.getValue() // 1 child instanceof Parent // true
模块化
实现模块化的目的:代码复用;可维护性;解决命名冲突
模块化的几种方式:
1)立刻执行函数
使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题。
(function(globalVariable){ globalVariable.test = function() {} // ... 声明各种变量、函数都不会污染全局作用域 })(globalVariable)
2)AMD和CMD:目前这两种实现方式已经很少见到
异步模块定义(AMD)是Asynchronous Module Definition的缩写,是 RequireJS 在推广过程中对模块定义的规范化产出。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。
通用模块定义(CMD)是Common Module Definition的缩写,是SeaJS 在推广过程中对模块定义的规范化产出。
CMD 推崇依赖就近,AMD 推崇依赖前置。
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... }) // AMD 默认推荐的是 define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() // 此处略去 100 行 b.doSomething() ... })
3)CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它。
“在 CommonJs 的模块化规范中,每一个文件就是一个模块,拥有自己独立的作用域、变量、以及方法等,对其他的模块都不可见。
CommonJS规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(module.exports)是对外的接口。
加载某个模块,其实是加载该模块的 module.exports 属性。require 方法用于加载模块。”
作者:一俢
链接:http://www.imooc.com/article/285854
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
//moudle-a.js moudle.exports = { a: 1}; //moudle-b.js var ma = require('./moudle-a'); var b = ma.a + 2;module.exports ={ b: b };
4)ES模块
- 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() {}
Proxy
概述
Proxy代理 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式。
基本用法
Proxy代理
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
let target = { // target 可以为空对象,调用 set 方法,向目标对象中添加了 属性
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
return true; //严格模式下,set代理如果没有返回true,就会报错。
}
}
let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
实例方法
1、get(target, propKey, receiver) 用于拦截某个属性的读取(read)操作,就是在读取目标对象的属性之前,搞点事情。
参数:
target:目标对象
property:属性名
receiver:操作行为所针对的对象,一般指proxy实例本身
let exam={};
let proxy = new Proxy(exam, { get(target, propKey, receiver) { console.log('Getting ' + propKey); return target[propKey]; } })
console.log(proxy.name);//undefined
2、set(target, propKey, value, receiver) 用来拦截目标对象的赋值(write)操作
参数:
target:目标对象
propertyName:属性名
propertyValue:属性值
receiver:Proxy实例本身
let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); return false; } if (value > 200) { throw new RangeError('The age seems invalid'); return false; } } // 对于满足条件的 age 属性以及其他属性,直接保存 obj[prop] = value; return true; } }; let proxy= new Proxy({}, validator) proxy.age = 100; proxy.age // 100 proxy.age = 'oppps' // 报错 proxy.age = 300 // 报错
3、apply(target, ctx, args) 用于拦截函数的调用、call 和 reply 操作。
target 表示目标对象,
ctx 表示目标对象上下文,
args 表示目标对象的参数数组。
function sub(a, b){ return a - b; } let handler = { apply: function(target, ctx, args){ console.log('handle apply'); return Reflect.apply(...arguments); } } let proxy = new Proxy(sub, handler) proxy(2, 1) // 1
4、has(target,propkey) 拦截 propKey in proxy 的操作,返回一个布尔值。即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。
let proxy = new Proxy(exam, handler) 'name' in proxy
5、deleteProperty(target, propKey) 拦截delete proxy[propKey]
的操作,返回一个布尔值。用于拦截 delete 操作。
6、construct(target, args) 用于拦截 new 命令。返回值必须为对象。
7、ownKeys(target) 用于拦截对象自身属性的读取操作。
8、getPrototypeOf(target) 拦截对象原型操作
9、isExtensible(target) 用于拦截 Object.isExtensible 操作。
map, filter, reduce
map
作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。
[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]
另外 map
的回调函数接受三个参数,分别是当前索引元素,索引,原数组
['1','2','3'].map(parseInt)
- 第一轮遍历
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
这块的内容,同时也是最难理解的一块内容。reduce
可以将数组中的元素通过回调函数最终转换为一个值。
如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码
const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
total += arr[i]
}
console.log(total) //6
但是如果我们使用 reduce
的话就可以将遍历部分的代码优化为一行代码
const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)
对于 reduce
来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce
的过程
- 首先初始值为
0
,该值会在执行第一次回调函数时作为第一个参数传入 - 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
- 在一次执行回调函数时,当前值和初始值相加得出结果
1
,该结果会在第二次执行回调函数时当做第一个参数传入 - 所以在第二次执行回调函数时,相加的值就分别是
1
和2
,以此类推,循环结束后得到结果6