webpack 工程相当庞大,但是 Webpack 本质上是一种事件流机制,通过事件流将各种插件串联起来,最终完成 webpack 的全流程,而实现事件流机制的核心是今天要讲的Tapable 模块。Webpack 负责编译的 Compiler 和创建 Bundle 的 Compilation 都是继承自 Tapable。所以在讲 Webpack 工作流程之前,我们需要先掌握 Tapable。
我们都知道 Node.js 特点提到事件驱动,这是因为 Node.js 本身利用 JavaScript 的语言特点实现了自定义的事件回调,Node.js 内部一个事件发射器 EventEmitter,通过这个类,可以进行事件监听与发射,这个也是 Node.js 的核心模块,很多 Node.js 内部模块都是继承自它,或者引用了它。
const EventEmitter = require('events').EventEmitter;
const event = new EventEmitter();
event.on('event_name', (arg) => {
console.log('event_name fire', arg);
});
setTimeout(function () {
event.emit('event_name', 'hello world');
}, 1000);
上面代码就是事件发射器的用法。
webpack 核心库 Tapable 的原理和 EventEmitter 类似,但是功能更强大,包括多种类型,通过事件的注册和监听,触发 webpack 生命周期中的函数方法,在 Webpack 中,tapable 都是放到对象的hooks上,所以我们叫他们钩子。翻阅 webpack 的源码时,会发现很多类似下面的代码:
// webpack 4.29.6
// lib/compiler
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
shouldEmit: new SyncBailHook(['compilation']),
done: new AsyncSeriesHook(['stats']),
additionalPass: new AsyncSeriesHook([]),
beforeRun: new AsyncSeriesHook(['compiler']),
run: new AsyncSeriesHook(['compiler']),
emit: new AsyncSeriesHook(['compilation']),
afterEmit: new AsyncSeriesHook(['compilation']),
thisCompilation: new SyncHook(['compilation', 'params']),
compilation: new SyncHook(['compilation', 'params']),
normalModuleFactory: new SyncHook(['normalModuleFactory']),
contextModuleFactory: new SyncHook(['contextModulefactory']),
beforeCompile: new AsyncSeriesHook(['params']),
compile: new SyncHook(['params']),
make: new AsyncParallelHook(['compilation']),
afterCompile: new AsyncSeriesHook(['compilation']),
watchRun: new AsyncSeriesHook(['compiler']),
failed: new SyncHook(['error']),
invalid: new SyncHook(['filename', 'changeTime']),
watchClose: new SyncHook([]),
environment: new SyncHook([]),
afterEnvironment: new SyncHook([]),
afterPlugins: new SyncHook(['compiler']),
entryOption: new SyncBailHook(['context', 'entry']),
};
}
}
这些代码就是一个类或者函数完整生命周期需要**「走过的路」**,所有 Webpack 代码虽然代码量很大,但是从hook找生命周期事件点,然后通过 hook 名称,基本就可以猜出大概流程。
在Tapable 的文档中显示了,Tapable 分为以下类型:
// tapable 1.1.1
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook,
} = require('tapable');
Hook 类型可以分为同步(Sync)和异步(Async),异步又分为并行和串行。
根据使用方式来分,又可以分为Basic、Waterfal、Bail和Loop四类,每类 Hook 都有自己的使用要点:
类型 | 使用要点 |
---|---|
Basic | 基础类型,不关心监听函数的返回值,不根据返回值做事情 |
Bail | 保险式,只要监听函数中有返回值(不为undefined),则跳过之后的监听函数 |
Waterfal | 瀑布式,上一步的返回值继续交给下一步处理和使用 |
Loop | 循环类型,如果该监听函数返回 true 则这个监听函数会反复执行,如果返回 undefined 则退出循环 |
基础类型包括SyncHook、AsyncParallelHook和AsyncSeriesHook,这类 Hook 不关心函数的返回值,会一直执行到底。下面以SyncHook为例来说明下:
const {SyncHook} = require('tapable');
// 所有的构造函数都接收一个可选的参数,这个参数是一个参数名的字符串数组
// 1. 这里array的字符串随便填写,但是array的长度必须与实际要接受参数个数保持一致;
// 2. 如果回调不接受参数,可以传入空数组。
// 后面类型都是这个规则,不再做过多说明
const hook = new SyncHook(['name']);
// 添加监听
hook.tap('1', (arg0, arg1) => {
// tap 的第一个参数是用来标识`call`传入的参数
// 因为new的时候只的array长度为1
// 所以这里只得到了`call`传入的第一个参数,即Webpack
// arg1 为 undefined
console.log(arg0, arg1, 1);
return '1';
});
hook.tap('2', (arg0) => {
console.log(arg0, 2);
});
hook.tap('3', (arg0) => {
console.log(arg0, 3);
});
// 传入参数,触发监听的函数回调
// 这里传入两个参数,但是实际回调函数只得到一个
hook.call('Webpack', 'Tapable');
// 执行结果:
/*
Webpack undefined 1 // 传入的参数需要和new实例的时候保持一致,否则获取不到多传的参数
Webpack 2
Webpack 3
*/
通过上面的代码可以得出结论:
详细的流程图如下:
再来看下AsyncSeriesHook的示例:
const {AsyncSeriesHook} = require('tapable');
const hook = new AsyncSeriesHook(['name']);
hook.tapAsync('one', (name, cb) => {
console.log('one', name);
setTimeout(() => {
console.log('one timeout');
cb();
}, 100);
});
hook.tapAsync('two', (name, cb) => {
console.log('two', name);
cb();
});
hook.callAsync('asyncHook', (endArgs) => {
console.log('end');
console.log('endArgs', endArgs);
});
// 执行结果:
/*
one asyncHook
one timeout
two asyncHook
end
endArgs undefined
*/
如果对代码 setTimeout 部分进行修改:
setTimeout(() => {
console.log('one timeout');
cb(1); // <- 回调
}, 100);
// 执行结果
/*
one asyncHook
one timeout
end
err 1
*/
说明在AsyncSeriesHook流程中只要任何地方回调了cb传入参数,则直接跳过后续流程,直接进入callAsync的回调,从而结束流程。
Bail类型的 Hook 包括:SyncBailHook、AsyncSeriesBailHook、AsyncParallelBailHook,Bail类型的 Hook 也是按回调栈顺序一次执行回调,但是如果其中一个回调函数返回结果result !== undefined 则退出回调栈调。代码示例如下:。
const {SyncBailHook} = require('tapable');
const hook = new SyncBailHook(['name']);
hook.tap('1', (name) => {
console.log(name, 1);
});
hook.tap('2', (name) => {
console.log(name, 2);
return 'stop';
});
hook.tap('3', (name) => {
console.log(name, 3);
});
hook.call('hello');
/* output
hello 1
hello 2
*/
通过上面的代码可以得出结论:
详细的流程图如下:
SyncBailHook类似Array.find,找到(或者发生)一件事情就停止执行;AsyncParallelBailHook类似Promise.race这里竞速场景,只要有一个回调解决了一个问题,全部都解决了。
Waterfall类型 Hook 包括 SyncWaterfallHook、AsyncSeriesWaterfallHook。类似Array.reduce效果,如果上一个回调函数的结果 result !== undefined,则会被作为下一个回调函数的第一个参数。代码示例如下:
const {SyncWaterfallHook} = require('tapable');
const hook = new SyncWaterfallHook(['arg0', 'arg1']);
hook.tap('1', (arg0, arg1) => {
console.log(arg0, arg1, 1);
return 1;
});
hook.tap('2', (arg0, arg1) => {
console.log(arg0, arg1, 2);
return 2;
});
hook.tap('3', (arg0, arg1) => {
// 这里 arg0 = 2
console.log(arg0, arg1, 3);
// 等同于 return undefined
});
hook.tap('4', (arg0, arg1) => {
// 这里 arg0 = 2 还是2
console.log(arg0, arg1, 4);
});
hook.call('Webpack', 'Tapable');
/* console log output
Webpack Tapable 1
1 'Tapable' 2
2 'Tapable' 3
2 'Tapable' 4 */
通过上面的代码可以得出结论:
详细的流程图如下:
这类 Hook 只有一个SyncLoopHook(虽然 Tapable 1.1.1 版本中存在AsyncSeriesLoopHook,但是并没有将它 export 出来),LoopHook执行特点是不停的循环执行回调函数,直到所有函数结果 result === undefined。为了更加直观的展现 LoopHook 的执行过程,我对示例代码做了一下丰富:
const {SyncLoopHook} = require('tapable');
const hook = new SyncLoopHook(['name']);
let callbackCalledCount1 = 0;
let callbackCalledCount2 = 0;
let callbackCalledCount3 = 0;
let intent = 0;
hook.tap('callback 1', (arg) => {
callbackCalledCount1++;
if (callbackCalledCount1 === 2) {
callbackCalledCount1 = 0;
intent -= 4;
intentLog('</callback-1>');
return;
} else {
intentLog('<callback-1>');
intent += 4;
return 'callback-1';
}
});
hook.tap('callback 2', (arg) => {
callbackCalledCount2++;
if (callbackCalledCount2 === 2) {
callbackCalledCount2 = 0;
intent -= 4;
intentLog('</callback-2>');
return;
} else {
intentLog('<callback-2>');
intent += 4;
return 'callback-2';
}
});
hook.tap('callback 3', (arg) => {
callbackCalledCount3++;
if (callbackCalledCount3 === 2) {
callbackCalledCount3 = 0;
intent -= 4;
intentLog('</callback-3>');
return;
} else {
intentLog('<callback-3>');
intent += 4;
return 'callback-3';
}
});
hook.call('args');
function intentLog(...text) {
console.log(new Array(intent).join(' '), ...text);
}
/* output
<callback-1>
</callback-1>
<callback-2>
<callback-1>
</callback-1>
</callback-2>
<callback-3>
<callback-1>
</callback-1>
<callback-2>
<callback-1>
</callback-1>
</callback-2>
</callback-3>
*/
通过上面的代码可以得出结论:
详细的流程图如下:
了解了 Tapable 的类型和基本使用方法,我们可能产生疑惑,这些类型在 Webpack 中是怎么被应用的,又是为什么要舍近求远的写个专门的库来实现异步,EventEmitter 和 Promise 不香吗?下面我们来看下 Webpack 内部的应用。
在 Webpack 中用的最多的是AsyncSeriesHook,我们摘录了 Webpack Compiler 一段代码:
// 为了好理解,对代码进行必要的精简
// 定义hooks
this.hooks = Object.freeze({
beforeRun: new AsyncSeriesHook(['compiler']),
run: new AsyncSeriesHook(['compiler']),
done: new AsyncSeriesHook(['stats']),
afterDone: new SyncHook(['stats']),
failed: new SyncHook(['error']),
});
const finalCallback = (err, stats) => {
// ...
if (err) {
this.hooks.failed.call(err);
}
if (callback !== undefined) callback(err, stats);
// 触发 afterDone
this.hooks.afterDone.call(stats);
};
const onCompiled = (err, compilation) => {
if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) {
// ...
// 生成stats实例
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, (err) => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
return;
}
// ...
this.hooks.done.callAsync(stats, (err) => {
if (err) return finalCallback(err);
this.cache.storeBuildDependencies(compilation.buildDependencies, (err) => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
});
};
const run = () => {
this.hooks.beforeRun.callAsync(this, (err) => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, (err) => {
if (err) return finalCallback(err);
this.readRecords((err) => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
};
// ==开始执行==
run();
在上面精简的代码中:
Webpack 内部的 Tapable 的使用,实际是实现了一个异步操作流程,因为使用了AsyncSeriesHook,所以整个流程都是串行的异步,并且任何函数中遇见错误,回调Callback传回错误(finalCallback(err)),则流程立即停止。
这里实际是实现了一个符合 Node.js 的错误优先原则异步流程,这种用法和实现,保证了异步的同时,也很好的控制了流程中遇见的错误,即当错误在任何环节发生的时候,就可以调用回调函数直接结束整个流程。看到这里不得不说这种设计真的很赞。
Node.js 的错误优先原则: Node.js 核心 api 暴露的大多数异步方法都遵循称为错误优先回调的惯用模式。 使用这种模式,回调函数作为参数传给方法。 当操作完成或出现错误时,回调函数将使用 Error 对象(如果有)作为第一个参数传入。 如果没有出现错误,则第一个参数将作为 null 传入。
Tapable 的执行流程可以分为四步:
下面以SyncHook源码为例,分析下整个流程。先来看下lib/SyncHook.js 主要代码:
class SyncHook extends Hook {
// 错误处理,防止调用者调用异步钩子
tapAsync() {
throw new Error('tapAsync is not supported on a SyncHook');
}
tapPromise() {
throw new Error('tapPromise is not supported on a SyncHook');
}
// 实现入口
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
首先所有的 Hook 都是继承自Hook类,针对同步 Hook 的事件绑定,如SyncHook、SyncBailHook、SyncLoopHook、SyncWaterfallHook, 会在子类中覆写基类Hook中 tapAsync 和 tapPromise 方法,这样做可以防止使用者在同步 Hook 中误用异步方法。
下面我按照执行流程的四个步骤来分析下源码,看一下一个完整的流程中,都是调用了什么方法和怎么实现的。
SyncHook中绑定事件是下面的代码。
hook.tap('evt1', (arg0) => {
console.log(arg0, 2);
});
hook.tap('evt2', (arg0) => {
console.log(arg0, 3);
});
下面我们来看下tap的实现,因为SyncHook是继承子Hook,所以我们找到lib/Hook.js中 tap 的实现代码:
tap(options, fn) {
// 实际调用了_tap
this._tap("sync", options, fn);
}
_tap(type, options, fn) {
// 这里主要进行了一些参数的类型判断
if (typeof options === "string") {
options = {
name: options
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
options = Object.assign({ type, fn }, options);
// 这里是注册了Interceptors(拦截器)
options = this._runRegisterInterceptors(options);
// 参数处理完之后,调用了_insert,这是关键代码
this._insert(options);
}
通过查阅Hook.tap和Hook._tap的代码,发现主要是做一些参数处理的工作,而主要的实现是在Hook._insert实现的:
// tapable/lib/Hook.js
_insert(item) {
this._resetCompilation();
let before;
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
// 这里根据 stage 对事件进行一个优先级排序
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
// 这是找到了回调栈
this.taps[i] = item;
}
_insert的代码主要目的是将传入的事件推入this.taps数组,等同于:
hook.tap('event', callback);
// → 即
this.taps.push({
type: 'sync',
name: 'event',
fn: callback,
});
在基类lib/Hook.js的constructor中,可以找到一些变量初始化的代码:
class Hook {
constructor(args = []) {
// 这里存入初始化的参数
this._args = args;
// 这里就是回调栈用到的数组
this.taps = [];
// 拦截器数组
this.interceptors = [];
this.call = this._call;
this.promise = this._promise;
this.callAsync = this._callAsync;
// 这个比较重要,后面拼代码会用
this._x = undefined;
}
}
这样绑定回调函数就完成了,下面看下触发回调的时候发生了什么。
在事件触发,我们使用同syncHook的call方法触发一个事件:
hook.call(1, 2);
这里的call方法,实际是通过Object.defineProperties添加到Hook.prototype上面的:
// tapable/lib/Hook.js
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
this[name] = this._createCall(type);
return this[name](...args);
};
}
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate('call', 'sync'),
configurable: true,
writable: true,
},
_promise: {
value: createCompileDelegate('promise', 'promise'),
configurable: true,
writable: true,
},
_callAsync: {
value: createCompileDelegate('callAsync', 'async'),
configurable: true,
writable: true,
},
});
在上面的代码中,Hook.prototype通过对象定义属性方法Object.defineProperties定义了三个属性方法:_call、_promise、_callAsync,这三个属性的value都是通过 createCompileDelegate返回的一个名为lazyCompileHook的函数,从名字上面来猜测是「懒编译」,当我们真正调用call方法的时候,才会编译出真正的call函数。
call函数编译的用到的是_createCall方法,这个是在 Hook 类定义的时候就定义的方法,_createCall实际最终调用了compile方法,而通过Hook.js代码来看,compile是个需要子类重写实现的方法:
// tapable/lib/Hook.js
compile(options) {
throw new Error("Abstract: should be overriden");
}
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
所以,在 Hook 中绕了一圈,我们又回到了SyncHook的类,我们再看下 SyncHook 的代码:
// lib/SyncHook.js
const HookCodeFactory = require('./HookCodeFactory');
class SyncHookCodeFactory extends HookCodeFactory {
content({onError, onDone, rethrowIfPossible}) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible,
});
}
}
const factory = new SyncHookCodeFactory();
class SyncHook extends Hook {
tapAsync() {
throw new Error('tapAsync is not supported on a SyncHook');
}
tapPromise() {
throw new Error('tapPromise is not supported on a SyncHook');
}
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
SyncHook 的compile来自是HookCodeFactory的子类SyncHookCodeFactory。在lib/HookCodeFactory.js找到setup方法:
// lib/HookCodeFactory
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
这里的instance实际就是SyncHook的实例,而_x就是我们之前绑定事件时候最后的_x。
最后factory.create(options)调用了HookCodeFactory的create方法,这个方法就是实际拼接可执行 JavaScript 代码片段的,具体看下实现:
// lib/HookCodeFactory.js
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.content({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
fn = new Function(
this.args({
after: "_callback"
}),
'"use strict";\n' +
this.header() +
this.content({
onError: err => `_callback(${err});\n`,
onResult: result => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
})
);
break;
case "promise":
let errorHelperUsed = false;
const content = this.content({
onError: err => {
errorHelperUsed = true;
return `_error(${err});\n`;
},
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
let code = "";
code += '"use strict";\n';
code += "return new Promise((_resolve, _reject) => {\n";
if (errorHelperUsed) {
code += "var _sync = true;\n";
code += "function _error(_err) {\n";
code += "if(_sync)\n";
code += "_resolve(Promise.resolve().then(() => { throw _err; }));\n";
code += "else\n";
code += "_reject(_err);\n";
code += "};\n";
}
code += this.header();
code += content;
if (errorHelperUsed) {
code += "_sync = false;\n";
}
code += "});\n";
fn = new Function(this.args(), code);
break;
}
this.deinit();
return fn;
}
上面create代码中的重要参数是type,而type是由 Hook 类在 createCompileDelegate("call", "sync")的时候传入进去的,所以调用 call方法,实际type为sync,在 create 中会进入到case 'sync'的分支,在switch中用到最重要的content实际是在class SyncHookCodeFactory extends HookCodeFactory的时候定义的。这里我们就不继续追踪代码生成的逻辑实现了,我们可以直接在最后将 fn的源码console.log出来:console.log(fn.toString()),大致可以得到下面的代码:
// 调用 call 的代码
const hook = new SyncHook(['argName0', 'argName1']);
hook.tap('evtName', (arg0) => {
console.log(arg0, 1);
});
hook.call('Webpack', 'Tapable');
// 最终得到的源码是:
function anonymous(argName0, argName1) {
'use strict';
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(argName0, argName1);
}
上面的_fn0实际就是我们tap绑定的回调函数,argName0和argsName1就是我们实例化SyncHook传入的形参,而我们实际只是在tap的回调中用了arg0一个参数,所以输出的结果是Webpack 1。
Tapable 是 Webpack 的核心模块,Webpack 的所有工作流程都是通过 Tapable 来实现的。Tapable 本质上是提供了多种类型的事件绑定机制,根据不同的流程特点可以选择不同类型的 Hook 来使用。Tapable 的核心实现在绑定事件阶段跟我们平时的自定义 JavaScript 事件绑定(例如 EventEmitter)没有太大区别,但是在事件触发执行的时候,会临时生成可以执行的函数代码片段。通过这种实现方式,Tapable 实现了强大的事件流程控制能力,也增加了如 waterfall/parallel 系列方法,实现了异步/并行等事件流的控制能力。
作者:三水清
链接:https://juejin.cn/post/7039898741075083271
webpack 在前端工程中随处可见,当前流行的 vue, react, weex 等解决方案都推崇 webpack 作为打包工具。前端工具云集的时代,这是你值得选择的之一。
webpack是前端工程构建的一套工具,为什么一个程序称之为一套呢,是因为webpack其实是npm的一个模块,使用起来的话,这期间还需要很多其它模块来进行支持,所以我称之为一套工具。
本文从一个小Demo开始,通过不断增加功能来说明webpack的基本配置,只针对新手。webpack基本的配置就可以熟悉了,会引入loader,配置loader选项,会设置alias,会用plugins差不多。
Plugins是webpack的基础,我们都知道webpage的plugin是基于事件机制工作的,这样最大的好处是易于扩展。讲解如果扩展内置插件和其他插件,以及我们常用的Plugins插件
webpack技巧的总结:进度汇报、压缩、复数文件打包、分离app文件与第三方库文件、资源映射、输出css文件、开发模式、分析包的大小、更小的react项目、更小的Lodash、引入文件夹中所有文件、清除extract-text-webpack-plugin日志。
Webpack 作为目前最流行的前端构建工具之一,在 vue/react 等 Framework 的生态圈中都占据重要地位。在开发现代 Web 应用的过程中,Webpack 和我们的开发过程和发布过程都息息相关,如何改善 Webpack 构建打包的性能也关系到我们开发和发布部署的效率。
新版 Webpack 中我们所做的每一个更新目的都在于此,为了当大家在使用 Webpack 的时候敏捷连续毫无顿挫感。 webpack 4 进行构建性能测试,得出的结果非常有趣。结果很惊人,构建时间降低了 60%-98%!
Webpack 是一个现代 JavaScript 应用程序的模块打包器 (module bundler) 。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块
Tobias Koppers是一位自由软件开发者,家住德国纽伦堡。他因写出webpack这个已有数百万开发者使用的开源软件而名噪一时。他目前专注于JavaScript和开源项目。以下是我对他个人的专访,希望对大家有所启发。
本文讲述css-loader开启css模块功能之后,如何与引用的npm包中样式文件不产生冲突。比如antd-mobilenpm包的引入。在不做特殊处理的前提下,样式文件将会被转译成css module。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!