遇见Vue.js
- 关键词:Vue.js – 读书笔记
二十七、知识拓展
Composition Event
Composition Event(复合事件),是DOM3级事件中新添加的一类事件类型,用于处理IME(Input Method Editor,输入法编辑器)的输入序列。IME可以让用户输入在物理键盘上找不到的字符。
复合事件就是针对检查和处理这种输入而设计的。
IME复合系统的工作原理:
缓存用户的键盘输入,直到一个字符被选中后才确定输入。缓存的键盘输入会暂时展示在输入框中,但不会真正被插入到DOM中。但是如果在复合事件的过程中改变了输入框的值,复合事件将提前结束,同时缓存的键盘输入值将会插入到输入框中。
复合事件类型:
compositionstart(打开时触发)
compositionend(关闭即用户选中了字符并确定输入时触发,表示返回正常键盘的输入状态)
compositionupdate(在start后、end事件触发前这段时间内,每次向输入字段中进行输入时均会出触发)
复合事件的兼容性表现
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应用举例:
- 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);