深入理解包与NPMM,package.json深入解读

包与NPM

CommonJS的包规范的定义其实也十分简单,它由包结构和包描述文件两个部分组成,前者用于组织包中的各种文件,后者则用于描述包的相关信息,以供外部读取分析。

包结构

包实际上是一个存档文件,即一个目录直接打包为 .zip 或 tar.gz 格式的文件,安装后解压还原为目录。完全符合CommonJS规范的包目录应该包含如下文件。

  1. package.json: 包描述文件。
  2. bin:用于存放可执行二进制文件的目录。
  3. lib:用于存放JavaScript代码的目录。
  4. doc:用于存放文档的目录。
  5. test:用于存放单元测试用例的代码。

可以看到,CommonJS包规范从文档、测试等方面都做过考虑。当一个包完成后向外公布时,用户可以看到单元测试和文档时,会给人一种踏实可靠的感觉。

包描述文件

包描述文件用于表达非代码相关的信息,它是一个JSON格式的文件——package.json,位于包的根目录下,是包的重要组成部分。而npm的所有行为都与包描述文件的字段息息相关。

CommonJS为package.json文件定义一些必须的字段。

  1. name:包名。规范定义它需要小写字符和数字的组成,可以包含. \ _和 - ,但不允许出现空格。包名必须是唯一的,以免对外公布时产生重名冲突的误解。除此以外,NPM还建议不要在包名中附带上node或js来重复表示它是JavaScript或Node模块。
  2. description:包简介。
  3. version:版本号。
  4. keywords:关键词数组,NPM中主要用来做分类搜索。一个好的关键词数组有利于用户快速找到你编写的包。
  5. maintainers:包维护者列表,每个维护者由name、email和web这3个属性组成。

NPM通过该属性进行权限认证。

  1. contribuctors:贡献者列表。
  2. bugs:一个可以反馈bug的网页地址或邮箱地址。
  3. licenses:当前包所使用的许可证列表,表示这个包在哪些许可证下使用。
  4. repositories:托管源代码的位置列表,表明可以通过哪些方式和地址访问包的源代码。
  5. dependencies:使用当前包所依赖的包列表。这个属性非常重要,NPM会通过这个属性自动加载依赖的包。

除了必选字段外,规范还定义了一部分可选的字段

  1. homepage:当前包的网站地址。

  2. os:操作系统支持的列表。这些操作系统的取值包括aix,freebsd,Linux,macos,solaris,vxworks,windows。如果设置了列表为空时,则不对操作系统做任何假设。

  3. CPU:CPU架构的支持列表。

  4. engine:支持的js引擎列表,有效的引擎列表取值包括ejs,flusspferd,gpsee,jsc,spidermonkey,narwhal,node,v8.

  5. builtin:标志当前包是否是内建在底层系统的标准组件。

  6. directories:包目录说明。

  7. implements:实现规范的列表,标志当前包实现了CommonJS的那些规范。

  8. scripts:脚本说明对象。它主要被包管理器用来安装,编译,测试和卸载包,为其提供钩子机制。

    1
    2
    3
    4
    5
    6
    7
    //示例如下:
    "scripts":{
    "install":"install.js",
    "uninstall":"uninstall.js",
    "build":"build.js",
    "test":"test.js"
    }

包规范的定义可以帮助Node解决依赖包安装的问题,而NPM正是基于该规范进行了实现。最初,NPM工具是有Issac Z.Schlueter单独创建,提供给node服务的Node包管理器,需要单独安装。后来,在v6.3版本时集成进Node中作为默认包管理器,作为软件包的一部分一起安装。

在包描述文件的规范中,NPM实际需要的字段主要有name,version,description,keywords,repositories,author,bin,main,scripts,engines,dependencies,devDependencies。

与包规范的区别在于多了author,bin,main和devDependencies这4个字段。

author:包作者。

bin:一些包作者希望包可以作为命令行工具使用。配置好bin字段后,通过npm install package_name -g命令可以将脚本添加到执行路径中,之后可以在命令行中直接执行。通过-g命令安装的模块包称为全局模式。

main:模块引入方法require()在引入包时,会优先检查这个字段,并将其作为包中其余模块的入口。如果不存在这个字段,require()方法会查找包目录下的 index.js , index.node , index.json 文件作为默认入口。

devDependencies:一些模块只在开发时需要依赖。配置这个属性,可以提示包的后续开发者安装依赖包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//以下是node比较有名的框架express项目的package.json文件
{
"name": "express",
"description": "Fast, unopinionated, minimalist web framework",
"version": "4.17.1",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
"Ciaran Jessup <ciaranj@gmail.com>",
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Guillermo Rauch <rauchg@gmail.com>",
"Jonathan Ong <me@jongleberry.com>",
"Roman Shtylman <shtylman+expressjs@gmail.com>",
"Young Jae Sim <hanul@hanul.me>"
],
"license": "MIT",
"repository": "expressjs/express",
"homepage": "http://expressjs.com/",
"keywords": [
"express",
"framework",
"sinatra",
"web",
"rest",
"restful",
"router",
"app",
"api"
],
"dependencies": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"devDependencies": {
"after": "0.8.2",
"connect-redis": "3.4.1",
"cookie-parser": "~1.4.4",
"cookie-session": "1.3.3",
"ejs": "2.6.1",
"eslint": "2.13.1",
"express-session": "1.16.1",
"hbs": "4.0.4",
"istanbul": "0.4.5",
"marked": "0.6.2",
"method-override": "3.0.0",
"mocha": "5.2.0",
"morgan": "1.9.1",
"multiparty": "4.2.1",
"pbkdf2-password": "1.2.1",
"should": "13.2.3",
"supertest": "3.3.0",
"vhost": "~3.0.2"
},
"engines": {
"node": ">= 0.10.0"
},
"files": [
"LICENSE",
"History.md",
"Readme.md",
"index.js",
"lib/"
],
"scripts": {
"lint": "eslint .",
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
},
"__npminstall_done": "Sun Feb 16 2020 11:22:54 GMT+0800 (GMT+08:00)",
"_from": "express@4.17.1",
"_resolved": "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz"
}

NPM常用功能

CommonJS包规范是理论,NPM是其中的一种实践。对于Node而言,NPM帮助完成了第三方模块的发布,安装和依赖等。借助NPM,Node与第三方模块之间形成了很好地一个生态系统。

安装依赖包

安装依赖包是NPM最常见的用法,它的执行语句是npm install express.。执行该命令后,NPM会在当前目录下创建node_module目录,然后在node_module目录下创建express目录,接着将包解压到这个目录下。

安装好依赖包后,直接在代码中调用require(‘express’),即可引入该包,require()方法在做路径分析时会通过,模块路径查找到express所在的位置。模块引入和包的安装是相辅相成的。

全局模式安装

如果包含有命令行工具,那么需要执行npm install express -g命令进行全局安装。需要注意的是,全局模式并不是将一个模块包安装为一个全局包的意思,他并不意味着可以从任何地方通过require()来引用到它。

全局模式这个称谓其实并不精确,存在诸多误导。实际上,-g是将一个包安装为全局可用的可执行命令。他根据包描述文件中的bin字段配置,将实际脚本链接到与Node可执行文件相同的路径下:

1
2
3
"bin":{
"express":"./bin/express"
}

事实上,通过全局模式安装的所有模块都被安装进一个统一的目录下,这个目录可以通过如下方式运算出来:

path.resolve(process.execPath, ‘..’ , ‘lib’ , ‘node_modules’);

如果Node可执行文件的位置是/user/local/bin/node,那么模块目录就是/user/local/lib/node_modules。最后,通过软链接的方式将bin字段配置的可执行文件链接到Node的可执行目录下。

从本地安装

对于一些没有发布到NPM上的包,或是因为网络原因导致无法直接安装的包,可通过将包下载到本地,然后本地安装。本地安装只需要为NPM指定package.json文件所在的位置即可:它可以是一个包含package.json的存档文件,也可以是一个URL地址,也可以是一个目录下有package.json文件的目录位置。具体参数如下:

1
2
3
npm install <tarbar file>
npm install <tarbar url>
npm install <folder>
从非官方源安装

如果不能通过官方源安装,可通过镜像安装。在执行命令时,添加 --registry=http://registry.url即可,实例:

1
npm insatll underscore --registry=http://registry.yrl

如果使用过程几乎使用镜像源安装,可执行命令指定默认源

1
npm config set registry http://registry.url

NPM潜在问题

作为为模块和包服务的工具,NPM十分便捷。它实质上已经是一个包共享平台,所有人都可以贡献并将其打包分享到这个平台上,也可以在许可证的允许下免费使用他们

JS中外部文件的优势

JS中外部文件的优势

在HTML中嵌入js代码虽然没有问题,但一般认为最好使用外部文件来包含代码。不过,并不存在必须使用外部文件的硬性要求,但支持使用外部文件的人多会强调如下优点:

  1. 可维护性:遍及不同HTML页面的js代码会造成维护问题,但把所有js文件放在一个文件夹中,维护起来就轻松多了。而且开发人员也可在不触及HTML标记的情况下,集中精力编辑js代码。
  2. 可缓存性:浏览器能够根据具体的缓存链接到所有的外部文件。也就是说,如果有两个页面都是使用同一个文件,那么这个文件只需下载一次。因此,最终结果就是能够加快页面加载的速度。
  3. 适应未来:通过外部文件来包含js无需使用前面提到的XHTML或注释hack。HTML和XHTM包含外部文件的语法是相同的。

把JavaScript插入到HTML页面中要使用<script>元素。使用这个元素可以把JavaScript嵌入到HTML页面中,让脚本与标记混合在一起;也可以包含外部的JavaScript文件。而我们需要注意的地方有:

  1. 在包含外部JavaScript文件时,必须将src属性设置为指向文件的 url 。而这个文件可以是与包含它的页面位于同一服务器上的文件,也可以是其他文件域中的文件。
  2. 所有<script>元素都会按照它们在页面中出现的先后顺序依次被解析。在不使用defer,async属性的情况下,只有解析完前面<script>代码后才会开始解析后面的</script>元素中的代码。
  3. 由于浏览器会先解析完不使用defer属性的<script>元素中的代码,然后再解析后面的内容,所以一般应该把<script>元素放在页面最后,及主要内容的后面,</body>标签前面。
  4. 使用defer属性可以让脚本在文档完全呈现之后再执行。延迟脚本总是按照它们指定的顺序执行。
  5. 使用ansyc属性可以表示当前脚本不必等待其他脚本,也不必阻塞文档呈现。不能保证异步脚本按照它们在页面中出现的顺序执行。
  6. 另外,使用<noscript>元素可以指定在不支持脚本的浏览器中显示的替代内容。但在启动了脚本的情况下,浏览器不会显示<noscript>元素中的任何内容。

vue进阶

独立构建VS运行时构建

  • 两者区别,独立构建包含模板编译器。模板编译是将编译vue模板字符串成纯js渲染函数,若想用template,则需要编译。
独立构建
  • 独立构建包含模板编译器并支持template选项。他也依赖于浏览器接口的存在,所以你不能用其来为服务器渲染。
运行时构建
  • 运行时构建不包含模板编译器,只能用render选项但即使使用运行时构建,在单文件组件中也依然可以写模板,因为单文件组件的模板会在构建时预编译为render函数。运行时构建要比独立构建轻量30%;

  • 默认npm导出的包是运行时构建。为使用独立构建在webpack配置中添加以下别名

    1
    2
    3
    4
    5
    resolve:{
    alias:{
    'vue$':'vue/dist/vue.js'
    }
    }
  • 不要用import Vue from ‘vue/dist/vue.js’-用一些工具或第三方工具库引入Vue,这可能导致应用程序在同一加载运行时和独立构建并造成错误。

数据劫持

  • 核心方法是通过Object.definedProperty()来实现对属性进行劫持,达到监听数据变动的目的。

beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

数据绑定原理

vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 ),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

组件传值

父组件与子组件传值

父组件传给子组件:子组件通过props方法接受数据;

数据绑定
  • 实现mvvm双向绑定需实现以下几点:
1
2
3
4
1.实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者。
2.实现一个指令解析器Compile,对每个元素节点进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
3.实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的响应回调函数,从而更新视图。
4.mvvm入口函数,整合以上三者。

图片描述

  • 我们知道可以利用Obeject.defineProperty()来监听属性变动
    那么将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 settergetter
    这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。。相关代码可以是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
let data = {name: 'kindom'};
observer(data);
data.name = 'queue';

function observer(data){
if(!data || typeof data!== 'object'){
return;
}
//取出所有属性的遍历
Object.keys(data).forEach(function(key){
defineReactive(data,key,data[key]);
});
}
function defineReactive(data,key,val){
orserver(val); //监听子属性
Object.defineProperty(data,key,{
enumerable: true, //可枚举
configurable: false, //不可再定义
get: function(){
return val;
},
set:functiion(newVal){
val = newVal;
dep.notify(); //通知所有的订阅者
}
});
}
functiion Dep(){
this.subs = [];
}
Dep.prototype = {
addSub: function(sub){
this.subs.push(sub);
},
notify: function(){
this.subs.forEach(function(sub){
sub.update();
})
}
}
实现编译
  • compile主要的工作是解析模板指令,将模板指令替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知更新视图。

图片描述

introction

  • vue是一套构建用户的渐进式框架。与其他重量级框架不同,vue采用自底向上增量开发的设计。
  • 指令带有v-,以表示它们是Vue.js提供的特殊属性,他们会在渲染过的DOM上应用特殊的响应式行为。
  • 计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter ,且计算属性是基于他的依赖缓存的。
v-if VS v-show
  • v-if是真实的条件渲染,因为他会确保条件快在切换当中适当的销毁与重建。若初始条件为假时,则什么也不会做,仅在第一次变为真时才开始局部编译(编译会被缓存起来)。
  • 相比之下, v-show 简单得多——元素始终被编译并保留,只是简单地基于 CSS 切换。
  • 一般来说, v-if 有更高的切换消耗而 v-show 有更高的初始渲染消耗。因此,如果需要频繁切换使用 v-show 较好,如果在运行时条件不大可能改变则使用 v-if 较好。
修饰符
  • .lazy:默认情况下,v-model在input事件中的同步框的值和数据,但你可以添加一个修饰符lazy,从而转变在change事件中同步。
  • .number
  • .trim
组件
  • Vue.component(tagName,options)

  • DOM模板解析说明:当DOM作为模板时,会受到一些HTML的一些限制,因为在Vue只有在浏览器解析和标准化HTML后才能获取模板内容。即table的子标签只能是tr,故需要is来指定。

    1
    2
    3
    <table>
    <tr is="my-row"></tr>
    </table>
  • 组件中的data必须是一个函数使每个组件中的数据引用都有一个自己的作用域,不会互相影响。

  • 父传子:使用props传递数据。组件实例的作用域是孤立的即子组件的模版内不能直接引用父组件的数据。

  • 子传父:通过自定义事件$emit

  • 非父子组件通信

    1
    2
    3
    4
    5
    6
    7
    var bus = new Vue()
    // 触发组件 A 中的事件
    bus.$emit('id-selected', 1)
    // 在组件 B 创建的钩子中监听事件
    bus.$on('id-selected', function (id) {
    // ...
    })

深入响应式原理

如何跟踪变化
  • 把一个普通js对象传给Vue实例来作为他的data选项,Vue将遍历它的属性,用Object.defineProperty将它们转为getting/setting。这是ES5的特性,不能打补丁实现,这便是为什么Vue不支持IE8及以下版本的原因。

  • 用户看不到getting/setting,但在内部它们让Vue追踪依赖,在属性被访问和修改时通知变化。这里需要注意的是浏览器控制台在打印对象时getting/setting的格式化并不同,可通过vue-devtools

    来获取更加友好地检查接口。

  • 每个组件实例都有watcher程序实例,他会在渲染的过程中把属性记录为依赖,之后当依赖项的setting被调用时,就会通知watcher重新计算,从而使和它相关联的组件得以更新。

data

变化检测问题
  • 受现在的js影响,vue不能检测到对象的属性的添加或删除。因为Vue在初始化实例时将属性转为getting/setting,所以必须在data对象上在能让Vue转换它,这样才能是响应的。那种`vm.a=2’不是响应式的,因为js边解释边执行,不可能在初始化时将2付给a

  • Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive properties)。然而它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上:

  • 您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

    1
    2
    3
    Vue.set(vm.someObject, 'b', 2)

    this.$set(this.someObject,'b',2)
  • 有时你想向已有对象上添加一些属性,例如使用Object.assign()或_.extend()方法来添加属性。但是,新添加到对象上的新属性不会被触发更新,这种情况下可可创建一个新的对象,让它包含原对象的属性和新对象属性。

1
2
//代替Object.assign(this.somObject,{a:1,b:2})
this.someObject = Object.assign({},this.someObject,{c:5})
  • 如果不在data中声明message,则message不是一个响应式的,同时Vue将发出警告表明你的渲染方法正试图访问一个不存在的属性。因为在依赖项跟踪系统中,他消除了一类边界情况,也使Vue实例在检查系统的帮助下运行的更高效。在代码可维护方面上这一点很重要:data对象就像组件状态的模式,在它上面声明的所有属性让组织代码更易于被其他开发者或是自己回头重新阅读加速理解。
异步更新队列
  • Vue执行DOM更新是异步的,只要观察到数据变化,Vue就开始一个队列,将同一事件循环内的所有的数据变化缓存起来。如果watch被多次触发,只会推入一次到队列中。然后再接下来循环中,Vue刷新队列并仅执行必要的DOM更新。

过渡效果

  • 元素封装成过渡组件后,在遇到删除或添加时,Vue将
    • 自动嗅探到目标元素是否有CSS过渡或动画,并在合适时添加或删除CSS类名。
    • 如果过渡组件设置了过渡的js钩子函数会在相应的阶段调用钩子函数。
    • 如果没有找到js钩子也无css动画,DOM操作在下一帧立即执行。(注:此指浏览器逐帧动画机制,与Vue及其nextTick概念不同)
  • 当只用js过渡时,在enter和leave中,回调函数done是必须的。否则,他们会被同步调用,过渡立即完成。推荐对于仅使用js过渡的元素添加:css="false",Vue会跳过CSS的检测,这也是避免过渡过程中CSS的影响。

组件传值

非父子组件间传值
  1. 单独的事件中心管理组件间的通信

    var eventHub = new Vue()

  2. 监听事件与销毁事件

    1
    2
    eventHub.$on('事件名称',触发的事件)
    eventHub.$off('事件名称')
  3. 触发事件

    eventHub.$emit('事件名称',数据)

  • 新建一个js文件,然后引入vue,实例化vue,最后暴露该实例。
  • 在要广播的地方引入定义好的实例。
  • 通过ViewEvent.$emit(‘名称’,’数据’)
  • 在要接收的地方通过ViewEvent.on(‘名称’,function(){}),同时可通过$event获取到传过来的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//新建ViewEvent.js文件
import Vue from 'Vue';
let ViewEvent = new Vue();
export default ViewEvent;

//app.vue
<template>
<div>
<home></home>
<news></news>
</div>
</template>

//home.vue
import ViewEvent from 'ViewEvent';
ViewEvent.$emit('test','123');

//news.vue
import ViewEvent from 'ViewEvent';
ViewEvent.$on('test',function(data){
});
父子组件
  • 利用在父组件中的html标签中定义ref属性,通过$refs获取子组件的dom节点进行操作。
  • 利用$parents获取父组件的数据。
  • 父组件可通过$event得到子组件中的信息

子组件通过自定义事件向父组件传递信息

1
<button @click='$emit("enlarge-text",0.1)'>扩大字体</button>

父组件监听子组件的事件

1
<menu-item @enlarge-text='fontSize += $event'></menu-item>
props
  • 除了自定义数据可传外,还可将this或方法(不可加括号)

vue路由配置

  1. 安装

    npm install vue-router --save / cnpm install vue-router --save

  2. 在main.js文件中引入并使用VueRouter

    import VueRouter from vue-router

    Vue.use(VueRouter)

  3. 配置路由

    1. 创建组件,引入组件

    2. 定义路由

      1
      2
      3
      4
      const routes = {
      {path: '/bar',component: Bar},
      {path: '/foo',component: Foo}
      }
    3. 实例化VueRouter

      1
      2
      3
      const router = new VueRouter({
      routes
      })
    4. 挂载

      1
      2
      3
      4
      new Vue({
      el: '#app',
      router
      })
    5. 在跟组件中加<router-view></router-view>

webpack.config配置

  • 当以命令行形式命令运行webpack或webpack-dev-server时,工具会发现我们并没有提供要打包的文件的入口和出口文件,此时他会检查项目根目录中的配置文件并读取这个文件就拿到了导出的这个配置对象,然后根据这个对象,进行打包。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //在内存中,根据指定的模板页面生成一份内存中的首页,同时把自动打包好的bundle.js文件注入到页面底部
    const htmlWebpackPlugin = require('html-webpack-plugin')
    //在输出对象中的plugins(是webpack插件的配置节点)数组中配置节点
    plugins:[
    new htmlWebpackPlugin({
    template:path.join(__dirname,'./src/index.html'), //指定模板文件路径
    filename:'index.html' //设置生成的内存页面的名称
    })
    ]
  • 配置第三方loader模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//下载相应的包
cnpm i style-loader css-loader -D
cnpm i less-loader less -D
cnpm i sass-loader node-sass -D
//webpack.config.js文件中
//在输出对象,与plugins平齐的地方配置模块中
module:{
rules:[
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }, //处理css文件的loader
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, //处理less文件的loader
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }, //处理less文件的loader
]
}
//在main.js文件中导入样式文件
import './css/index.scss';
  • 默认情况下,webpack无法处理css文件中的url地址,不管是图片还是字体库,只要是url地址都处理不了。
1
2
3
4
5
cnpm i url-loader file-loader -D
//在webpack.config.js文件中配置
{ test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader' } //处理文件路径
//若想不使用默认的base64格式,则可用与url地址传参一样
{ test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?limit=100'} //当图片大小大于100字节时会被转为base64格式,小于等于100子节时为普通图片
语法转换(高 ——> 低)
  • 在webpack中。默认只能处理一部分ES6新语法,一些更高级ES6语法或ES7语法,webpack是处理不了的,这时就需要借助第三方loader来帮助webpack处理这些高级语法,当第三方loader把高级语法转为低级语法后,会将结果交给webpack去打包到main.js中。

  • 通过Babel,可将高级语法转为低级语法

    1. 安装包
    1
    2
    cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
    cnpm i babel-preset-env babel-preset-stage-0 -D
    1. 打开webpack的配置文件,在module节点下的rules数组中添加一个新的匹配规则。

      1
      2
      3
      4
      { test: /\.js$/, use:'babel-loader',exclude:/node_modules/}
      注意:在配置babel的loader规则时,必须通过exclude选项将node_modules目录排除在外,原因有两:
      1.不排除node_modules时,Babel会将node_modules中的所有第三方js文件都打包编译,这样非常消耗内存,同时,打包速度也非常慢。
      2.哪怕最终Babel将所有node_modules中的js文件转换完毕,项目也无法正常进行。
    2. 在项目根目录中新建一个叫做babelrc的Babel配置文件,这个配置文件属于JSON格式,故不可加注释,字符串需用双引号。

      1
      2
      3
      4
      5
      在babelrc写如下配置:preset可翻译为【语法】
      {
      "preset": {"env":"stage-0"},
      "plugins": ["transform-runtime"]
      }
包查找规则
  • 在webpack中尝试使用vue,即使用import Vue from 'vue'导入的Vue构造函数,功能不完整,只提供了runtime-only的方式,并没有提供像网页那样的使用方式。可使用import Vue from '../node_modules/vue/dist/vue.js'
  • 包的使用规则
1
2
3
4
1.找项目的根目录中是否有node_modules文件夹
2.在node_modules中根据包名找相应的vue文件夹
3.在vue文件夹中找一个package.json的包配置属性
4.在package.json文件中查找一个main属性【main属性指定了这个包在被加载时的入口文件】

渲染组件

  • 默认 webpack 无法打包 vue文件,需要安装相关的loader
1
2
3
4
5
cnpm i vue-loader vue-template-compler -D
//在配置文件中,新增loader配置项
{ test:/\.vue$/, use: 'vue-loader' }
//导入login组件
import login from './login.vue'
  • 在webpack中,如果想要通过 vue ,把一个组件渲染到页面中去展示vm实例中的render函数可实现。
  • createElements是一个方法,调用它可将指定的组件模板渲染为html结构。
1
2
3
4
5
6
7
8
9
10
11
let vm = new Vue({
el: '#app',
data:{},
methods:{},
render:function(createElements){
return createElements(login);
//注:这里的login会替换掉页面中的el指定的那个容器
}
})
//简写
render: c => c(login)
webpack中使用vue
1
2
3
4
5
6
7
1.安装 vue 包,cnpm i vue -S
2.由于在webpack中推荐使用vue组件模板文件定义组件,故需要安装能解析这种文件的loader cnpm i vue-loader vue-template-compiler -D
3.在main.js中导入vue模块, import Vue from 'vue'
4.定义.vue结尾的组件,由三部分组成:template script style
5.使用import login from './login.vue'导入这个组件
6.创建vm实例 let vm = new Vue({ el: '#app', render: c => c(login) })
7.在页面创建一个id为app的div元素,作为我们vm实例要控制的区域;

注意:App这个组件是通过vm实例的render函数渲染出来的,render函数如果要渲染组件,渲染出来的组件只能放到el:‘#app’ 所指定的元素中。

Account和Goodlist组件是通过路由配置监听到的,所以这两个组件只能展示到属于路由的中去。

加载轮播图

  1. 使用vue-resource获取数据。
  2. 使用vue-resource的this.$http.get获取数据
  3. 获取到的数据保存到data身上。
  4. 使用v-for循环渲染到每个item项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cnpm i vue-resource -D
import VueResource from 'vue-resource'
Vue.use(VueResource);

//组件文件,vue
import { Toast } from 'mint-ui';

methods:{
getLunBotu(){
this.$http.get('https://api.dujin.org/pic/').then(res => {

if(res.status == 200){
this.lunbotulist = res.body[0];
console.log(this.lunbotulist);
}else{
Toast('加载失败')
}
})
}
},
created(){
this.getLunBotu();
}


//解决页面切换动画问题
<transition>
<router-view></router-view>
</transition>


.app-container{
padding-top: 40px;
overflow-x: hidden; //只能将x超出部分隐藏,若将y的也隐藏,则页面不可进行上下滑动
}
.v-enter{ //离开和进入需分开写,确保动画都是往左走
opacity: 0;
transform: translateX(100%);
}
.v-leave-to{
opacity: 0;
transform: translateX(-100%);
position: absolute; //解决动画切换时,内容是从下到上替换
}

.v-leave-active,
.v-enter-active{
transition: all 0.5s ease;
}
Promise使用
  • promise本质就是解决回调地狱问题。
  1. Promise是一个构造函数,故可通过 new Promise() 方法得到一个 Promise的实例。
  2. Promise表示一个异步操作,每当我们 new 一个Promise 实例,就表示一个具体的异步操作。故内部拿到操作结果后,无法使用 return 把操作结果返回给调用者,只能通过回调函数将成功和失败结果返回给调用者。

普通读取文件方法

1
2
3
4
5
6
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname,'./files/1.txt'),'utf-8',(err, dataStr) => {
if(err) throw err;
console.log(dataStr);
})
导入时间插件
1
2
3
4
5
6
7
8
cnpm i moment -S

//导入格式化的插件
import moment from 'moment';
//定义全局的过滤器
Vue.filter('dateFormat',function(dataStr,pattern = "YYYY-MM-DD HH:mm:ss"){
return moment(dataStr).format(pattern);
})
vue-resource请求数据
1
2
3
4
//设置请求的根路径
Vue.http.options.root = 'http://****.com';
//全局设置post 时的表单数据格式组织形式 application/x-www-form-urlencoded
Vue.http.options.emulateJSON = true;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//post请求内容及拼接数据
this.$http.post("api/postcomment/" + this.$route.params.id,{
content: this.msg.trim()
}).then(function(result){
if(result.body.status === 0){
//拼接出一个对象·
let cmt = {
user_name: '匿名用户',
add_time: Date.now(),
content: this.msg.trim()
};
this.comments.unshift(cmt);
this.msg = "";
}
})
制作顶部滑动条
  1. 需要借助于 MUI 中的 tab-top-webview-main.html

  2. 需要把slider 区域 mui-fullscreen 类去掉

  3. 滑动条无法正常触发滑动,通过检查官方文档,发现这是 js 组件,需要初始化一下

    1
    2
    3
    4
    5
    6
    7
    8
    1.导入mui.js文件
    2.调用官方提供的方式去初始化
    mounted(){ //调用时机会重要
    mui('.mui-scroll-wrapper').scroll({
    deceleration: 0.0005
    //flick减速系数,系数越大,滚动速度越慢,滚动距离越小,默认为0.0006
    })
    }
  4. 我们在初始化 滑动条时,导入的 mui.js ,但是控制台报错:因webpack打包好的bundle.js文件默认启用严格模式,故无法解析mui.js文件里的一些属性。

    1
    2
    3
    4
    5
    6
    解决方法:remove "use strict" directive 
    cnpm i babel-plugin-transform-remove-strict-mode -D
    //在 .babelrc文件中
    {
    "plugins":["transform-remove-strict-mode"]
    }
动画transition

当插入或删除包含在transition组件中的元素,Vue将会做以下处理

  1. 自动嗅探目标元素是否应用了css过渡或动画,如果是在恰当的时机添加/删除css类名。
  2. 如果过渡组件提供了js钩子函数,这些钩子函数将在恰当时机被调用。
  3. 如果没有找到js钩子并且没有检测到css过渡/动画,DOM操作*(插入/删除)在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和Vue的nextTick概念不同)
用手机调试项目
1
"dev": "webpack-dev-server --open --port 3000 --hot --host 172.23.232.1"   连接上电脑主机
  1. 保证手机可正常运行;
  2. 要保证手机于开发项目的电脑处于同一个WIFI环境中,也就是说手机可以访问到 电脑的 IP
  3. 打开自己项目的package.json文件,添加 –host指令,把当前电脑的WIFI IP地址设置为 –host 的指令值
    1. 通过cmd终端运行 ‘ipconfig’ ,乍看 无线网的 ip 地址。
使用邮箱发送短信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cnpm i nodemailer -D

const nodemailer = require('nodemailer');
async function main(){
let testAccount = await nodemailer.createTestAcount();
let transporter = nodemailer.createTransport({
host: "smtp.qq.com", //在node_modues-> lib -> well-known -> services.json 中找
port: 465,
secure:true,
auth: {
auth: {
user: '3511564414@qq.com', // 发送给哪个邮箱
pass: 'kzqknaxbiearcife' // 在qq邮箱中 -> 设置 -> 账户 -> 开启POP3/SMTP服务
}
});
let sendObj = {
from: '"Fred Foo 👻" <3511564414@qq.com>', // sender address
to: "3511564414@qq.com", // list of receivers
subject: "Hello ✔", // Subject line
text: "Hello world?", // plain text body
// html: "<b>Hello world?</b>" // html body
};
let info = await transporter.sendMail(sendObj);
}
main().catch(console.error);

webpack学习

npm相关

1
npm i webpack --save-dev      开发环境下的依赖

运行cnpm run dev自动打开

1
2
3
4
//package.json文件
"scripts": {
"dev": "webpack-dev-server --open --port 3000 --hot"
}
1
2
3
4
5
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server",
"build": "webpack"
}

webpack功能

  • 代码转换
  • 文件优化
  • 代码分割
  • 模块合并
  • 自动刷新
  • 代码校验
  • 自动发布
loader功能

loader让webpack能够去处理那些非js文件,(webpack自身只能理解js)。loader能让所有类型的文件转换为webpack能够处理的有效模块,然后就可利用webpack的打包能力对其进行处理。

loader的两大目标:

  1. test属性,用于标识出应该被对应的loader进行转换的某个文件。
  2. use属性,表示在转换时,应使用哪个loader。

webpack使用

  • webpack默认只支持js文件
  • node webpack
1
2
3
4
5
//安装本地的webpack
cnpm i webpack webpack-cli -D
//表示开发依赖
//在命令行上输入webpack即可将入口文件打包
//webpack可以进行0配置
手动配置webpack文件

webpack配置文件webpack.config.js

1
2
3
4
5
6
7
8
module.exports = {
mode: 'development',//模式默认两种,production development
entry: path.join(__dirname,'./src/main.js'),
output: {
path: path.resolve(__dirname,'dist'),//路径必须是一个绝对路径
filename: 'bundle.js',//打包后的文件名
}
}

production:开发的未压缩的文件

development:开发的压缩的文件

更改配置文件名字
1
2
3
4
5
在package.json文件中的script对象上
scripts:{
"builder": "webpack --config webpack.configmy.js"
}
//命令行上运行 cnpm run builder即可
webpack以本地形式打开文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cnpm i webpack-dev-server -D
//webpack.config.js
module.exports = {
devServer:{ //开发服务器配置
port: 3000, //在3000端口打开
progress: true, //显示进度条
contentBase: './build', //以当前目录运行程序
compress: true //启动压缩

}
}
//package.json文件
scripts:{
"dev": "webpack-dev-server"
}
webpack解析html模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cnpm i html-webpack-plugin -D
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
output: {
path: path.resove(__dirname,'./dist'),
filename: 'bundle.[hash].js', //每次输出的文件都不同
//filename: 'bundle.[hash:8].js', 只显示8位hash值
}
plugins: [ //数组 放着所有的webpack插件
new HtmlWebpackPlugin({
template: './src/index.html' //以index.html文件为模板
filename: 'index.html', //打包后的文件名
minify: {
removeAttributeQuotes:true, //压缩时,删除属性中的双引号
collapseWhiteSpace: true, //折叠空行,将html文件打包成一行

},
hash: true, //添加哈希戳
})

]
}
webpack解析css模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
cnpm i style-loader css-loader -D  //处理css文件
cnpm i less less-loader -D //less-loader会调用less进行解析
cnpm i node-sass sass-loader -D //处理sass和scss文件
cnpm i stylus stylus-loader -D //处理stylus文件

//main.js文件导入css文件
require('./index.css');

//webpack.config.js
module.exports = {
module: { //模块
rules: [ //规则 css-loader 处理@import这种语法的,将多个css文件合并为一个css文件
//style-loader 他是把css插入到head的标签中
//loader特点:希望一个loader处理一个功能
//只用一个loader用字符串,多个loader用数组,当有参数时可写成对象方式
//loader顺序:默认从右向左执行
{
test: /\.css$/,
use: [
{
loader:'style-loader',
options: {
insertAt: 'top' //将样式插到head顶部
}
},
'css-loader'
]
},
{ test: /\.less$/, use: ['style-loader','css-loader','less-loader'] }
]

}
}
抽离文件的插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cnpm i mini-css-extract-plugin -D
//专门用于抽离css样式的插件

//webpack.config.js
let MiniCssExtractPlugin = require('mini-css-extract-plugin');
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filenmae: 'main.css' //抽离出的名字叫main.css
})
],
module: {
rules:[
{
test:/\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader' //先解析成css样式,然后使用MiniCssExtractPlugin这个插件的加载器将样式分离成一个main.css文件
]
}
]
}
自动加前缀的插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cnpm i postcss-loader autoprefixer 

module: {
rules: [
{
test: /\.less$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
}

//新建一个postcss.config.js文件
module.exports = {
plugins: [require('autoprefixer')]
}
webpack中处理js模块
  • 将es6转为es5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cnpm i babel-loader @babel/core @babel/preset-env -D
// @babel/core是babel的核心模块,@babel/preset-env 将高级语法转为低级语法
cnpm i eslint eslint-loader -D //语法检查

cnpm i @babel/plugin-proposal-class-properties -D //像class类这些高级语法

cnpm i @babel/runtime @babel/plugin-transform-runtime -D @babel/runtime //像generator遍历器,在plugins中配置

//webpack.config.js
module.exports = {
module: {
noParse: /jquery/, //不去解析jquery中的依赖库
rules: [
{ test: /\.js$/,
use: {
{
loader:'babel-loader', //用该模块将es6转为es5
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties','@babel/plugin-transform-runtime']
},
{
loader: 'eslint-loader', //校验es语法规范
options: {
enforce: 'pre' //强制先执行,因为loader默认是从下往上执行
}
}
}
},
exclude: /node_modules/, //不去node_module中找
include: path.resolve(__dirname,'src') //去src文件夹下找
}
]
}
}
webpack第三方模块的使用
1
2
3
4
5
6
7
8
module.exports = {
resolve: { //解析第三方包 common
modules: [path.resolve('node_modules')], //解析时先到该目录下寻找
alias:{ //别名 vue vue.runtime
bootstrap: 'bootstrap/dist/css/bootstrap.css'
}
}
}
1
2
3
4
5
6
7
8
9
10
cnpm i jquery 
cnpm i expose-loader

//main.js文件
import $ from 'jquery'; //此$不是window上的的$
window.$ //undefined

import $ from 'expose-loader?$!jquery';
//expose-loader 暴露 全局的loader 内联的loader
//pre 前面执行的loader normal 普通loader 内联loader 后置 postloader

将$暴露给window的另一种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cnpm i jquery 
cnpm i expose-loader

//main.js文件
import $ from 'jquery';

//webpack.config.js文件
module: {
rules: [
{
test:require.resolve('jquery'),
use: 'expose-loader?$!jquery'
}
]
}

在每个模块中注入$对象

1
2
3
4
5
6
7
8
9
10
11
12
13
//webpack.config.js文件
const webpack = require('webpack')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new Webpack.ProvidePlugin({ //在每个模块中注入$
$: 'jquery'
})
]
}
1
2
3
4
5
6
7
8
9
//main.js文件
import $ from 'jquery' //运行npm run build时会将jquery打包

//不像将jquery打包,只需在webpack.config.js文件中
module.exports = {
externals: {
jquery: '$'
}
}
webpack打包图片
  1. 在js中创建图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cnpm i file-loader -D  
//file-loader默认会在内部生成一张图片到dist文件夹下,并把生成的图片的名字返回回来

//main.js文件
import logo from './logo.png';
//把图片引入,返回结果是一个新的图片地址
let image = new Image();
image.src = logo; //就是一个普通的字符串
document.body.appendChildren(image)

//webpack.config.js文件
module.exports = {
module: {
rules: [
{ test: /\.(png|jpg|gif)$/, use: 'fle-loader' },
]
}
}
  1. 在css引入background(‘url’)

  2. 使用标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    cnpm i html-withimg-loader -D
    cnpm i url-loader -D
    //在html文件中引入image,但是路径不在打包目录中的路径中
    module.exports = {
    module: {
    rules: [
    {test: /\.html$/, use: 'html-withimg-loader'},
    {
    test: /\.(png|jpg|gif)$/,
    //做一个限制,当图片小于多少k时用base64来转化
    //否则用file-loader产生真实的图片
    use: {
    loader: 'url-loader',
    options: {
    limit: 200*1024,
    outputPath: '/img/' //输出路径放置在img目录下
    publicPath: 'http://www.chenchuyin.com' //统一给图片加上该域名
    }
    }
    }
    ]
    }
    }
webpack有多入口文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cnpm i html-webpack-plugin - D
cnpm i @babel/core @babel/preset-dev @babel-loadedr @webpack-dev-server -D
//webpack.config.js文件
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//源码映射 会单独生成一个sourcemap文件,出错了胡hi标识当前出错的列和行
devtool: 'source-map', //增加映射文件 可以帮我们调试源代码
//多入口
entry: {
home: './src/index.js',
other: './src/other.js'
},
output: {
//[name]表示 home 和 other [hash]添加哈希戳
filename: '[name].[hash].js',
path: path.resolve(__dirname,'dist')
},
plugins: [
//若无chunks,会把两个js文件都引入到index.html和other.html文件中
new HtmlWebpackPlugin({
tempalte: './index.html',
filename: 'index.html',
chunks: ['index'] //index.html文件引入index.js文件
}),
new HtmlWebpackPlugin({
tempalte: './other.html',
filename: 'other.html',
chunks: ['other','index'] //other.html文件引入other.js和index.js文件
})
]
}
  1. 不会产生单独文件,但是可以显示行和列

    1
    devtol:'eval-source-map'
  2. 不会产生列,但是是一个单独的映射文件

    1
    devtoole: 'cheap-module-source-map'  //产生后你可以保存起来
  3. 不会产生文件,集成在打包后的文件中,不会产生列

    1
    devtoole: 'cheap-module-eval-source-map'
自动打包出实体文件
1
2
3
4
5
6
7
8
module.exports = {
watch: true, //监听文件,只要文件有所修改便直接生成新的实体文件
watchOptions:{ //监控选项
poll: 1000, //每秒 查看文件是否更改 1000次
aggreatement: 500, //防抖 如果一直输入代码,则每过500次再生成实体文件
ignored: /node_modules/ //不需要进行监控的文件
}
}
webpack小插件
  1. cleanWebpackPlugin
  2. copyWebpackPlugin
  3. bannerPlugin(内置的,不需要安装该模块)
1
2
3
4
5
6
7
8
9
10
11
12
13
cnpm i clean-webpack-plugin copy-webpack-plugin webpack -D

const CleanWebpackPlugin = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
plugins: [
new CleanWebpackPlugin('./dist'), //打包之前先将dist目录清除
new CopyWebpackPlugin(
{from: 'doc', to: './'} //将doc.txt文件复制到输出的根目录下
),
new webpack.BannerPlugin('make 2019 by chenchuyin')
//会将这句话插入到出口js文件的开头部分
]
webpack实现跨域功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//webpack-dev-server默认打开localhost:8080,若想访问3000端口,可使用代理
module.exports = {
devServer:{
proxy: { //重写的方式 把请求代理到express服务器上
// '/api': 'http://localhost:3000', //配置了一个代理
'api': {
target: 'http://localhost:3000',
pathRewrute: {'api': ''}
}
}
}
}

//server.js文件
const express = require('express')
let app = express();
app.get('/user', (req,res) => {
res.json({name:'陈楚吟'})
})
app.listen(3000);

//index.js文件
let xhr = new XMLHttpRequest();
xhr.open('GET','/api/user',true); //请求还是有api目录,显示时将其删除
xhr.onload = function(){
console.log(xhr.response);
}
xhr.send();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
devServer:{
//我们前端只想单纯模拟数据
before(app){
app.get('/user', (req,res) => {
res.json({name: '陈楚吟'})
})
}
}

//server.js文件
const express = require('express')
let app = express();
app.listen(3000);

//index.js文件
let xhr = new XMLHttpRequest();
xhr.open('GET','/api/user',true); //请求还是有api目录,显示时将其删除
xhr.onload = function(){
console.log(xhr.response);
}
xhr.send();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cnpm i webpack-dev-middleware -D

devServer:{
//有服务端,不用代理来处理,在服务端中启动webpack端口用服务端端口(即前端页面和服务器在同一个端口)

}

//server.js文件
const express = require('express');
const webpack = require('webpack');
let app = express();

//中间件
let middle = require('webpack-dev-middleware');
let config = require('./webpack.config.js');
let compiler = webpack(config);
app.use(middle(compiler));
app.get('/user', (req,res) => {
res.json({name:'陈楚吟'})
})
app.listen(3000);
实现多线程打包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cnpm i happypack
//模块happypack可以实现多线程来打包进程
let Happypack = require('happypack')
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
include: 'src',
use: 'Happypack/loader?id=js'
},
{
test: /\.css$/,
use: 'Happypack/loader?id=css'
}
]
},
palugins: [
new Happypack({
id: 'css',
use: ['style-loader','css-loader']
})
new Happypack({
id: 'js',
use: [{
loader:'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}]
})
]
webpack自带优化
1
2
3
4
//import 在生成环境下会自动去除没用的代码
//因tree-shaking方法会将没用到的代码自动删除掉·

//require(es6)模块会把结果都放到default上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = { 
optimization: { //将公共部分抽离出来
splitChunks: { //分割代码块
cacheGrounps:{ //缓存组
common:{ //公共的模块
chunks: 'initial', //代码在刚开始时就被抽离出
minSize: 0, //代码有多少字节才被抽离
minChunks: 2 //代码块被使用多少次才被抽离
},
vendor:{
priority: 1, //抽离权重,较大的先抽离
test: /node_modules/, //将第三方模块抽离出来
chunks: 'initial',
minSize: 0,
minChunks: 2
}
}
}
}
}
webpack懒加载
  • vue和react的懒加载其实都是通过import实现
1
2
3
4
5
6
7
8
9
10
//index.js
let button = document.createElement('button')
button.innerHTML = 'hello';
button.addEventListener('click',function(){
//import 是草案中的语法,由jsonp实现动态加载文件
//返回值是一个1promise对象
import('./source.js').then(data => {
console.log(data.default);
})
})
webpack热更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
devServer: {
hot: true, //启动热更新操作
port: 3000
},
plugins: [
new webpack.NameModulesPlugin(), //告诉我们哪个模块热更新
new webpack.HotModuleReplacementPlugin() //热更新插件
]
}

//index.js文件
import str from './source';
if(module.hot){
module.hot.accept('./source', () => {
require('./source');
})
}
Tapable
  • webpack本质上是一种事件流机制,它的工作流程是将各个插件串联起来,而实现这一切的核心是Tapable,Tapable是有点类似于nodeJs的events库,核心原理也是依赖于发布订阅模式。
1
2


webpack中使用vue

在普通网页中使用vue

1
2
3
4
5
6
7
8
9
10
//1.使用script标签,引入vue的包   (此包功能较全)
<scrpit src="./lib/vue.js"></srcipt>

//2.在index页面中,创建一个id为app的div容器
<div id="app"></div>

//3.通过new Vue得到一个vm实例
let vm = new Vue({
el: '#app'
})

在webpack中尝试使用vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//注意:在webpack中,使用import Vue from 'vue' 导入的Vue构造函数功能不完整,只提供了runtime-only的形式,并无提供像网页中那样的使用方式;
import Vue from 'vue'

let vm = new Vue({
el: '#app'
})

解决:指定完整vue.js文件的路径
方式一:import Vue from './node_modules/vue/dist/vue.js';
方式二:import vue from 'vue'
在webpack.config.js文件中
module.exports = {
resolve: {
alias: { //修改vue时被导入时包的路径
"vue$": "vue/dist/vue.js"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cnpm i vue -S
cnpm i vue-loader vue-template-compiler -D

//webpack.config.js文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
mode: "development",
entry: './src/index.js',
output: {
path: path.resolve(__dirname,'./dist'),
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new VueLoaderPlugin()
],
module: {
rules: [
{ test: /\.css/, use: ['style-loader','css-loader'] },
{ test: /\.vue/, use: 'vue-loader' }
]
}
}
1
2
3
4
5
6
7
1.安装 vue 包,cnpm i vue -S
2.由于在webpack中推荐使用vue组件模板文件定义组件,故需要安装能解析这种文件的loader cnpm i vue-loader vue-template-compiler -D
3.在main.js中导入vue模块, import Vue from 'vue'
4.定义.vue结尾的组件,由三部分组成:template script style
5.使用import login from './login.vue'导入这个组件
6.创建vm实例 let vm = new Vue({ el: '#app', render: c => c(login) })
7.在页面创建一个id为app的div元素,作为我们vm实例要控制的区域;

注意:App这个组件是通过vm实例的render函数渲染出来的,render函数如果要渲染组件,渲染出来的组件只能放到el:‘#app’ 所指定的元素中。

Account和Goodlist组件是通过路由配置监听到的,所以这两个组件只能展示到属于路由的中去。

包的查找规则

找vue文件

  1. 找项目根目录中有没有 node_modules 的文件夹
  2. 在node_modules中根据包名找对应的vue文件夹
  3. 在vue文件夹中,找一个叫package.json 的包配置文件
  4. 在 package.json 文件中,查找一个main 属性【main属性指定了这个包在被加载时的入口文件】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//sf-pack.js文件
//需要找到当前执行命令的路径,拿到webpack.config.json
const path = require('path')

//config配置文件
let config = require(path.resolve('webpack.config.js'));

let Compiler = require('../lib/Compiler.js');
let compiler = new Compiler(config);
//标识运行编译
compiler.run();

//Compiler.js文件
const path = require('path');
const fs = require('fs');
class Compiler{
constructor(config){
//entry output
this.config = config;
//需要保存入口文件的路径
this.entryId; //'./src/index.js'
//需要保存所有模块依赖
this.modules = {};
this.entry = config.entry; //入口路径
//工作路径
this.root = process.cwd();
}
getSource(modulePath){
let content = fs.readFileSync(modulePath,'utf-8');
return content;
}
//构建模块
buildModule(modulePath,isEntry){
//拿到模块内容
let source = this.getSource(modulePath);
//模块id modulePath = modulePath - this.root;
let modulePath = './' + path.relative(this.root,modulePath);
}
run(){
//执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root,this.entry),true);
//true表示为主模块
//发射一个文件,该文件即为打包后的文件
this.emitFile();
}
}
module.exports = Compiler

攻克object对象

Object

  • Object构造函数创建一个对象包装器。如果给定值是null或undefined,将返回一个空对象

改变原型链的方法

几乎所有的js对象都是Object的实例;一个典型的对象继承Object.prototype的属性及方法。改变Object原型会通过原型链改变所有对象。

  • Object.create(null)创建的对象
  • Object.setPrototypeOf()
  • obj.__proto__ = 原型对象

Object构造函数的属性

  • Object.length //值为1

  • Object.prototype :可以为所有Object类型的对象添加属性(现在一般用class类代替)

    • Object.prototype.constructor
      • 返回创建实例对象的Object构造函数的引用(new 关键字后面的值);注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。对原始类型来说,如1true"test",该值只可读。
    1
    2
    3
    4
    var arr = [];
    arr.constructor //Array
    var obj = new zhuTou()
    obj.constrctor //zhuTou

Object构造函数的方法

Object.assign()复制

Object.assign( targetObj, obj1,obj2,…)

  • 将obj1,obj2等对象里的自身属性且为可枚举属性复制到targetObj对象上,属于浅拷贝。
  • 若有相同属性,则targetObj里的属性会被覆盖。
  • 返回值是一个与targetObj一样的对象,两者绝对相等。
  • Deep Clone let obj3 = JSON.parse(JSON.stringify(obj1)); obj1与obj3更改属性值互不影响。
  • 可拷贝symbol类型属性,继承属性和不可枚举属性不能被拷贝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let targetObj = {name:'targetObj'}
let obj1 = Object.create({foo:1},{
bar1:{
value:1 //bar是一个不可枚举属性
},
bar2:{
value:2,
enumerable:true //bar2是个自身可枚举属性
},
})
let obj2 = { [Symbol('foo')]:2,name:'obj1' };
let
let obj = Object.assign(targetObject,obj1,obj2)
{
name:'obj1', //后来居上,覆盖targetObj里的值
[Symbol('foo')]:2, //可拷贝symbol类型属性
bar2 : 2, //bar1不可枚举,bar2指定可枚举
}
  • 原始类型会被包装成对象
1
2
3
4
5
6
7
8
9
const v1 = "abc";
const v2 = true;
const v3 = 10;
const v4 = Symbol("foo")

const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
  • 异常会打断后续拷贝任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const target = Object.defineProperty({}, "foo", {
value: 1,
writable: false
}); // target 的 foo 属性是个只读属性。

Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: "foo" is read-only
// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。

console.log(target.bar); // 2,说明第一个源对象拷贝成功了。
console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
console.log(target.foo); // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。
  • 异常会打断后续拷贝任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//以下函数会拷贝所有自有属性的属性描述符
function completeAssign(target,...sources){
sources.forEach(source => {
let descritors = Object.keys(source).reduce( (descriptors,key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source,key);
return descriptors;
},{});
//Object.assign默认也会拷贝可枚举的Symbols
object.getOwnPropertySymbols(source).forEach( sym => {
let descriptor = Object.getOwnPropertyDescriptor(source,sym);
if(descriptor.enumerable){
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target,descriptors);
});
return target;
}
  • polyfill不支持 symbol 属性,因为ES5 中根本没有 symbol :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}

let to = Object(target);

for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];

if (nextSource != null) { // Skip over if undefined or null
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
Object.create()继承
  • Object(proto[,propertiesObject]):实现类式继承

    • proto:新创建的原型对象。
    • propertiesObject:未指定为undefined,要添加到新创建对象的可枚举属性(不是其原型链上的枚举属性,而是自定义属性)对象的属性描述符以及相应的属性名称。若为null或非原始包装对象抛出TypeError异常。
    • 返回一个带着指定原型对象和属性的新对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function Shape(){	//父类
    this.x = 0;
    this.y = 0;
    }
    Shape.prototype.move = function(x,y){
    this.x += x;
    this.y += y;
    console.log(this,'shape moved');
    };
    function Rectangle(){
    Shape.call(this);
    }
    Rectangle.prototype = Object.create(Shape.prototype);
    Rectangle.prototype.constructor = Rectangle;//没有这句,输出的this指向父类Shape
    var rect = new Rectangle();
    console.log(rect instanceof Rectangle);
    console.log(rect instanceof Shape);
    rect.move();
    //true
    //true
    //Rectangle { x: NaN, y: NaN } 'shape moved'
    • 继承多个对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function MyClass(){
    SuperClass.call(this);
    otherSuperClass.call(this);
    }
    //继承一个类
    MyClass.prototype = Object.create(SuperClass.prototype);
    //混合其他
    Object.assign(MyClass.prototype,OtherSuperclass.prototype);
    //重新指定constructor
    MyClass.prototype.constructor = MyClass;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    var o;
    // 创建一个原型为null的空对象
    o = Object.create(null);

    o = {};

    // 以字面量方式创建的空对象就相当于:
    o = Object.create(Object.prototype);
    o = Object.create(Object.prototype, {
    // foo会成为所创建对象的数据属性
    foo: {
    writable:true,
    configurable:true,
    value: "hello"
    },
    // bar会成为所创建对象的访问器属性
    bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
    console.log("Setting `o.bar` to", value);
    }
    }
    });


    function Constructor(){}
    o = new Constructor();
    // 上面的一句就相当于:
    o = Object.create(Constructor.prototype);
    // 当然,如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码


    // 创建一个以另一个空对象为原型,且拥有一个属性p的对象
    o = Object.create({}, { p: { value: 42 } })

    // 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的:
    o.p = 24
    o.p
    //42

    o.q = 12
    for (var prop in o) {
    console.log(prop)
    }
    //"q"

    delete o.p
    //false

    //创建一个可写的,可枚举的,可配置的属性p
    o2 = Object.create({}, {
    p: {
    value: 42,
    writable: true,
    enumerable: true,
    configurable: true
    }
    });
Object.defineProperties()
  • Object.defineProperties(obj,props):在对象obj上定义或修改现有属性,并返回该对象obj
  • props:要定义的其可枚举属性或修改的属性描述符对象。对象上存在的属性描述符主要有两种:数据描述符(有值的属性,该值可写,也可能不可写)和访问器描述符。描述符具有以下键:
    • configurable:默认为false。描述属性能否被修改或删除,一旦定义值后不可修改,否则会抛出错误。但是删除configurable属性值为false的属性值nothing happen,也不会报错。
    • enumerable:默认为false。能否使用for…in循环和Object.keys(),是否可枚举。
    • value:默认undefined。可为任何有效js值。
    • writable:默认false。是否可重写。
    • get:默认undefined。作为函数的getter函数,若无getter函数则为undefined。
    • set:与get同理。
object.defineProperty()
  • Object.defineProperty(obj,prop,descriptor):返回被传递给函数的对象,即obj,该方法式定义key为Symbol的属性的方法之一。
  • 通过简单赋值操作的普通属性可delete,通过该方法定义的属性值不能修改
  • 如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
  • 如果某些属性是继承来的,需要先冻结Object.prototype,明确指定所有属性,或通过Object.create(nill)将__proto__属性指向null。var des
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//使用__proto__
var obj = {};
var descriptor = Object.create(null);//没有继承的属性
//默认没有enumerable,没有configurable,没有writable
descriptor.value="static";
Object.defineProperty(obj,"key",descriptor);

//显式定义
Object.defineProperty(obj,"key",{
enumerable:false,
confingurable:false,
writable:false,
value:'static'
});

//循环使用同一个对象
function withValue(value){
var d = withValue.d || (
withValue.d = {
enumerable:false,
configurable:false,
writable:false,
value:null
}
);
d.value = value;
return d;
}
Object.defineProperty(obj,"key",withValue("static"));

//如果freeze可用,防止代码添加或删除对象圆形的属性
(Object.freeze || object)(Object.prototype);
  • 如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是falsevaluegetset字段的默认值为undefined。一个没有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数据描述符。
  • 如果访问者的属性是被继承的,它的get和set方法会在子对象的属性被修改或访问时调用。如果这些方法用一个变量存值,该值会被所有对象共享。
1
2
3
4
5
6
7
8
9
10
11
12
function myClass(){}
var value;
myClass.prototype.x = 2;
Object.defineProperty(myClass.prototype,"x",{
get(){ return value; },
set(x){ value = x; }
});
var a = new myClass();
var b = new myClass();
a.x = 1;
b.x //1
myClass.prototype.x //2 实例上的x与原型上的x不同,更改实例上的值不影响原型上的值
  • 这可通过将值存入另一个属性中解决,在get或set中,this指向某个被访问的被访问和修改属性的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myClass(){}
Object.defineProperty(myClass.prototype,"x",{
get(){ return this.stored_x; },
set(x){ this._stored_x = x; }
});
myClass.prototype.x = 1;
Object.defineProperty(myClass.prototype,"y",{
writable:false,
value:"y"
});

var a = new myClass();
var b = new myClass();
a.x = 2;
b.x //undefined
Object.entries()
  • Object.entries(obj):返回一个给定对象自身可枚举属性的键值对数组,其排列顺序与for…in循环(会遍历原型链上的可枚举属性)返回顺序相同。如果是类数组则按数字键从小到大排列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

// array like object
const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]

// array like object with random key ordering
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

// getFoo is property which isn't enumerable
const myObj = Object.create({}, { getFoo: { value() { return this.foo; } } });
myObj.foo = 'bar';
console.log(Object.entries(myObj)); // [ ['foo', 'bar'] ]

// non-object argument will be coerced to an object
console.log(Object.entries('foo')); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]

// iterate through key-value gracefully
const obj = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
}

// Or, using array extras
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});
Object.freeze()
  • Object.freeze(obj):将obj对象冻结,不能对其自身属性进行任何操作,同时该对象的原型也不能修改。如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。数组作为一种对象,被冻结,其元素不能被修改。没有数组元素可以被添加或移除。
  • 这个方法返回传递的对象obj,而不是创建一个被冻结的副本。
1
2
3
4
5
6
//浅冻结
obj1 = { name:'nikita', internal:{} };
Object.freeze(obj1);
obj1.name = 'hello'
obj1.internal.a = 1;
obj1 //{name: "nikita", internal: {a:1}}
  • 递归冻结每个对象属性,即深度冻结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function deepFreeze(obj){
//取回定义在obj上的属性名
var propNames = object.getPropertyName(obj);
//在冻结对象之前冻结属性
propNames.forEach(function(name){
var prop = obj[name];
if(typeof prop == 'object' && prop !== null){
deepFreeze(prop);
}
});
//冻结自身
return Object.freeze(obj);
}
obj2 = {
internal: {}
};

deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
Object.getOwnPropertyDescriptor()
  • Object.getOwnPropertyDescriptor(obj,prop):返回指定对象自身属性的属性描述符。
1
2
3
4
5
6
7
8
9
10
var o, d;

o = { get foo() { return 17; } };
d = Object.getOwnPropertyDescriptor(o, "foo");
// d {
// configurable: true,
// enumerable: true,
// get: /*the getter function*/,
// set: undefined
// }
  • 若第一个参数不是对象,ES5报错,ES6强制转换为对象。
1
2
3
4
5
6
7
8
9
10
Object.getOwnPropertyDescriptor('foo', 0);
// 类型错误: "foo" 不是一个对象 // ES5 code

Object.getOwnPropertyDescriptor('foo', 0);
// Object returned by ES2015 code: {
// configurable: false,
// enumerable: true,
// value: "f",
// writable: false
// }
  • Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
  • 浅拷贝一个对象
1
2
3
4
Object.create(
Object.getPrototypeOf(obj),
Object.getownpropertyDescriptors(obj)
);
  • 创建子类
1
2
3
4
function superClass(){}
superClass.prototype={}
function subClass = {}
subClass.prototype = Object.create(superClass.prototype,Object.getOwnpropertyDescriptors({}));
Object.getOwnPropertyNames(obj)
  • 返回一个包含所有属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组
Object.keys(obj)
  • 返回自身属性的字符串数组
Object.values(obj)
  • 返回给定对象自身的所有可枚举属性值的数组。
Object.is()

object.is(val1,val2):判断两个值是否是相同的值,作用与”==”相似,只不过Object.is()不会隐式类型转换,返回boolean值。

注意相等的几种情况

  • 两个值都是 undefined
  • 两个值都是 null
  • 两个值都是 true 或者都是 false
  • 两个值是由相同个数的字符按照相同的顺序组成的字符串
  • 两个值指向同一个对象
  • 两个值都是数字并且
    • 都是正零 +0
    • 都是负零 -0
    • 都是 NaN
    • 都是除零和 NaN 外的其它同一个数字
1
2
3
4
5
// 特例
Object.is(0, -0); // false
Object.is(0, +0); // true
Object.is(-0, -0); // true
Object.is(NaN, 0/0); // true
Object.isExtensible(obj)
  • 检测对象obj是否可扩展(能否加新的属性),返回boolean值。
  • 默认是可扩展的,即可为他们添加新的属性。以及他们的__prop__属性也是可被更改的。Object.preventExtensions,Object.seal或Object.freeze方法都可使对象obj变成不可扩展。
  • 若参数不是object类型,ES5会报错,ES6会将其转为不可扩展的普通对象.
Object.isFrozen(obj)
  • 判断对象obj是否被冻结,冻结指它不可扩展,所有属性所有属性都是不可配置的,且所有数据属性(即没有getter或setter组件的访问器)都是不可写的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var oneProp = {p:42}
// 让这个对象变的不可扩展,并不意味着这个对象变成了冻结对象,
// 因为p属性仍然是可以配置的(而且可写的).
Object.preventExtensions(oneProp);
Object.isFrozen(oneProp) //=== false
//此时如果删除了这个属性,则它会变成一个冻结对象
delete oneProp.p;
Object.isFrozen(oneProp) //===true
//一个不可扩展的对象,拥有一个不可写但可配置的属性,则它仍是不可冻结的
var nonWritable = {e:'plep'};
Object.preventExtensions(nonWritable);
Object.defineProperty(noWritable,'e',{writable:false});
Object.isFrozen(nonWritable) //===false
//将该属性变为不可配置则可让其变为冻结对象
Object.defineProperty(noWritable,'e',{configurable:false});
Object.isFrozen(nonWritable) //===true
  • 如果参数不是对象类型,ES5报错。ES6将其视为一个冻结的普通对象。
Object.isSealed(obj)
  • 判断一个对象是否被密封,返回boolean值。密封对象是指那些不可扩展且所有自身属性都不可配置且不可删除(但不一定是不可写的)的对象。
Object.hasOwnProperty(str/symbol)
  • 判断某个属性是否为对象自身属性,与in不同,该方法会忽略掉从原型链上继承来的属性。即使属性值为null或undefined,只要属性存在都返回true
Object.toString()
  • 返回【Object type】
1
2
3
4
5
6
7
8
9
var toString = Object.prototype.toString;

toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]

//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
obj.valueOf()
  • 返回指定对象的原始值
对象 返回值
Array 返回数组对象本身。
Boolean 布尔值。
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function 函数本身。
Number 数字值。
Object 对象本身。这是默认情况。
String 字符串值。
Math 和 Error 对象没有 valueOf 方法。

注意:字符串上下文中的对象通过 toString()方法转换,这与使用valueOf转换为原始字符串的String对象不同。所有对象都能转换成一个“[object *类型*]”这种格式的字符串。但是很多对象不能转换为数字,布尔或函数。

1
2
3
4
5
6
// new一个Boolean对象
var newBool = new Boolean(true);
// valueOf()返回的是true,两者的值相等
console.log(newBool.valueOf() == newBool); // true
// 但是不全等,两者类型不相等,前者是boolean类型,后者是object类型
console.log(newBool.valueOf() === newBool); // false
Object.seal(prop)

**Object.seal()**方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。

qq小程序天坑之旅

qq小程序

吐槽qq小程序开发者工具
  • 不能通过uni-app打开。
  • 项目文件间互相引用问题颇多。
  • 开发工具容易崩溃。
  • 文档不够详尽。
注意点
  • 在组件qss中不应使用id,属性,标签选择器,最好用类选择器。
  • qml文件标签名只能是小写字母,中划线,下划线的组合,所以自定义组件的标签名也只能包含这些字符。
  • 自定义组件也是可以引用自定义组件,引用方法类似于页面引用自定义组件的方式usingComponent字段
  • 自定义组件和页面所在项根目录名不能以qq-为前缀,否则会报错。
  • 是否在页面文件中使用 usingComponents 会使得页面的 this 对象的原型稍有差异,包括:
    • 使用 usingComponents 页面的原型与不使用时不一致,即 Object.getPrototypeOf(this) 结果不同。
    • 使用 usingComponents 时会多一些方法,如 selectComponent
    • 出于性能考虑,使用 usingComponents 时, setData 内容不会被直接深复制,即 this.setData({ field: obj })this.data.field === obj。(深复制会在这个值被组件间传递时发生。)
  • 直接修改this.data而不调用this.setData是无法改变页面的状态的,还会造成数据不一致
  • 不要将data里的任何一项的value设为undefined,否则这一项将不被设置并可能会存在一些潜在问题。
  • navigateTo,redirectTo只能打开非tabBar页面,switchtab只能打开tabBar的页面。reLaunch可打开任意页面。调用页面路由带的参数可以在目标页面的onLoad中获取。
  • 通过全局函数getApp()可以获取全局的应用实例,如果需要全局的数据可以在App()中获取。
  • require只支持相对路径,不支持绝对路径。
  • 约定以on开头的API用来监听某个时间是否发生,当事件触发时会调用这个回调函数并将相关数据以参数的形式传入。
  • 除继承样式外,app.qss中的样式,组将所在页面的样式对自定义组件无效。组件可以指定他所在节点的默认样式,使用:host选择器。
使用自定义组件
  1. 在自定义组件中的json文件定义{"component":true}
  2. 在自定义组件中的qml编写组件模板(同index.qml写的相同),qss中加样式。(在组件qss中不应使用id,属性,标签选择器,最好用类选择器)
  3. 在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod() {}
},
externalClasses:['my-class']//从index页面中传过来的类
})
  1. 在index.json文件中引用声明。
1
2
3
4
5
6
7
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
//路径是相对于index文件夹的
//component-tag-name是在页面中使用的组件名
  1. index页面中使用组件。
1
2
3
4
<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text" class="my-class"></component-tag-name>
</view>
父子组件间传值

注:设index页面是引用页面,component为组件,便于理解。

1
2
3
4
5
6
7
8
<view>
<component-tag-name prop-a="{{dataFieldA}}" prop-b="{{dataFieldB}}">
<!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
<view>这里是插入到组件slot中的内容</view>
</component-tag-name>
</view>
<!-- 在引用组件中的js文件中定义dataFiledA,并通过属性propA传给组件 -->
<!-- 标签中中划线连接者,在组件中都需要将其转为驼峰式 -->

在组件的js文件中通过properties进行接收

1
2
3
4
5
Component({
properties:{
propA:Object //注名类型
}
})

在组件的qml文件中引用

1
2
3
4
5
<view class="wrapper">
<view>这是组件内部的节点</view>
<slot></slot>
{{propA.name}}
</view>
组件中使用多slot
  • 默认情况下,一个组件只能使用一个slot。需要使用多slot时,可以在组件js中声明启用。
1
2
3
4
5
6
7
8
Component({
options:{
multipleSlots:true
},
properties:{},
data:{},
methods:{}
})
  • 在组件模板component.qml使用多个slot,以不同name值区分
1
2
3
4
5
<view class="wrapper">
<slot name = "before"></slot>
<view>这里是组件的内部细节</view>
<slot name="after"></slot>
</view>
  • 在index.qml中使用时,用 slot 属性来将节点插入到不同的slot上。
1
2
3
4
5
6
7
8
<view>
<header>
<!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 -->
<view slot="before">这里是插入到组件slot name="before"中的内容</view>
<!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 -->
<view slot="after">这里是插入到组件slot name="after"中的内容</view>
</header>
</view>
导入模板
  • 定义模板template.qml及其样式文件template.qss
1
2
3
4
5
6
7
8
9
10
11
<!-- template.qml -->
<template name="navigation">
<view class="navigation_grounp">
<view class="navigation" hover-class="none" hover-stop-propagation="false">
<block wx:if="{{true}}">
<text class="nav_search">返回</text>
</block>
<text class="nav_title">nija</text>
</view>
</view>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 /* template.qss */
.navigation_grounp{
position: flexed;
top: 0;
width: 100%;
}
.navigation_grounp > .navigation{
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 0 15px;
background-color: green;
height: 44px;
color:red;
}
.navigation .nav_search{
width: 33.3%;
overflow: hidden;
}
.navigation .nav_title{
width: 33.3%;
text-align: center;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
  • 在index文件中导入
1
2
3
<!-- index.qml -->
<import src="./../template/template" />
<template is="navigation"></template>
1
2
/* index.qss,没导入该文件,其样式不会在index中显示 */
@import './../template/template.qss'

proto

类的prototype属性和__proto__属性

  • 每个对象都有__proto__属性,指向对应的构造函数的prototype属性。
  • 子类的__proto__属性表示构造函数的继承,总是指向父类。
  • 子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{}
class B extends A{}
B.__proto__ === A //true
B.prototype.__proto__ === A.prototype //true

//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype);
//B继承A的静态属性
Object.setPrototypeOf(B,A);

//Object.setPrototypeOf方法实现
Object.setPrototypeOf = function(obj,proto){
obj.__proto__ = proto;
return obj;
}

这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。

上面代码的A,只要是一个有prototype属性的函数,就能被B继承。由于函数都有prototype属性(除了Function.prototype函数),因此A可以是任意函数。

以下两种特殊情况
  • 子类继承Object类
1
2
3
4
class A extends Object{}
A.__proto__ === Object //true
A.prototype.__proto__ === Object.prototype //true
//A其实就是构造函数Object的复制,A的实例就是Object的实例
  • 不存在任何继承
1
2
3
class A{}
A.__proto__ === Function.prototype //true
A.prototype.__proto__ === Object.prototype //true

A作为一个基类,即不存在任何继承,就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象即object实例,所以A.prototype.__proto__指向构造函数Object的prototype属性。

实例的 proto 属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point { /* ... */ }
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}

toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

上面代码中,ColorPoint继承了Point,导致前者原型的原型是后者的原型。

因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。

1
2
3
4
5
p2.__proto__.__proto__.printName = function () {
console.log('Ha');
};

p1.printName() // "Ha"
原生构造函数的继承
  • 原生构造函数是指语言内置的构造函数,通常用来生成数据结构。一般不能继承,有以下九种:

    Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function MyArray() {
Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
var colors = new MyArray();
colors[0] = "red";
colors.length // 0

colors.length = 0;
colors[0] // "red"

之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。

ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。

ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。下面是一个继承Array的例子。

1
2
3
4
5
6
7
8
9
10
11
12
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined

项目开发流程

WEB前端项目开发流程

项目需求分析

这个环节是由设计师完成,设计师首先和客户进行交流,了解客户的需求,然后分析项目的可行性,如果项目可以被实现,设计师直接进行设计开发。

页面设计

这个环节主要是UI设计师参与,UI设计师根据产品需求分析文档,对产品的整体美术风格、交互设计、界面结构、操作流程等做出设计。负责项目中各种交互界面、图标、LOGO、按钮等相关元素的设计与制作。最后设计出一份设计稿并交由程序员进行下一步开发。程序员拿到设计稿交流完需求后公司方需要支付3成定金。

编码

这个部分由程序员来实现。(程序员分为WEB前端开发工程师和后台开发工程师。前端开发人员主要根据原型图用代码写出我们可以在网页上看的见的页面,途中一些问题也需要同设计师直接交流,后台就做一些我们看不见的管理系统以及功能的实现。)程序员根据UI设计师的设计,用编码来完成整个项目的各个功能。

部署

由程序员部署到服务器上。

维护

程序的维护是整个项目的最后一个阶段,但也是耗时最多,成本最高最高的的一个阶段。程序的维护是由程序员免费维护半年。

事件机制

事件机制之冒泡、传播、委托

DOM事件流(event flow)存在三个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段

事件捕获阶段(event capuring)

通俗理解就是,当鼠标点击或触发dom事件时,浏览器会从根节点由外往内进行事件传播,即点击了子元素,如果父元素通过事件捕获注册了对应的事件的话,会先触发父元素绑定的的事件。

事件冒泡(dubbed bubbling)

与事件捕获相反,事件冒泡是从目标元素由内往外进行事件传播,直到根节点。

无论是事件冒泡还是事件捕获,都有一个共同点就是事件传播,她就像一根引线,只有通过引线才能将绑在引线上的鞭炮(事件监听器)引爆,试想一下,如果引线不导火了,那鞭炮就只有一响了!

dom事件标准事件流的触发先后顺序是:先捕获后冒泡,即当触发dom事件时,会进行事件捕获,捕获到事件源后通过事件传播进行事件冒泡。不同浏览器对此有不同的实现,IE10及以下不支持捕获型事件,所以就少了一个时间捕获阶段,IE11,Chrome,Firefox,Safari等浏览器则同时存在。

事件绑定的方法
addEventlistener(event,listener,useCapture)

参数定义:event——(事件名称:如click,不带on)

listener——事件监听函数,

useCapture——是否采用事件捕获进行事件捕获,默认为false,即采用事件冒泡方式。

addEventListener在IE11,Chrome,Firefox,Safari等浏览器都得到支持。

attachEvent(event,listener)

参数定义:event—(事件名称,如onclick,带on),

listener—事件监听函数。

attachEvent主要用于IE浏览器,并且仅在IE10及以下才支持,IE11已经废了这个方法了

事件冒泡例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
#parent{width:200px;height:200px;background:yellow;margin:10px auto;border:1px solid black;}
#children{width:50px;height:50px;background:pink;margin:80px auto;}
</style>
<title>Document</title>
</head>
<body>
<div id="parent">
<div id="children"></div>
</div>
</body>
</html>
<script>
var children = document.getElementById('children');
var parent = document.getElementById('parent');
document.body.addEventListener('click',function(){console.log('body')},false);
parent.addEventListener('click',function(){console.log('parent')},false);
children.addEventListener('click',function(){console.log('children');
//event.stopProparation;
//可停止事件传播},false);
</script>

当点击子盒子时,打印结果依次为children——parent——body

事件触发的顺序是由内到外的,这就是事件冒泡,虽然只点击了子元素,但是他的父元素也会触发相应的事件,其实这也是合理的,因为父元素里面,点击了子元素不就相当于变相的点击了父元素。

若不想触发父元素可停止事件传播只需在子元素中添加event.stopProparation;即可。

事件捕获例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
body{background: green;}
#parent{width:200px;height:200px;background:yellow;margin:10px auto;border:1px solid black;}
#children{width:50px;height:50px;background:pink;margin:80px auto;}
</style>
<title>Document</title>
</head>
<body>
<div id="parent">
<div id="children"></div>
</div>
</body>
</html>
<script>
var children = document.getElementById('children');
var parent = document.getElementById('parent');
document.body.addEventListener('click',function(){console.log('body')},true);
parent.addEventListener('click',function(){console.log('parent')},true);
children.addEventListener('click',function(){console.log('children')},true);
</script>

点击children方块,打印body——parent——childre

点击parent方块,打印body——parent

点击body区域,打印body

事件通过事件捕获的方式注册了click事件,所以在事件捕获阶段就会触发,先是触发最外围注册了事件捕获的body,而后触发事件捕获的parent,最后触发事件源。这就是事件的时间流程。

MVVC模型架构

理解MVVC架构模式

MVC架构

Model(模型):数据层,负责存储数据。

View(视图):展现层,用户所看到的页面。

Controller(控制器):协调层,负责协调Model和View,根据用户在Model上做出的改变,同时把更改的信息返回到View上。

Controller可以直接访问Model,也可以直接控制View,但是Model与View不能互相通信,相当于Controller就是介于这两者之间的协调者。

img

MVC是过去常用的架构模式,随着App的发展,界面的多沿海,显然再使用MVC模式的会导致ViewController代码不断增多,更容易造成一些冗余的代码,如果作为迭代项目更不易维护。从而诞生了MVVC的架构模式。

MVVC

M -Model

V - View

VM - ViewModel

MVVM是将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双喜那个的,因此试图的数据的变化会同事修改数据源,而数据源数据的变化也会立即反应到View上。

MVVC优点

1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。

2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。

3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。

4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

|