遇见Vue.js

  • 关键词:Vue.js – 读书笔记

二十七、知识拓展

Composition Event

Composition Event(复合事件),是DOM3级事件中新添加的一类事件类型,用于处理IME(Input Method Editor,输入法编辑器)的输入序列。IME可以让用户输入在物理键盘上找不到的字符。

复合事件就是针对检查和处理这种输入而设计的。

IME复合系统的工作原理:

缓存用户的键盘输入,直到一个字符被选中后才确定输入。缓存的键盘输入会暂时展示在输入框中,但不会真正被插入到DOM中。但是如果在复合事件的过程中改变了输入框的值,复合事件将提前结束,同时缓存的键盘输入值将会插入到输入框中。

复合事件类型:

compositionstart(打开时触发)

compositionend(关闭即用户选中了字符并确定输入时触发,表示返回正常键盘的输入状态)

compositionupdate(在start后、end事件触发前这段时间内,每次向输入字段中进行输入时均会出触发)

复合事件的兼容性表现

image

ES 6

ECMAScript6是JavaScript语言的一代标准。

ECMAScript6与JavaScript的关系,前者是后者的规格,后者是前者的一种实现。

它们使JavaScript变得更加强大、更富有表现力。

1.模块

在ES 6之前,使用最广泛的是CommonJS(node模块化加载)规范和AMD(RequireJS)规范。前者用于服务器环境,后者则专注于浏览器环境。

一个文件就是一个模块,一个模块内部的所有变量。

  • export 用来对外暴露接口,暴露的各接口通过名字来进行区分。
    //暴露三个接口给外界
    export const sqrt = Math.sqrt;
    export function square(x){
        return x * x;
    }
    export function diag(x,y){
        return sqrt(square(x)) + square(y);
    }
    //也可以采用以下方式:
    const sqrt = Math.sqrt;
    function square(x){
        return x * x;
    }
    function diag(x,y){
        return sqrt(square(x) + square(y));
    }
    export {sqrt, square, diag};
    //这种方式,结构清晰,一目了然
    //as语法,别名
    export {sqrt as sq1, sqrt as sq2};
  • import 用来加载模块(文件)。
    import { square, diag} from './lib';
    //as语法
    import { square as sq } from './lib';
    //import会执行加载的模块,因此有空import的语法
    //只加载执行模块,不引用任何接口
    import './lib';
    //还可以整体加载模块,达到命名空间的效果
    import * as lib from './lib';
  • export default

上面使用的export有一个问题,即使用模块接口的人b必须要知道该模块export了哪些接口。有时候一个模块实际上只对外暴露一个接口,这时候实际上没必要再限定暴露的接口名字。

    export default function(){}
    //本质上,export default就是输出一个名为default的变量或方法,然后系统允许我们进行重命名
    //lib.js
    function add(x,y){
        return x * y;
    }
    export {add as default};
    //等同于
    export default add;
    //main.js
    import {default as myAdd} from 'lib';
    //等同于
    import myAdd from 'lib';
  • export/import在Vue.js中的使用

Vue.js采用export/import进行模块化开发,文件通过export暴露接口,通过import引用其他文件的内容。Vue.js的源码结构优雅、层次严谨,这一切都离不开export和import。

2.let

代码中任何一对花括号(){}中的语句集都属于一个块,其中定义的所有变量在代码块外都是不可见的,称之为块级作用域。

之前,只有全局作用域和函数作用域,问题很多,内层变量可能覆盖外层变量、用于计数的循环变量泄漏为全局变量等。以往,开发者往往要通过闭包等方式来模拟块级作用域,很烦琐。

let关键词为JavaScript带来了块级作用域。

  • let的使用

let的作用是声明变量,let声明的变量只在let命令所在的代码块内有效。

let不允许重复声明。在相同作用域内,重复声明同一个变量会报错。

let不存在变量提升,因此,变量需要在声明后才可以使用。

    console.log(foo);//输出undefined
    console.log(bar);//报错ReferenceError
    var foo = "dd";
    var bar = "dd";

let存在暂时性死区(temporal deadzone, 简称TDZ),即指在当前代码块内如果使用了let声明变量,则在该条声明语句之前,该变量不可用。

  • let在Vue.js中的使用

let,严谨、不易发生错误,同时含有块级作用域等诸多优点,在Vue.js中运用let命令的情况比比皆是。

3.const

  • const的使用

const用于声明一个常量,一旦声明,常量的值便不能再被更改,进入只读模式。

在严格模式下,对已使用const声明的变量重新赋值甚至会报错,提示”TypeError:’xxx’ is read-only”。

使用const声明后不得再更改的特性,也意味着我们在声明变量时就必须初始化,不能留到以后再赋值。

在严格模式下,使用const声明变量不赋值会报错,提示”SyntaxError: missing = in const declaration”。

const的作用域与let相同,只在声明所在的块级作用域内有效。

const与let一样,同样不允许重复声明,不存在变量提升以及TDZ(Temporal DeadZone)。

还需要注意的是:const在声明复合类型的变量时,只能保证变量名指向的地址不变,并不保证该地址的数据不变(复合类型的变量名指向数据地址而不指向数据)。因此,如果使用const声明一个对象,并不能保证对象不可更改。

如果真的想达到对象本身不可变的效果,则应当使用Object.freeze方法。

  • const在Vue.js中的使用

在vue.js中对于一些常用的变量都采用了const方式声明。

transition的动画类型的声明便采用了const命令。

正则检测用的变量,也都使用const声明。

object

  • 1.Object.create

语法:Object.create(proto, [propertiesObject])

参数:

proto——一个对象,作为新创建对象的原型;

propertiesObject——可选。该参数对象是一个数组与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符。

该参数对象不能是underfined。另外,只有该对象自身拥有的可枚举的属性才有效,也就是说,该对象的原型链上属性是无效的。

抛出异常:如果proto参数不是null或对象值,则抛出一个TypeError异常

用法:创建一个拥有指定原型和若干指定属性的对象

    //如何使用Object.create()来实现类式继承。这是一个单继承。
    //Shape-superclass
    function Shape(){
        this.x = 0;
        this.y = 0;
    }
    Shape.prototype.move = function(x, y){
        this.x += x;
        this.y += y;
        console.log("Shape moved");
    }

    //Rectangle-subclass
    function Rectangle(){
        Shape.call(this); // call super constructor
    }
    Rectangle.prototype = Object.create(Shape.prototype);

    var rect = new Rectangle();

    rect instanceof Rectangle //true
    rect instanceof Shape //true

    rect.move(1,1); // "Shape moved"

    //Vue.js应用举例
    //src/global-api.js
    //Vue.extend用于创建基础Vue构造器的“子类”
    Vue.extend = function(extendOptions){
        ...
        var Sub = createClass(name || 'VueComponent')
        Sub.prototype = Object.create(Super.prototype)
        Sub.prototype.constructor = Sub
        ...
    }
  • 2.Object.keys

语法:Object.keys(obj)

参数:

obj——返回该对象的所有可枚举自身属性的属性名。

用法:返回一个字符串数组,其元素来自于给定对象上可枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。

    var arr = ["a", "b", "c"];
    alert(Object.keys(arr));//弹出"0, 1, 2"
    //类数组对象
    var obj = {
        0: "a",
        1: "b",
        2: "c"
    }
    alert(Object.keys(obj));//弹出"0, 1, 2"
    //getFoo是一个不可枚举的属性
    var my_obj = Object.create(
        {},
        {
            getFoo: {
                value: function(){
                    return this.foo
                }
            }
        }
    );
    my_obj.foo = 1;
    alert(Object.keys(my_obj));//只弹出foo

    //Vue.js应用举例
    //src/util/lang.js
    //Mix properties into target object
    export function extend(to, from){
        var keys = Object.keys(from);
        var i = keys.length;
        while(i--){
            to[keys[i]] = from[keys[i]];
        }
        return to;
    }
  • 3.Object.isExtensible

语法:Object.isExtensible(obj)

参数:obj{Object}

用法: 判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。在默认情况下,对象是可扩展的,即可以为其添加新的属性并且其__proto__属性可以被更改。

Object.preventExtensions、Object.seal或Object.freeze方法都可以标记一个对象为不可扩展的(non-extensible)。

    //新对象默认是可扩展的
    var emptry = {};
    Object.isExtensible(empty); // true

    //...可以变得不可扩展
    Object.preventExtensions(empty);
    Object.isExtensible(empty); // false

    //密封对象是不可扩展的
    var sealed = Object.seal({});
    Object.isExtensible(sealed); // fanlse

    //冻结对象也是不可扩展的
    var frozen = Object.freeze({});
    Object.isExtensible(empty); // false

Vue.js应用举例:

image

  • 4.Object.getOwnPropertyNames

语法:Object.getOwnPropertyNames(obj)

参数:Obj{Object}

用法:返回一个由指定对象的所有自身属性的属性名(包括不可枚举的属性)组成的数组。数组中枚举属性的顺序与通过for…in loop(或Object.keys)迭代该对象属性时的一致。数组中不可枚举属性的顺序未定义。

    var arr = ["a", "b", "c"];
    console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
    //类数组对象
    var obj = {
        0: "a",
        1: "b",
        2: "c"
    }
    console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
    //使用Array.forEach输出属性名和属性值
    Object.getOwnPropertyNames(obj).forEach(function(val, idx, array){
        console.log(val + "->" + obj[val]);
    )};
    //输出:
    // 0 -> a
    // 1 -> b
    // 2 -> c
    //不可枚举是属性
    var my_obj = Object.create({},{
        getFoo: {
            value: function(){
                return this.foo
            },
            enumerable: false
        }
    });
    my_obj.foo = 1;
    console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]
  • 5.Object.defineProperty

语法:Object.defineProperty(obj, prop, descriptor)

参数:

obj——需要定义属性的对象

prop——需被定义或修改的属性名

descriptor——需被定义或修改的属性的描述符

用法:该方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。

该方法的使用较为复杂,但其在Vue.js中有极为重要的应用——Vue.js实现数据和视图联动的核心便在于该方法。

Vue.js应用举例:

    //src/util/lang.js
    export function def(obj, key, val, enumerable){
        Object.defineProperty(obj, key, {
            value: val,
            enumerable: !!enumerable,
            writable: true,
            configurable: true
        })
    }

函数柯里化

函数柯里化(curry)过程是通过逐步传参,在每一步中返回一个更具体的部分配置的函数的过程。通过不断传参的过程,我们可以实现对函数的高度复用。函数柯里化是利用闭包、高阶函数特性来实现动态创建函数、参数复用的过程。柯里化可以使得代码逻辑更加清晰、代码实现更加优雅。在Vue.js中大量应用了函数柯里化技术。

  • 1.动态创建函数
    //通常,我们会使用以下方式来注册DOM事件
    var addEvent = function(el, type, fn, capture){
        if(window.addEventListener){
            el.addEventListener(type, function(e){
                fn.call(el, e);
            }, capture);
        }else if(window.attachEvent){
            el.attachEvent("on" + type, function(e){
                fn.call(el, e);
            })
        }
    }
    //使用这种方式的问题是:
    //每次调用addEvent方法时,都会执行一次判断来决定使用哪个方法注册事件。
    //实际上,我们可以在页面载入后只进行一次判断,然后使用同一注册方法注册不同的事件
    //我们使用函数柯里化来实现:
    var addEvent = (function(){
        if(window.addEventListener){
            return function(el, sType, fn, capture){
                el.addEventListener(sType, function(e){
                    fn.call(el, e);
                }, (capture));
            };
        }else if(window.attachEvent){
            return function(el, sType, fn, capture){
                el.attachEvent("on" + sType, function(e){
                    fn.call(el, e);
                });
            };
        }
    })()
    //以上自执行代码首先会判断浏览器支持的事件注册方法,根据不同的注册方法,返回一个事件注册的函数赋值给addEvent,
    //以后调用addEvent方法注册事件时,内部就不会再次进行判断了,而是直接使用当前浏览器支持的事件注册方法来注册相应的事件
  • 2.参数复用
    //如果我们需要求10与任意数的和,则可能会这么写:
    function sum(x, y){
        return x + y;
    }
    sum(10, 20);
    //在多次调用同一方法时,我们会传入一个相同的参数10。如果使用函数柯里化,我们就可以实现参数复用:
    function sum(x){
        return function(y){
            return x + y;
        }
    }
    var addTen = sum(10);
    addTen(20);