var,let以及const区别

在全局作用域下使用let,const声明变量,变量是不会被挂载到window上的;这一点跟var不同。

let,const声明的变量,不能在声明前使用,存在暂时性死区;var可以进行变量提升。

  暂时性死区:虽然变量在编译的环节中被告知在这块作用域中可以访问,但是访问是受限制的。

  函数提升:函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部

letconst 作用基本一致,但是后者声明的变量不能再次赋值。

原型继承和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,该结果会在第二次执行回调函数时当做第一个参数传入
  • 所以在第二次执行回调函数时,相加的值就分别是 12,以此类推,循环结束后得到结果 6
02-14 04:51