pma934 / pma934.github.io Goto Github PK
View Code? Open in Web Editor NEWbuild by https://github.com/pma934/vuetify-blog
build by https://github.com/pma934/vuetify-blog
ES6
HTMLAllCollection对象(DOM组)
转化为数组Array.from(document.all)
、Array.from(str)
对String使用,效果相当于str.split('')
Array.prototype.slice.call()
是一样的,比[...xxx]
作用范围更大 list.flat(Infinity)
...变量名
),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。var aaa = function(x){
return new Promise((resolve,reject)=>{
resolve(x+1)
})
}
aaa(3).then(res=>{
console.log("hhh:"+res)
return res+1
}).then(res=>{
console.log("hhh:"+res)
return res+1
}).then(res=>{
console.log("hhh:"+res)
return res+1
}).then(res=>{
console.log("hhh:"+res)
return res+1
}).then(res=>{
console.log("hhh:"+res)
return res+1
})
//hhh:4
//hhh:5
//hhh:6
//hhh:7
//hhh:8
//Promise {<resolved>: 9}
// 除了使用new构造新的promise之外还可以
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包装成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})
async function test() {
return "1"
}
console.log(test()) // -> Promise {<resolved>: "1"}
防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
节流
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
function debounce(fn) {
var timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function throttle(fn) {
var timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
timeout = timeout === null ? setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
timeout = null
}, 500) : timeout;
};
}
基本类型
基本类型与引用类型的区别
undefined 和 null 区别
this指向
闭包
js异步响应原理
尾调用优化
原型链
js实现instanceof
//js实现instanceof
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L) // 当 O 显式原型 严格等于 L隐式原型 时,返回true
return true;
L = L.__proto__;
}
}
new的过程中发生了什么
js实现一个new
//js实现new
function new_(){
//1.创建一个空对象
let newObj = {}
//constructor是构造函数,args是其参数
let [constructor,...args] = [...arguments]
//2.把新对象的原型指向构造函数的prototype
newObj.__proto__ = constructor.prototype
//3.绑定 this 并执行构造函数
let res = constructor.apply(newObj,args)
//4.返回对象
return res instanceof Object ? res : newObj
}
为什么 0.1 + 0.2 != 0.3?如何解决这个问题?
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
控制保留位数<img>
标签有一个属性是src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求data-src
,因为可以直接用dataset取出),在需要的时候也就是图片进入可视区域的之前,将URL取出放到src中。模块化
基于比较和非比较的排序
组件化与模块化
v-for 的 key
进程线程是什么,进程间的通信有哪些方法
OSI七层模型
DNS的运行过程,DNS性能优化有哪些方法
https协议加密具体过程
http1.0 、http1.1和http2.0的区别
在地址栏里输入一个URL,到这个页面呈现出来,中间会发生什么?
[] == ![] //true
["1","2","3"].map(parseInt) //[1,NaN,NaN]
这是因为parseInt默认两个参数,第二个参数是进制<template>
<div class="upload-head">
<el-upload
class="upload-image"
action="/"
single-file
accept="image/jpeg, image/png, image/bmp"
:multiple="false"
:show-file-list="false"
:auto-upload="false"
:on-change="onChange"
>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
</el-upload>
<img title="图像预览处" :src="base64" alt="图像预览处" style="float:left" />
<el-dialog title="提示" :visible.sync="dialogVisible" :area="730" @close="handleClose">
<div
class="backImg"
:style="{backgroundImage:`${url}`,backgroundSize:backgroundSize,backgroundPosition:`${backPosX}px ${backPosY}px`}"
@mousedown="startDrag"
@mouseup="stopDrag"
>
<div class="clipMask"></div>
</div>
<div class="zoomSet">
<el-button icon="h-icon-zoom_in" class="rsbtn" v-repeat-click="zoomRatioAdd" />
<el-slider v-model="zoomRatio" vertical :max="100" height="100px"></el-slider>
<el-button icon="h-icon-zoom_out" class="rsbtn" v-repeat-click="zoomRatioDec" />
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="clipImg()">裁 剪</el-button>
<el-button @click="dialogVisible = false">关 闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "UploadHead",
data() {
return {
//界面原始属性
radius: 185, //截取圆的半径
marginX: 168, //截取圆离左右边界的间隔
marginY: 18, //截取圆离上下边界的间隔
//---------
dialogVisible: false, //
url: "",
img: new Image(),
backgroundSize: "",
imgHeight: 0,
imgWidth: 0,
backPosX: 150,
backPosY: 0,
clickX: 0,
clickY: 0,
zoomRatio: 0,
base64: "",
};
},
directives: {
RepeatClick: {
bind(el, binding, vnode) {
let interval = null;
let startTime;
const handler = vnode.context[binding.expression].bind(); //() => vnode.context[binding.expression].apply();
const clear = () => {
if (new Date() - startTime < 100) {
handler();
}
clearInterval(interval);
interval = null;
};
el.addEventListener("mousedown", (e) => {
if (e.button !== 0) return;
startTime = new Date();
document.addEventListener("mouseup", clear, { once: true });
clearInterval(interval);
interval = setInterval(handler, 100);
});
},
},
},
computed: {
cutBoxHeight() {
return 2 * (this.radius + this.marginY);
},
cutBoxWidth() {
return 2 * (this.radius + this.marginX);
},
limitX() {
return (
this.marginX +
2 * this.radius -
this.imgWidth * (this.zoomRatio / 100 + 1)
);
},
limitY() {
return (
this.marginY +
2 * this.radius -
this.imgHeight * (this.zoomRatio / 100 + 1)
);
},
},
watch: {
zoomRatio() {
this.zooming();
},
},
methods: {
//上传图片时触发
onChange(file, fileList) {
console.log(file, fileList);
this.url = `url(${file.url})`;
this.getImageSize(file.url);
fileList.shift();
},
//设置头像截取时,图像的初始大小及位置
getImageSize(url) {
this.img.src = url;
window.iii = this.img;
// 加载完成获取宽高
this.img.onload = () => {
this.dialogVisible = true;
let width = this.img.width;
let height = this.img.height;
if (width > height) {
width = (width * this.cutBoxHeight) / height;
height = this.cutBoxHeight;
this.backgroundSize = `auto ${this.cutBoxHeight}px`;
this.backPosX = this.marginX + this.radius - width / 2;
this.backPosY = 0;
} else {
height = (height * this.cutBoxHeight) / width;
width = this.cutBoxHeight;
this.backgroundSize = `${this.cutBoxHeight}px`;
this.backPosY = this.marginY + this.radius - height / 2;
this.backPosX = this.marginX - this.marginY;
}
this.imgHeight = height;
this.imgWidth = width;
};
},
//移动图像方法
startDrag(e) {
e.preventDefault();
this.clickX = e.pageX;
this.clickY = e.pageY;
document.addEventListener("mousemove", this.dragPic);
document.addEventListener("mouseup", this.stopDrag);
},
stopDrag() {
document.removeEventListener("mousemove", this.dragPic);
},
dragPic(e) {
let moveX = e.pageX - this.clickX;
let moveY = e.pageY - this.clickY;
this.clickX = e.pageX;
this.clickY = e.pageY;
this.handleMoveX(moveX);
this.handleMoveY(moveY);
},
handleMoveX(moveX) {
if (this.backPosX + moveX > this.marginX) {
return false;
}
if (this.backPosX + moveX < this.limitX) {
return false;
}
this.backPosX += moveX;
},
handleMoveY(moveY) {
if (this.backPosY + moveY > this.marginY) {
return false;
}
if (this.backPosY + moveY < this.limitY) {
return false;
}
this.backPosY += moveY;
},
//放缩图像方法相关
zoomRatioAdd() {
this.zoomRatio++;
},
zoomRatioDec() {
this.zoomRatio--;
},
zooming() {
let ratio = this.zoomRatio / 100 + 1;
let width = this.imgWidth * ratio;
let height = this.imgHeight * ratio;
this.backgroundSize = width > height ? `auto ${height}px` : `${width}px`;
if (this.backPosX < this.limitX) {
this.backPosX = this.limitX;
}
if (this.backPosY < this.limitY) {
this.backPosY = this.limitY;
}
},
//关闭截取窗口的回调
handleClose() {
console.log("原始尺寸:", this.img.width, this.img.height);
console.log("比例尺寸:", this.imgWidth, this.imgHeight);
console.log(
"放缩尺寸:",
this.imgWidth * (this.zoomRatio / 100 + 1),
this.imgHeight * (this.zoomRatio / 100 + 1)
);
this.zoomRatio = 0;
},
clipImg() {
let zoomWidth = this.imgWidth * (this.zoomRatio / 100 + 1);
let zoomHeight = this.imgHeight * (this.zoomRatio / 100 + 1);
let canvas = document.createElement("canvas"); //不append到html中,会视为变量,随GC回收
canvas.width = 2 * this.radius;
canvas.height = 2 * this.radius;
let naturalWidth = this.img.width;
let naturalHeight = this.img.height;
if (canvas.getContext) {
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, 2 * this.radius, 2 * this.radius);
ctx.beginPath();
ctx.arc(this.radius, this.radius, this.radius, 0, 2 * Math.PI, false);
ctx.clip();
ctx.drawImage(
this.img,
0,
0,
naturalWidth,
naturalHeight,
this.backPosX - 168,
this.backPosY - 18,
zoomWidth,
zoomHeight
);
}
this.base64 = canvas.toDataURL("image/png", 0.8);
console.log(this.base64);
},
},
};
</script>
<style lang="scss" scoped>
$radius: 185px; //截取圆的半径
$marginX: 168px; //截取圆离左右边界的间隔
$marginY: 18px; //截取圆离上下边界的间隔
.backImg {
height: 2 * ($radius + $marginY); //406px;
width: 2 * ($radius + $marginX); //706px;
cursor: pointer;
overflow: hidden;
background-repeat: no-repeat;
}
.clipMask {
position: relative;
top: $marginY;
left: $marginX;
width: 2 * $radius;
height: 2 * $radius;
border-radius: $radius;
box-shadow: 0px 0px 0px 500px rgba(0, 0, 0, 0.4);
border: 2px solid #ffffff80;
}
.zoomSet {
width: 40px;
height: 200px;
padding-left: 3px;
background-color: #ffffff0f;
position: absolute;
right: 30px;
bottom: 90px;
color: #ffffff;
font-weight: 600;
.rsbtn {
color: rgb(255, 255, 255);
margin: 10px 2px;
}
}
</style>
marked文档中有这段示例
// Create reference instance
const marked = require('marked');
// Set options
// `highlight` example uses `highlight.js`
marked.setOptions({
renderer: new marked.Renderer(),
highlight: function(code) {
return require('highlight.js').highlightAuto(code).value;
},
pedantic: false,
gfm: true,
tables: true,
breaks: false,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false
});
// Compile
console.log(marked(markdownString));
与highlight.js不同,pygmentize .js库使用异步高亮显示。 这个例子表明,对于您使用的highlighter(语法高亮解析器),marked是不可知的。
marked.setOptions({
highlight: function(code, lang, callback) {
require('pygmentize-bundled') ({ lang: lang, format: 'html' }, code, function (err, result) {
callback(err, result.toString());
});
}
});
console.log(marked(markdownString));
在这两个例子中,code都是一个String,表示要传递给highlighter的代码段。在本例中,lang是一个字符串,它通知荧光笔要为code使用什么编程语言,而callback是异步highlighter完成后将调用的函数。
看了下pygmentize的文档示例
var pygmentize = require('pygmentize-bundled')
pygmentize({ lang: 'js', format: 'html' }, 'var a = "b";', function (err, result) {
console.log(result.toString())
})
在vue中使用会报错
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in mounted hook: "TypeError: exec is not a function"
found in
---> <About> at src/views/About.vue
<VContent>
<VApp>
<App> at src/App.vue
<Root>
warn @ vue.runtime.esm.js?2b0e:619
logError @ vue.runtime.esm.js?2b0e:1884
globalHandleError @ vue.runtime.esm.js?2b0e:1879
handleError @ vue.runtime.esm.js?2b0e:1839
invokeWithErrorHandling @ vue.runtime.esm.js?2b0e:1862
callHook @ vue.runtime.esm.js?2b0e:4213
insert @ vue.runtime.esm.js?2b0e:3139
invokeInsertHook @ vue.runtime.esm.js?2b0e:6340
patch @ vue.runtime.esm.js?2b0e:6559
Vue._update @ vue.runtime.esm.js?2b0e:3942
updateComponent @ vue.runtime.esm.js?2b0e:4060
get @ vue.runtime.esm.js?2b0e:4473
run @ vue.runtime.esm.js?2b0e:4548
flushSchedulerQueue @ vue.runtime.esm.js?2b0e:4304
(anonymous) @ vue.runtime.esm.js?2b0e:1980
flushCallbacks @ vue.runtime.esm.js?2b0e:1906
Promise.then (async)
timerFunc @ vue.runtime.esm.js?2b0e:1933
nextTick @ vue.runtime.esm.js?2b0e:1990
queueWatcher @ vue.runtime.esm.js?2b0e:4396
update @ vue.runtime.esm.js?2b0e:4538
notify @ vue.runtime.esm.js?2b0e:730
reactiveSetter @ vue.runtime.esm.js?2b0e:1055
(anonymous) @ vue-router.esm.js?8c4f:2555
(anonymous) @ vue-router.esm.js?8c4f:2554
updateRoute @ vue-router.esm.js?8c4f:2013
(anonymous) @ vue-router.esm.js?8c4f:1891
(anonymous) @ vue-router.esm.js?8c4f:2000
step @ vue-router.esm.js?8c4f:1730
step @ vue-router.esm.js?8c4f:1737
runQueue @ vue-router.esm.js?8c4f:1741
(anonymous) @ vue-router.esm.js?8c4f:1995
step @ vue-router.esm.js?8c4f:1730
(anonymous) @ vue-router.esm.js?8c4f:1734
(anonymous) @ vue-router.esm.js?8c4f:1980
(anonymous) @ vue-router.esm.js?8c4f:1773
(anonymous) @ vue-router.esm.js?8c4f:1849
Promise.then (async)
(anonymous) @ vue-router.esm.js?8c4f:1796
(anonymous) @ vue-router.esm.js?8c4f:1817
(anonymous) @ vue-router.esm.js?8c4f:1817
flatMapComponents @ vue-router.esm.js?8c4f:1816
(anonymous) @ vue-router.esm.js?8c4f:1752
iterator @ vue-router.esm.js?8c4f:1959
step @ vue-router.esm.js?8c4f:1733
step @ vue-router.esm.js?8c4f:1737
runQueue @ vue-router.esm.js?8c4f:1741
confirmTransition @ vue-router.esm.js?8c4f:1988
transitionTo @ vue-router.esm.js?8c4f:1890
init @ vue-router.esm.js?8c4f:2546
beforeCreate @ vue-router.esm.js?8c4f:553
invokeWithErrorHandling @ vue.runtime.esm.js?2b0e:1854
callHook @ vue.runtime.esm.js?2b0e:4213
Vue._init @ vue.runtime.esm.js?2b0e:4998
Vue @ vue.runtime.esm.js?2b0e:5079
(anonymous) @ main.js?56d7:13
./src/main.js @ app.js:7783
__webpack_require__ @ app.js:772
fn @ app.js:130
0 @ app.js:7905
__webpack_require__ @ app.js:772
(anonymous) @ app.js:948
(anonymous) @ app.js:951
vue.runtime.esm.js?2b0e:1888 TypeError: exec is not a function
at pythonVersion (index.js?b376:127)
at spawnPygmentize (index.js?b376:105)
at pygmentize (index.js?b376:78)
at VueComponent.mounted (About.vue?c330:41)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at callHook (vue.runtime.esm.js?2b0e:4213)
at Object.insert (vue.runtime.esm.js?2b0e:3139)
at invokeInsertHook (vue.runtime.esm.js?2b0e:6340)
at VueComponent.patch [as __patch__] (vue.runtime.esm.js?2b0e:6559)
at VueComponent.Vue._update (vue.runtime.esm.js?2b0e:3942)
直接在js文件里面run也失败了,返回的result是undefined
c:\Users\Hp\Desktop\vuetifydemo\a.js:4
console.log(result.toString())
^
TypeError: Cannot read property 'toString' of undefined
使用highlight.js体验良好
github:https://github.com/highlightjs/highlight.js
使用说明: https://highlightjs.org/usage/
配置文档:https://highlightjs.readthedocs.io/en/latest/
安装:
npm install highlight.js --save
配合marked使用:
import marked from "marked";
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
marked.setOptions({
renderer: new marked.Renderer(),
highlight: function(code) {
return hljs.highlightAuto(code).value;
},
gfm: true,
tables: true,
breaks: true
});
当你在main.js里声明了Vue.prototype.a = 1
后,因为你的每一个vue组件都是一个Vue对象的实例,所以即使你没有在组件内部使用data(){return{……}}
声明a,你依然可以在组件中通过this.a来访问。
当然,你也可以在组件中添加一个变量a,这时你访问的就是你在组件中添加的a,而不再是之前在原型中添加的a了,当然你对组件的a继续修改即不会影响原型中的a和其他组建中的a,就类似于下面这段代码(Form是一个自定义对象类型,Vue也可以看作一个自定义对象类型,而每个.vue文件就是一个对象的实例)
function Form(){
}
Form.prototype.a= 0
var f1 = new Form()
var f2 = new Form()
console.log(f1) //Form {}
console.log(f1.a) //0
console.log(f2) //Form {}
console.log(f2.a) //0
f1.a++
console.log('f1.a++之后') //f1.a++即 f1.a = f1.a + 1,在f1中添加了a属性,其值为原型中a的值+1
console.log(f1) //Form {a: 1}
console.log(f1.a) //1
console.log(f2) //Form {}
console.log(f2.a) //0
当然,上面说的是针对于 a的值是 基本的数据类型(undefined,boolean,number,string,null)
如果a是引用类型的值情况就不一样了。就如同下面代码一样。
function Form(){
}
Form.prototype.a= {value:0}
var f1 = new Form()
var f2 = new Form()
console.log(f1) //Form {}
console.log(f1.a.value) //0
console.log(f2) //Form {}
console.log(f2.a.value) //0
f1.a.value++
console.log('f1.a++之后') //f1.a++之后
console.log(f1) //Form {}
console.log(f1.a.value) //1
console.log(f2) //Form {}
console.log(f2.a.value) //1
原型中a的值是一个Object类数据,在实例中使用f1.a.value++
并没有修改f1.a指向的指针
,实例中依然是访问的原型中的a,同时改变的也是原型中a.value的值
。而f2.a.value
访问的也是原型中a.value的值,因此f2.a.value的值变化了。
由此可以想到。
由此可以得到启发,如果我们在main.js中声明Vue.prototype.global={a:0}
那我们只要不在组件中重新添加global这个变量,就能在所有组件中使用this.global.a
这个值了,同时在一个组件中修改了this.global.a的值,其他所有组件中访问的this.global.a的值也会变化。
这样就相当于使用Vue.prototype实现了vue的全局变量
在main.js里通过
new Vue({
……
data(){
return{
a:1
}
},
……
})
声明,然后在所有子组件中都可以通过$root.a
访问到相同的变量。
# This is an <h1> tag
## This is an <h2> tag
### This is an <h3> tag
#### This is an <h4> tag
##### This is an <h5> tag
###### This is an <h6> tag
*这是斜体*
_这是斜体_
**这是黑体**
__这是黑体__
*斜体里加**黑体***
**黑体里加*斜体***
这是斜体
这是斜体
这是黑体
这是黑体
斜体里加黑体
黑体里加斜体
* Item 1
* Item 2
* Item 2a
* Item 2b
1. Item 1
1. Item 2
1. Item 3
1. Item 3a
1. Item 3b
- [ ] List item
![Yaktocat的图片](https://octodex.github.com/images/yaktocat.png)
http://github.com - automatic!
[GitHub](http://github.com)
http://github.com - automatic!
GitHub
As Kanye West said:
> We're living the future so
> the present is our past.
As Kanye West said:
We're living the future so
the present is our past.
I think you should use an
`<addr>` element here instead.
I think you should use an
<addr>
element here instead.
First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column
First Header | Second Header |
---|---|
Content from cell 1 | Content from cell 2 |
Content in the first column | Content in the second column |
~~this~~
this
😝🌟🐫✨🚶
:sparkles: :camel: :boom:
😝🌟🐫✨🚶
✨ 🐫 💥
<mark>高亮</mark>
高亮
<iframe height="300" style="width: 100%;" src="//codepen.io/pma934/embed/byaMOZ/?height=300&theme-id=dark&default-tab=css,result"></iframe>
<details>
<summary>题目</summary>
内容
</details>
<span id="XXXX">XXXX</span>
[跳转XXXX](#XXXX)
[回到顶部锚点](#顶部锚点)
之前不知道在哪看到了setTimeout(fn,0)和setTimeout(fn,1)效果是一样的
这句话,结果导致后来越记越歪,记成了setTimeout(fn,0)和setTimeout(fn)都会被当作setTimeout(fn,1)执行。
现在想想,其实应该就按字面意思理解,仅仅是setTimeout(fn,0)和setTimeout(fn,1)的执行效果是一样的,其原因如下:
setTimeout(function(){console.log(123)},0)
for(let i=1;i<10;i++){
i--
}
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); });
}
上面的打印结果大家都知道,就是10个10
但是如果假设setTimeout(fn,0)真的能到达0秒延时又会怎么样呢?
是不是会打印0到9呢?
因此即使setTimeout(fn,0)真的能到达0秒延时,输出依然是10个10
同理,下面代码输出的是9、8、7、6、5、4、3、2、1、0而不是死循环
for (var i = 0; i < 10; i++) {
setTimeout(function() { i--; console.log(i); }, 0);
}
为此,我们可以顺便看看浏览器的事件循环机制
安装 webpack 、webpack-cli
npm install webpack --save-dev
npm install webpack-cli --save-dev
直接npx webpack
打包工具 => 输出后的结果(js 模块)
默认配置文件名称 webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
}
}
npm install webpack-dev-server -D
//npm常用设置
"scripts": {
"dev": "webpack-dev-server"
},
module.exports ={
……
devServer:{
port:3000,
progress:true,
contentBase:'./dis',
open:true,
}
……
}
安装插件 html-webpack-plugin
npm install html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports ={
……
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
hash:true,
minify:{
removeAttributeQuotes:true, //去双引号
collapseWhitespace:true, //折叠空行
}
})
]
……
}
const path = require('path')
module.exports ={
entry:'./src/index.js',
output:{
filename:'bundle.[hash:8].js', //使用了hash,更改文件后build的新文件不会覆盖旧文件
path:path.resolve(__dirname,'dist')
}
}
不要在 html 里引入
ERROR in ./src/index.css 1:4 Module parse failed: Unexpected token (1:4) You may need an appropriate loader to handle this file type.
body{ | background-color: red | } @ ./src/index.js 5:0-22
安装 css-loader 和 style-loader
css-loader 接续@import
style-loader 插入html
希望单个 loader 功能单一
多个 loader 组合出多种功能
单个可以直接字符串引入,多个使用数组;其中单个也可以用对象,能穿参数
loader 有顺序,右向左执行 css-loader -> style-loader
从下向上执行
module: { //模块
rules: [ //规则
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
}
安装 less-loader 同时记得安装 less
{
test: /\.less$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'
}
},
'css-loader',
'less-loader'
]
},
抽离css
mini-css-extract-plugin
替换直接插入的style-loader使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
new MiniCssExtractPlugin({
filename:'main.css',
})
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
自动添加前缀
安装postcss-loader、autoprefixer
放在css-loader下面(先)
需要配置文件,否则
Error: No PostCSS Config found in: C:\Users\Hp\Desktop\webpack\src at config.load.then (C:\Users\Hp\Desktop\webpack\node_modules\postcss-load-config\src\index.js:55:15)
压缩css
安装 optimize-css-assets-webpack-plugin
该配置 似乎只有在mode为 production 时优化项才会触发
const OptimizeCss = require('optimize-css-assets-webpack-plugin')
optimization: { //优化项
minimizer: [
new OptimizeCss()
]
},
压缩js
配置optimization会导致默认js压缩失效,因此需要安装
uglifyjs-webpack-plugin
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
hash: true,
minify: {
removeAttributeQuotes: true, //去双引号
collapseWhitespace: true, //折叠空行
}
}),
new MiniCssExtractPlugin({
filename: 'main.css',
})
],
还需要babel,不然会报错(下面说)
ERROR in index.js from UglifyJs Unexpected token: keyword «const» [./src/index.js:1,0][index.js:91,0]
安装 babel-loader @babel/core @babel/preset-env
module: { //模块
rules: [ //规则
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{ //用babel-loader需要把es6->es5
presets:[
'@babel/preset-env' //语法转化
]
}
}
},
ES7 安装plugin-proposal-class-properties
class A{
a=1;
}
Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.
npm install @babel/plugin-proposal-class-properties -D
module: { //模块
rules: [ //规则
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{ //用babel-loader需要把es6->es5
presets:[
'@babel/preset-env' //语法转化
],
plugins:[
'@babel/plugin-proposal-class-properties'
]
}
}
},
装饰器 安装plugin-proposal-decorators
@log //装饰器
class A{
a=1;
}
ERROR in ./src/index.js Module build failed (from ./node_modules/babel-loader/lib/index.js): SyntaxError: C:\Users\Hp\Desktop\webpack_study\src\index.js: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (16:1)
npm install @babel/plugin-proposal-decorators -D
options:{ //用babel-loader需要把es6->es5
presets:[
'@babel/preset-env' //语法转化
],
plugins:[
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
一些高级函数还是会报错
Generator 函数的语法 浏览器会报错
function * gen(params){
yield 1;
}
Uncaught ReferenceError: regeneratorRuntime is not defined at eval (a.js:3) at Object../src/a.js (index.js?73d00fef3436265b63dd:383) at __webpack_require__ (index.js?73d00fef3436265b63dd:20) at eval (index.js:5) at Object../src/index.js (index.js?73d00fef3436265b63dd:405) at __webpack_require__ (index.js?73d00fef3436265b63dd:20) at eval (webpack:///multi_(:8080/webpack)-dev-server/client?:2:18) at Object.0 (index.js?73d00fef3436265b63dd:438) at __webpack_require__ (index.js?73d00fef3436265b63dd:20) at index.js?73d00fef3436265b63dd:84
使用插件plugin-transform-runtime
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
{
"plugins": ["@babel/plugin-transform-runtime"]
}
但是还是会报错
WARNING in (webpack)/buildin/global.js 13:53-60 "export 'default' (imported as '_typeof') was not found in '@babel/runtime/helpers/typeof'
需要设置js查找的范围
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{ //用babel-loader需要把es6->es5
presets:[
'@babel/preset-env' //语法转化
],
plugins:[
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"@babel/plugin-transform-runtime"
]
}
},
include:path.resolve(__dirname,'src'), //指定包括
exclude:/node_modules/ //指定排除
}
在a.js里会报错,es6语法不应该使用module.exports
class B{
}
function * gen(params){
yield 1;
}
console.log(gen().next())
module.exports = "123"
安装 @babel/polyfill
使用"foobar".includes("foo")
这种语法 ==不懂==
npm install --save @babel/polyfill
eslint
npm install eslint eslint-loader
{
test:/\.js$/,
use:{
loader:'eslint-loader',
enfore:'pre' //强制先执行,先给js检查错误
}
},
import $ from 'jquery';
console.log($)
ƒ ( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery…
import $ from 'jquery';
console.log(window.$)
undefined
使用expose-loader暴露全局变量
import $ from 'expose-loader?$!jquery'; //内联loader
console.log(window.$)
如果觉得上面写法恶心,可以在webpack.config.js里设置
module: { //模块
rules: [ //规则
{
test:require.resolve('jquery'),
use:'expose-loader?$!jquery'
},
……
]
}
const webpack = require('webpack')
……
module.exports = {
……
plugins: [
……
new webpack.ProvidePlugin({
jquery:'$'
})
],
js里
npm i file-loader -D
css里
css-loader 已经处理过了
html里
npm i html-withimg-loader -D
把图片变成base64
npm i url-loader -D
{
test: /\.(png|jpg|gif)$/,
use: {
loader:'url-loader',
options: {
limit: 1
}
}
},
img
{
test: /\.(png|jpg|gif)$/,
use: {
loader:'url-loader',
options: {
limit: 1,
outputPath:'img/' //路径
}
}
},
css
plugins: [
……
new MiniCssExtractPlugin({
filename: 'css/main.css',
}),
……
]
给输出都加上某个路径(为CDN服务)
output: {
filename: 'index.js', //'bundle.[hash:8].js',
path: path.resolve(__dirname, 'dist'),
publicPath:'http://xxx.xxx.xxx' //给输出的东西都加上某个路径
},
xxx/
|
+——src/
| |
| +——index.js
| |
| +——other.js
|
+——index.html
|
+——……
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
home: './src/index.js',
other: './src/other.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'home.html',
chunks: ['home'] //代码块,指定引入特定的
}),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'other.html',
chunks: ['other']
})
]
}
watch:true, //实时监控
watchOptions:{ //监控的选项
poll:1000, //每秒 问我 1000次
aggregateTimeout:500, //防抖
ignored:/node_modules/ //
},
// 1) leanWebpackPlugin 清除
// 2) copyWebpackPlugin 复制
// 3) bannerPlugin 内置 版权声明
1.与线上环境一致的接口地址,每次构建前端代码时不需要修改调用接口的代码
2.不同于使用mock直接拦截ajax请求,使用mock server能更好的模拟 POST、GET 请求(在控制台的Network选项页能看到真实的ajax请求信息)
3.mock 数据可以由工具生成不需要自己手动写,同时可以灵活的修改接口数据来适应开发
npm install koa-generator -g
koa2 xxx
npm install
生成的koa2项目目录如下
xxx/
|
+——bin/
| |
| +—— www 程序入口
|
+——public/ 静态资源
| |
| +—— image/
| +—— javascripts/
| +—— stylesheets/
|
+——routes/ 路由配置
| |
| +—— index.js
| +—— users.js
|
+——views/ 页面模板引擎
| |
| +—— error.pug
| +—— index.pug
| +——
|
+——app.js 使用koa的js
|
+——package.json
bin/www 是入口文件,打开端口监听(默认3000),启动koa服务
app.js 是使用koa的js,里面已经配置好了,解析http请求、使用静态资源、log埋点、使用路由等中间件。
npm install mockjs --save
const router = require('koa-router')()
const Mock = require('mockjs')
router.prefix('/mockapi') //路由路径
//ctx.params 路由传值
//ctx.query 参数传值
//ctx.request.body Post参数
//people
const mockPeople = Mock.mock({
'peoples|5000': [{
'id|+1': 1,
'guid': '@guid',
'name': '@cname',
'age': '@integer(20, 50)',
'birthday': '@date("MM-dd")',
'address': '@county(true)',
'email': '@email',
}]
});
router.get('/people', async (ctx, next) => {
ctx.body = ctx.query['id'] ? mockPeople['peoples'][ctx.query['id'] - 1] : mockPeople['peoples']
})
router.get('/people/:id', async (ctx, next) => {
ctx.body = mockPeople['peoples'][ctx.params['id'] - 1]
})
router.post('/people', async (ctx, next) => {
let postData = ctx.request.body
let id = postData.id ? postData.id : 1
ctx.body = mockPeople['peoples'][id - 1]
})
module.exports = router
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const index = require('./routes/index')
const users = require('./routes/users')
const mockapi = require('./routes/mockapi') //<--引用模块
// error handler
onerror(app)
// middlewares
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'pug'
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
app.use(mockapi.routes(), mockapi.allowedMethods()) //<--配置路由
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
完成上述步骤之后,你就成功搭建了一个mock server了。
使用npm start
启动程序后,可以使用postman测试一下效果
虽然到上面mock server的搭建就结束了,但是你还可以做一些事情。
比如:
我的mockApi:http://www.coolan.win:3333/mockapi
之前没认真看设计模式,感觉只是一些构造函数的定义,结果真被问到的时候发现,因为不太清楚所以不知道要做什么
就像前几天的面试,面试官问了我vue双向绑定的原理,然后我说了数据劫持和发布-订阅模式的结合。然后又说了Object.defineProperty()设置set、get实现的数据劫持,Event.addEventListener()设置的发布订阅,然后面试官就叫我写一下发布订阅是怎么实现的,我以为是要写个dom绑定事件,结果面试官强调是写这个addEventListener是怎么实现的。。。当时因为对设计模式不太清楚,以为是要定义一个类,然后添加一个类似update这种的方法,然后创建一个类的实例,当实例的值更新时,就会自动触发这个update方法什么的。。。然后就没写出来。。。
设计模式是可重用的用于解决软件设计中一般问题的方案。设计模式如此让人着迷,以至在任何编程语言中都有对其进行的探索。
其中一个原因是它可以让我们站在巨人的肩膀上,获得前人所有的经验,保证我们以优雅的方式组织我们的代码,满足我们解决问题所需要的条件。
设计模式同样也为我们描述问题提供了通用的词汇。这比我们通过代码来向别人传达语法和语义性的描述更为方便。
一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题 。模式的另一种解释就是一个我们如何解决问题的模板,那些可以在许多不同的情况里使用的模板。 设计模式有以下三点好处:
模式是行之有效的解决方法:他们提供固定的解决方法来解决在软件开发中出现的问题,这些都是久经考验的反应了开发者的经验和见解的使用模式来定义的技术。
可以很容易地重用:一个模式通常反映了一个可以适应自己需要的开箱即用的解决方案。这个特性让它们很健壮。
善于表达:当我们看到一个提供某种解决方案的模式时,一般有一组结构和词汇可以非常优雅地帮助表达相当大的解决方案。
创建型设计模式
结构设计模式
行为设计模式
SN | 描述 |
---|---|
Creational | 根据创建对象的概念分成下面几类。 |
Class | |
Factory Method(工厂方法) | 通过将数据和事件接口化来构建若干个子类。 |
Object | |
Abstract Factory(抽象工厂) | 建立若干族类的一个实例,这个实例不需要具体类的细节信息。(抽象类) |
Builder (建造者) | 将对象的构建方法和其表现形式分离开来,总是构建相同类型的对象。 |
Prototype(原型) | 一个完全初始化的实例,用于拷贝或者克隆。 |
Singleton(单例) | 一个类只有唯一的一个实例,这个实例在整个程序中有一个全局的访问点。 |
--- | --- |
Structural | 根据构建对象块的方法分成下面几类。 |
Class | |
Adapter(适配器) | 将不同类的接口进行匹配,调整,这样尽管内部接口不兼容但是不同的类还是可以协同工作的。 |
Bridge(桥接模式) | 将对象的接口从其实现中分离出来,这样对象的实现和接口可以独立的变化。 |
Composite(组合模式) | 通过将简单可组合的对象组合起来,构成一个完整的对象,这个对象的能力将会超过这些组成部分的能力的总和,即会有新的能力产生。 |
Decorator(装饰器) | 动态给对象增加一些可替换的处理流程。 |
Facada(外观模式) | 一个类隐藏了内部子系统的复杂度,只暴露出一些简单的接口。 |
Flyweight(享元模式) | 一个细粒度对象,用于将包含在其它地方的信息 在不同对象之间高效地共享。 |
Proxy(代理模式) | 一个充当占位符的对象用来代表一个真实的对象。 |
--- | --- |
Behavioral | 基于对象间作用方式来分类。 |
Class | |
Interpreter(解释器) | 将语言元素包含在一个应用中的一种方式,用于匹配目标语言的语法。 |
Template Method(模板方法) | 在一个方法中为某个算法建立一层外壳,将算法的具体步骤交付给子类去做。 |
Object | |
Chain of Responsibility(响应链) | 一种将请求在一串对象中传递的方式,寻找可以处理这个请求的对象。 |
Command(命令) | 封装命令请求为一个对象,从而使记录日志,队列缓存请求,未处理请求进行错误处理 这些功能称为可能。 |
Iterator(迭代器) | 在不需要知道集合内部工作原理的情况下,顺序访问一个集合里面的元素。 |
Mediator(中介者模式) | 在类之间定义简化的通信方式,用于避免类之间显式的持有彼此的引用。 |
Observer(观察者模式) | 用于将变化通知给多个类的方式,可以保证类之间的一致性。 |
State(状态) | 当对象状态改变时,改变对象的行为。 |
Strategy(策略) | 将算法封装到类中,将选择和实现分离开来。 |
Visitor(访问者) | 为类增加新的操作而不改变类本身。 |
观察者模式是这样一种设计模式。一个被称作被观察者的对象,维护一组被称为观察者的对象,这些对象依赖于被观察者,被观察者自动将自身的状态的任何变化通知给它们。
一个或者更多的观察者对一个被观察者的状态感兴趣,将这种兴趣通过附着自身的方式注册在被观察者身上。当被观察者发生变化,而这种变化也是观察者所关心的,就会产生一个通知,这个通知将会被送出去,最后将会调用每个观察者的更新方法。当观察者不在对被观察者的状态感兴趣的时候,它们只需要简单的将自身剥离即可。 ———— 《设计模式:可重用的面向对象软件的元素》
观察者模式确实很有用,但是在javascript时间里面,通常我们使用一种叫做发布/订阅模式的变体来实现观察者模式。这两种模式很相似,但是也有一些值得注意的不同。
一个简单的观察者模式js代码实现
//被观察者以及其增加,删除,通知在观察者列表中的观察者的能力进行建模
function Subject() {
this.observerList = [];
}
Subject.prototype.addObserver = function (observer) {
this.observerList.push(observer)
}
Subject.prototype.removeObserver = function (observer) {
let index = this.observerList.indexOf(observer)
this.observerList.splice(index, 1)
}
Subject.prototype.notify = function (context) {
for (let i in this.observerList) {
this.observerList[i].update(context);
}
}
//观察者的一个框架。这里的update 函数之后会被具体的行为覆盖。
function Observer() {
this.update = function () {
// ...
};
}
一个简单的发布/订阅模式代码实现
class Publisher {
constructor() {
this.subscribers = {}
}
on(topic, event) {
if (!this.subscribers[topic]) {
this.subscribers[topic] = []
}
if(typeof event !== 'function'){
event = ()=>{}
}
this.subscribers[topic].push(event)
}
remove(topic, event) {
let events = this.subscribers[topic]
if (events) {
let i = events.indexOf(event)
if (i !== -1) {
events.splice(i, 1)
}
}
}
emit(topic, ...args) {
let events = this.subscribers[topic]
if (events) {
for (let key in events) {
events[key](...args)
}
}
}
}
单例模式的定义:单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
一个简单的单例模式实现
var Singleton = (function () {
function myObject(name, age) {
this.name = name
this.age = age
}
myObject.prototype.getName = function () {
console.log(this.name)
}
var instance;
return function (...args) {
if (!instance) {
instance = new myObject(...args)
}
return instance
}
})()
var a = Singleton('小明', 11)
var b = Singleton('小刚', 12)
a.getName() //小明
b.getName() //小明
console.log(a===b) //true
工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。有点类似与函数柯里化的感觉。
一个简单的工厂模式实现
//可以生产各种各样的齿轮
class GearFactory {
constructor(radius, thickness, material) {
this.radius = radius
this.thickness = thickness
this.material = material
}
}
//生产半径5cm、厚度2mm的铁齿轮的接口
function createGear(){
return new GearFactory('5cm','Fe','2mm')
}
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的 相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系,避免了对象之间相互的引用关系导致的逻辑混乱的问题。
让人联想到了星型拓扑结构
中介者模式聊天室示例
class User {
constructor(name) {
this.name = name
this.log = []
this.room = null
}
showLog() {
this.log.forEach(x => console.log(x))
}
into(chatRoom) {
this.room = chatRoom
this.room.into(this)
}
send(msg, user) {
if (this.room) {
this.room.send(this, msg, user)
this.log.push(`you send ${user&&user.name||'all'}:${msg}`)
}
}
receive(msg, from) {
this.log.push(`${from.name} to you:${msg}`)
}
}
class ChatRoom {
constructor() {
this.user = []
this.log = []
}
into(user) {
this.user.push(user)
}
send(from, msg, to) {
if (to) {
this.log.push(`${from.name} to ${to.name}:${msg}`)
if (this.user.indexOf(to) !== -1) {
to.receive(msg, from)
}
} else {
this.log.push(`${from.name} to all:${msg}`)
this.user.forEach(user => {
if (user !== from) {
user.receive(msg, from)
}
})
}
}
showLog() {
this.log.forEach(x => console.log(x))
}
}
var room = new ChatRoom()
var a = new User('A')
var b = new User('B')
var c = new User('C')
a.into(room)
a.send('有人吗?')
b.into(room)
c.into(room)
b.send('有人吗?')
c.send('有人吗?')
a.send('有', b)
c.send('有')
console.log('--room-')
room.showLog()
console.log('---a---')
a.showLog()
console.log('---b---')
b.showLog()
console.log('---c---')
c.showLog()
适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。
这个例子可能没表达好,主要作用就是包装接口对象,在不改变原对象的情况,将对象包装一层,来保证其能适应不同的接口
class Person {
constructor(name, brithday) {
this.name = name
this.brithday = brithday
}
}
//Person不能直接使用
function getAge(brithYear, brithMonth, brithDay) {
let now = new Date()
let nowYear = now.getFullYear(),
nowMonth = now.getMonth(),
nowDay = now.getDay()
let age = nowYear - brithYear
if (nowMonth < brithMonth) {
age--
} else if (nowMonth === brithMonth) {
if (nowDay < brithDay) {
age--
}
}
return age
}
class AgePerson {
constructor(name, brithday) {
this.person = new Person(name, brithday)
this.brithday = brithday.split('/').map(d => +d)
}
age() {
console.log(getAge(...this.brithday))
}
}
var ming = new AgePerson('小明', '1999/9/9')
ming.age()
装饰器模式并不去深入依赖于对象是如何创建的,而是专注于扩展它们的功能这一问题上。不同于只依赖于原型继承,我们在一个简单的基础对象上面逐步添加能够提供附加功能的装饰对象。它的想法是,不同于子类划分,我们向一个基础对象添加(装饰)属性或者方法,因此它会是更加轻巧的。
AOP(Aspect-Oriented Programming)装饰函数
// 前置代码
Function.prototype.before = function (fn) {
const self = this
return function () {
fn.apply(this, arguments)
return self.apply(this, arguments)
}
}
// 后置代码
Function.prototype.after = function (fn) {
const self = this
return function () {
let res = self.apply(this, arguments)
fn.apply(this, arguments)
return res
}
}
const wear1 = function () {
console.log('穿上第一件衣服')
}
const wear2 = function () {
console.log('穿上第二件衣服')
}
const wear3 = function () {
console.log('穿上第三件衣服')
}
const wear = wear2.before(wear1).after(wear3)
wear()
// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服
外观模式提供了一个接口,隐藏了内部的逻辑,更加方便外部调用。
比如jquery的$ ,不论何时我们使用jQuery的$(el).css或者$(el).animate()方法,我们实际上都是在使用一个门面——更加简单的公共接口让我们避免为了使得行为工作起来而不得不去手动调用jQuery核心的内置方法。
比如红宝书中定义的createXHR就是一个使用外观模式的示例
function createXHR() {
if (typeof XMLHttpRequest != "undefined") {
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined") {
if (typeof arguments.callee.activeXString != "string") {
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i, len;
for (i = 0, len = versions.length; i < len; i++) {
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
在我们需要在一个对象后多次进行访问控制访问和上下文,代理模式是非常有用处的。
当实例化一个对象开销很大的时候,它可以帮助我们控制成本,提供更高级的方式去关联和修改对象,就是在上下文中运行一个特别的方法。
JS中的事件委托(事件代理)就是使用了代理模式。
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
资料参考:JavaScript 设计模式
自己实现一个promise是前端常见的面试题之一(虽然我还没遇到过),同时也是深入理解promise的一种途径,通过自己实现,来更加深刻的理解其中的原理
要自己实现promise,首先得了解Promises/A+条例到底有哪些内容~
同步版先不考虑其他的,直接实现能按照 promise --> then 的顺序执行
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
function Promise(fn){
let that = this
this.status = PENDING
this.value = undefined
this.reason = undefined
let resolve = function(value){
that.value = value
that.status = FULFILLED
}
let reject = function(reason){
that.reason = reason
that.status = REJECTED
}
try{
fn(resolve,reject)
}catch(e){
reject(e)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
if(this.status === FULFILLED){
onFulfilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.reason)
}
}
接下来在同步版的基础上再解决两个问题
new Promise((resolve,reject)=>{
setTimeout(()=>{resolve(1)})
}).then(x=>console.log(x))
因此如果执行到then时,需要如下操作:
function Promise(fn){
let that = this
this.status = PENDING
this.value = undefined
this.reason = undefined
this.resolvedCb = []
this.rejectedCb = []
let resolve = function(value){
that.value = value
that.status = FULFILLED
that.resolvedCb.forEach(cb=>cb(that.value))
}
let reject = function(reason){
that.reason = reason
that.status = REJECTED
that.rejectedCb.forEach(cb=>cb(that.reason))
}
try{
fn(resolve,reject)
}catch(e){
reject(e)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
onRejected = onRejected instanceof Function?onRejected:()=>{}
if(this.status === FULFILLED){
onFulfilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.reason)
}
if(this.status === PENDING){
this.resolvedCb.push(onFulfilled)
this.rejectedCb.push(onRejected)
}
}
其中的关键代码如下:
if(this.status === PENDING){
this.resolvedCb.push(onFulfilled)
this.rejectedCb.push(onRejected)
}
that.resolvedCb.forEach(cb=>cb(that.value))
that.rejectedCb.forEach(cb=>cb(that.reason))
onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
onRejected = onRejected instanceof Function?onRejected:()=>{}
根据Promises/A+协议,then也应该返回一个promise,同时这也是then链式调用的条件
Promise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
onRejected = onRejected instanceof Function?onRejected:()=>{}
if(this.status === FULFILLED){
onFulfilled(this.value)
return new Promise(()=>{})
}
if(this.status === REJECTED){
onRejected(this.reason)
return new Promise(()=>{})
}
if(this.status === PENDING){
this.resolvedCb.push(onFulfilled)
this.rejectedCb.push(onRejected)
return new Promise(()=>{})
}
}
然后FULFILLED与REJECTED状态下,then返回的内容会直接成为下一个then的回调函数的输入
首先要能够捕获throw,然后根据结果类型判断使用resolve还是reject
if (this.status === FULFILLED) {
return new Promise((resolve, reject) => {
try {
let res = onFulfilled(this.value)
resolve(res)
} catch (e) {
reject(e)
}
})
}
这样就实现了同步版的then链式调用
function Promise(fn) {
……
}
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
onRejected = onRejected instanceof Function ? onRejected : () => {}
if (this.status === FULFILLED) {
return new Promise((resolve, reject) => {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECTED) {
return new Promise((resolve, reject) => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
this.resolvedCb.push(onFulfilled)
this.rejectedCb.push(onRejected)
return new Promise(() => {
})
}
}
接着考虑异步的情况下,异步的情况下,执行到then时状态还是PENDING,之后的then也是在PENDING状态下返回的promise的基础上调用的
考虑异步,没有链式调用时只需要把onFulfilled、onRejected放入回调数组中,链式调用的话,应该把FULFILLED、REJECTED状态的promise中的回调函数放入回调数组中(这里逻辑感觉挺复杂的,主要是保证执行resolvedCb或rejectedCb中内容执行时,有和FULFILLED或REJECTED中promise的回调执行相同的效果)
这里注意一点,onFulfilled和onRejected的输入是新promise的value或者reason属性,因此直接用this,但是整个回调是放在上一个promise的数组中的,因此用that标识原来的this
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
onRejected = onRejected instanceof Function ? onRejected : () => {}
//let that = this
……
if (this.status === PENDING) {
return new Promise((resolve, reject) => {
this.resolvedCb.push(() => {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
})
this.rejectedCb.push(() => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
})
}
}
这一步的完整程序如下
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
function Promise(fn) {
let that = this
this.status = PENDING
this.value = undefined
this.reason = undefined
this.resolvedCb = []
this.rejectedCb = []
let resolve = function (value) {
that.value = value
that.status = FULFILLED
that.resolvedCb.forEach(cb => cb(that.value))
}
let reject = function (reason) {
that.reason = reason
that.status = REJECTED
that.rejectedCb.forEach(cb => cb(that.reason))
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
onRejected = onRejected instanceof Function ? onRejected : () => {}
let that = this
if (this.status === FULFILLED) {
return new Promise((resolve, reject) => {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECTED) {
return new Promise((resolve, reject) => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
return new Promise((resolve, reject) => {
that.resolvedCb.push(() => {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
})
that.rejectedCb.push(() => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
})
}
}
这个版本基本上能和标准的promise一致了,但是还有一些细节问题,比如下面代码的执行结果
new Promise((resolve, reject) => {
resolve(new Promise((rs, rj) => {rs(9)}))
}).then(res => {
console.log(`res:${res}`)
},err => {
console.log(`err:${err}`)
})
//res:9
new Promise((resolve, reject) => {
resolve(new Promise((rs, rj) => {rj(9)}))
}).then(res => {
console.log(`res:${res}`)
},err => {
console.log(`err:${err}`)
})
//err:9
因此下一步就是解决resolve的内容是promise的情况
ps:下面代码效果是一致的,即只有resolve会解析内部promise的内容
new Promise((resolve, reject) => {
reject(new Promise((rs, rj) => {rs(9)}))
}).then(res => {
console.log(`res:${res}`)
},err => {
console.log(`err:${err}`)
})
//err:[object Promise]
new Promise((resolve, reject) => {
reject(new Promise((rs, rj) => {rj(9)}))
}).then(res => {
console.log(`res:${res}`)
},err => {
console.log(`err:${err}`)
})
//err:[object Promise]
同理then中return返回的promise也会解析,throw返回的promise不会解析
new Promise((resolve, reject) => {
resolve(1)
}).then(res => {
return new Promise(resolve=>resolve(res))
}).then(res => {
console.log(`res:${res}`)
},err => {
console.log(`err:${err}`)
})
//res:1
new Promise((resolve, reject) => {
resolve(1)
}).then(res => {
throw new Promise(resolve=>resolve(res))
}).then(res => {
console.log(`res:${res}`)
},err => {
console.log(`err:${err}`)
})
// err:[object Promise]
只有resolve和return中的promise会被解析,即只用this.value会被解析,而this.reason则不会
因此只需要修改if (this.status === FULFILLED) {}
和that.resolvedCb.push(() => {})
中的内容
先考虑同步状态,执行到this.status === FULFILLED
时,如果this.value是一个promise的话,相当于之后then的链式调用都是接到这个promise后面的,因此:
if (this.status === FULFILLED) {
return new Promise((resolve, reject) => {
if (this.value instanceof Promise) {
this.value.then(onFulfilled, onRejected)
} else {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
}
})
}
再考虑异步的,直接和上面一步同理
if (this.status === PENDING) {
return new Promise((resolve, reject) => {
that.resolvedCb.push(() => {
if (this.value instanceof Promise) {
this.value.then(onFulfilled, onRejected)
} else {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
}
})
that.rejectedCb.push(() => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
})
}
完整版如下:
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
function Promise(fn) {
let that = this
this.status = PENDING
this.value = undefined
this.reason = undefined
this.resolvedCb = []
this.rejectedCb = []
let resolve = function (value) {
that.value = value
that.status = FULFILLED
that.resolvedCb.forEach(cb => cb(that.value))
}
let reject = function (reason) {
that.reason = reason
that.status = REJECTED
that.rejectedCb.forEach(cb => cb(that.reason))
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
onRejected = onRejected instanceof Function ? onRejected : () => {}
let that = this
if (this.status === FULFILLED) {
return new Promise((resolve, reject) => {
if (this.value instanceof Promise) {
this.value.then(onFulfilled, onRejected)
} else {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
}
})
}
if (this.status === REJECTED) {
return new Promise((resolve, reject) => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
return new Promise((resolve, reject) => {
that.resolvedCb.push(() => {
if (this.value instanceof Promise) {
this.value.then(onFulfilled, onRejected)
} else {
try {
resolve(onFulfilled(this.value))
} catch (e) {
reject(e)
}
}
})
that.rejectedCb.push(() => {
try {
resolve(onRejected(this.reason))
} catch (e) {
reject(e)
}
})
})
}
}
Promise.resolve = function(value){
return new Promise(resolve=>{
resolve(value)
})
}
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason)
})
}
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
p的状态由p1、p2、p3决定,分成两种情况。
Promise.all = function (promises) {
let resolveList = []
return new Promise((resolve, reject) => {
if(promises.length === 0){ //promises为空数组的情况下,会返回resolve([])
resolve(resolveList)
}
promises.forEach((p,i) => {
Promise.resolve(p).then(re => {
resolveList[i] = re
if (promises.length === resolveList.length) {
//因为promise异步的原因,还是得放里面
resolve(resolveList)
}
}, rj => {
reject(rj)
})
})
})
}
var a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('3')
},300)
})
var b = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2')
},200)
})
var c = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('1')
},100)
})
var d = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('4')
},400)
})
Promise.all([a, b, c, d]).then(res => console.log(res), res => console.log(res))
真正的promise.all返回是[3,2,1,4],我的返回是[1,2,3,4]
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race = function (promises) {
let flag = true
return new Promise((resolve, reject) => {
promises.forEach(p => {
Promise.resolve(p).then(re => {
if (flag) {
flag = false
resolve(re);
}
}, rj => {
if (flag) {
flag = false
reject(rj);
}
})
})
})
}
function Promise(fn) {
let that = this
this.resolvedCb = []
this.rejectedCb = []
this.state = 'pending'
let resolve = function (value) {
if (value instanceof Promise) { //解决resolve包裹promise的问题
value.then(resolve, reject)
} else {
queueMicrotask(() => {
if (that.state === 'pending') {
that.state = 'resolved'
that.resolvedCb.forEach(cb => cb(value))
}
})
}
}
let reject = function (reason) {
queueMicrotask(() => {
if (that.state === 'pending') {
that.state = 'rejected'
that.rejectedCb.forEach(cb => cb(reason))
}
})
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
//解决透传的问题
onFulfilled = onFulfilled instanceof Function ? onFulfilled : r => r
onRejected = onRejected instanceof Function ? onRejected : r => {
throw r
}
return new Promise((resolve, reject) => {
this.resolvedCb.push(function (value) {
try {
resolve(onFulfilled(value))
} catch (e) {
reject(e)
}
})
this.rejectedCb.push(function (reason) {
try {
resolve(onRejected(reason))
} catch (e) {
reject(e)
}
})
})
}
事件循环执行到微任务时,会把微任务放入微任务队列,待到执行栈代码执行完毕之后才会执行微软栈中的内容;因此使用了html的微任务apiqueueMicrotask
将resolve和reject的回调函数放入微任务队列。
之前代码then中的FULFILLED和REJECTED状态部分是同步执行的现在不需要了
之前漏考虑了new Promise((resolve)=>{resolve(1)}).then().then((res) => console.log(res))
这种透传的问题,现在修正了
之前是在then中解决resolve包裹promise的问题,现在同步执行到then时是得不到value的内容的,因此将处理这个问题的代码放到了resolve函数中
参考资料
参见ES国际标准
function abc(){console.log('abc')}
var binded = abc.bind(null)
binded() //abc
console.log(binded.prototype) //undefined
var abc = ()=>{console.log('abc')}
abc() //abc
console.log(abc.prototype) //undefined
首先,我们需要牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性
__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)
constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数
prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是用这个构造函数创建的实例的原型对象
每个对象都有构造函数是指每个对象都可以找到其对应的constructor,
这个constructor可能是对象自己本身显式定义的或者通过__proto__在原型链中找到的
而单从constructor这个属性来讲,只有prototype对象才有。
函数创建的对象.__proto__ === 该函数.prototype
,该函数.prototype.constructor===该函数本身
__proto__
和constructor
属性是对象所独有的;② prototype
属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__
和constructor
属性。__proto__
属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__
属性所指向的那个对象(父对象)里找,一直找,直到__proto__
属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__
属性将对象连接起来的这条链路即我们所谓的原型链。prototype
属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype
。constructor
属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。https://blog.csdn.net/cc18868876837/article/details/81211729
function abc(){console.log('abc')}
abc() // abc
console.log(abc.prototype) //abc {}
var binded = abc.bind(null)
console.log(typeof binded) //function
console.log(binded instanceof Function) //true
console.log(Object.prototype.toString.call(binded)) //[object Function]
console.log(binded.prototype) //undefined
binded() //abc
之前无意中看到 并不是所有函数都有 prototype 属性
这句话,但是想了半天就是想不出到底哪个函数没有 prototype 属性
然后问了一下同学,同学告诉我反例是Function.prototype.bind()
Function.prototype.bind().prototype //undefined
bind
一般的用法是fn.bind(obj)
,然后返回的是绑定obj内容上下文(this指向obj)的fn。如果obj为空的话,则this指向window,如下:
var age = 50
var child = {age:1}
function getAge(){
console.log(this.age)
}
var childGetAge = getAge.bind(child)
var _getAge = getAge.bind()
getAge() //50
childGetAge() //1
_getAge() //50
因此Function.prototype.bind()
其实就是Function.prototype
于是我现在有了一个疑问Function.prototype到底是不是函数呢?
Function.prototype instanceof Function //false
typeof Object.prototype //object
Object.prototype.toString.call(Function.prototype) //[object Function]
Function.prototype.bind.call(Function.prototype) //ƒ () { [native code] }
不得不说红宝书第六章真的很重要,也真的很难QAQ
function Pet(){
this.master = "zxz"
}
Pet.prototype.about = function(){
console.log(`这只宠物的主人是${this.master}`)
}
function Cat(color){
this.color = color
}
Cat.prototype = new Pet()
Cat.prototype.constructor = Cat
var coco = new Cat()
coco.about() //这只宠物的主人是zxz
function Pet(name,age){
this.name = name
this.age = age
}
function Cat(name,age,color){
this.color = color
Pet.call(this,name,age)
}
var coco = new Cat("coco",3,"白")
console.log(coco) //Cat { color: '白', name: 'coco', age: 3 }
function Pet(name,age){
this.name = name
this.age = age
}
Pet.prototype.about = function(){
console.log(`这只宠物叫${this.name},今年${this.age}岁了`)
}
function Cat(name,age,color){
this.color = color
Pet.call(this,name,age) //通过call或者apply定义Pet中以及定义的属性
}
Cat.prototype = new Pet() //继承原型,coco.__proto__ => Cat.prototype ; coco.__proto__.__proto__ => new Pet().__proto__ => Pet.prototype
Cat.prototype.constructor = Cat // 保持构造函数的一致性,不然构造函数指向Pet的
Cat.prototype.about = function(){ //重写对象方法
console.log(`这只${this.color}色猫猫叫${this.name},今年${this.age}岁了`)
}
var wowo = new Pet("wowo",4)
wowo.about()//这只宠物叫wowo,今年4岁了
var coco = new Cat("coco",3,"白")
coco.about()//这只白色猫猫叫coco,今年3岁了
console.log(coco.__proto__.constructor) //[Function: Cat]
console.log(coco instanceof Cat) //true
console.log(coco instanceof Pet) //true
缺点:原型上有一些多余的属性
console.log(coco)
function object(o){
function F(){}
F.prototype = o
return new F();
}
ES5使用object.create()规范了原型式继承
(method) ObjectConstructor.create(o: object): any (+1 overload)
有重载,第二个参数格式类似Object.definedProperties()
方法
function createAnother(original){
var clone = object(original); // object是上面的原型式继承
clone.sayHi = function(){
console.log('hi')
}
return clone
}
1.通过call或者apply,继承父类构造函数中的属性
2.使用Object.create(Pet.prototype)替换原型,子类的原型对象设置为父类的一个原型式继承
3.保持构造函数的一致性
好像就是把组合继承中的Cat.prototype = new Pet()
换成了Cat.prototype = Object.create(Pet.prototype)
避免了二次调用new SuperType()
function Pet(name, age) {
this.name = name
this.age = age
}
Pet.prototype.about = function () {
console.log(`这只宠物叫${this.name},今年${this.age}岁了`)
}
function Cat(name, age, color) {
this.color = color
Pet.call(this, name, age) //通过call或者apply定义Pet中以及定义的属性
}
/***** 继承步骤上组合继承不同的地方 ***********************/
Cat.prototype = Object.create(Pet.prototype)
/***** **************************************************/
Cat.prototype.constructor = Cat // 保持构造函数的一致性,不然构造函数指向Pet的
Cat.prototype.about = function () { //重写对象方法
console.log(`这只${this.color}色猫猫叫${this.name},今年${this.age}岁了`)
}
var coco = new Cat("coco", 3, "白")
/***** 这部分得到的结果都和组合继承相同 *******************/
var wowo = new Pet("wowo", 4)
wowo.about() //这只宠物叫wowo,今年4岁了
var coco = new Cat("coco", 3, "白")
coco.about() //这只白色猫猫叫coco,今年3岁了
console.log(coco.__proto__.constructor) //[Function: Cat]
console.log(coco instanceof Cat) //true
console.log(coco instanceof Pet) //true
/***** **************************************************/
console.log(coco)
class
实现继承的核心在于使用 extends
表明继承自哪个父类,并且在子类构造函数中必须调用 super
,因为这段代码可以看成 Pet.call(this, name, age)
。
class Pet {
constructor(name, age) {
this.name = name
this.age = age
}
about() {
console.log(`这只宠物叫${this.name},今年${this.age}岁了`)
}
}
class Cat extends Pet {
constructor(name, age, color) {
super(name, age)
this.color = color
}
about() { //重写对象方法
console.log(`这只${this.color}色猫猫叫${this.name},今年${this.age}岁了`)
}
}
/***** 这部分得到的结果都和组合继承相同 *******************/
var wowo = new Pet("wowo", 4)
wowo.about() //这只宠物叫wowo,今年4岁了
var coco = new Cat("coco", 3, "白")
coco.about() //这只白色猫猫叫coco,今年3岁了
console.log(coco.__proto__.constructor === Cat) //[Function: Cat]
console.log(coco instanceof Cat) //true
console.log(coco instanceof Pet) //true
/***** **************************************************/
console.log(coco)
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
Object.keys(Pet.prototype) //[]
有道云
面向对象的程序设计
面向对象 Object-Oriented OO
标志:有类的概念
数据类型
[[Configurable]]: 表示能否通过delete删除属性从而重新定义属性,能够修改属性的特性,或者能否把属性修改为访问器属性
[[Enumerable]]: 表示能否通过for-in循环返回属性
[[Writable]]: 表示能否修改属性的值
[[Value]]: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值undefined
要修改属性默认的特性,必须使用ECMAScript 5的Object.defineProperty()方法。这个方法接受三个参数:属性所在对象,属性名和一个描述符对象。其中描述符对象的属性值必须是:configurable、enumerable、writable和value。设置其中一个或多个。
var person = {};
Object.defineProperty(person, 'name', {
writable: false,
value: 'Yeaseon'
});
Object.defineProperty()方法不能对configurable: false的对象进行修改。
即,一旦把对象设置成不可配置的之后就不能再改回可配置的了
调用Object.defineProperty()方法创建一个新属性时,如果不指定,configurable、enumerable 和 writable 特性的默认值都是false,即不可删除,不可遍历,不可修改
创建对象
工程模式:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
构造器模式:
构造器函数应该始终以一个大写字母开头,而非构造器函数始终以小写字母开头
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
原型模式:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
原型模式有一个致命问题就是 当属性是引用类型时
修改任何一个实例的该属性都会改变原型的属性
Person.prototype //原型函数获取原型
Object.getPrototypeOf(person1) //实例对象获取原型
结合使用构造函数和原型模式
用构造函数模式定义实例属性,用原型模式定义方法和共享属性。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
动态原型模式:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job =job;
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
注意:使用动态原型模式时,不能使用对象原型字面量重写原型
不然,如下会报错
function Person(){
}
var friend = new Person();
Person.prototype = { //重写
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
寄生构造函数模式:
稳妥构造函数模式:
继承
继承是通过原型链实现的,使用最多的继承模式是组合继承,使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
原型链
function SuperType () {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType () {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue()); // true
出于种种原因,项目组需要构建新的移动端统一框架,用以作为之后的H5应用的开发基座
之前的移动端框架存在以下几点问题。
ios会在浏览器上注入window.webkit.messageHandlers.sadp.postMessage的方法,该方法用于调用sadp模块下的方法,调用方式则是提供cmd传递方法名,再传递一个sequence,和入参data。代码如下
//ios
window.webkit.messageHandlers.sadp.postMessage({
cmd: "activeDevice",
sequence: sequence,//生成一个uuid
data: data
});
安卓则会直接注入window.模块名.方法名的方法供js调用,不过入参需要转换为JSON字符串,代码如下
//安卓
const jsonStr = JSON.stringify({
sequence:sequence,//生成一个uuid
data:data
});
window.sadp.activeDevice(jsonStr);
在调用移动端注入的方法后,移动端会接收到参数,然后通过调用window.activeDeviceCallBack({data,sequence})来返回结果
然后之前为了统一两端调用,在js上封装的方法大概如下
const request = (模块名,方法名,keepAlive=false) => {
const cbList = {};
return function(data,cb) {
if(!window[方法名+'CallBack']) {
window[方法名+'CallBack'] = function(obj) {
cbList[obj.sequence](obj.data)
if(!keepAlive){
delete cbList[obj.sequence];
}
if(Object.keys(cbList).length === 0){
delete window[方法名+'CallBack'];
}
}
}
const sequence = getUUID();
cbList[sequence] = cb;
console.log(模块名,方法名,sequence)
if(安卓){
const jsonStr = JSON.stringify({
sequence:sequence,
data:data
});
window[模块名][方法名](jsonStr);
}else if(IOS){
window.webkit.messageHandlers.模块名.postMessage({
cmd: "方法名",
sequence: sequence,
data: data
});
}
}
}
api封装
//activeDevice,单次回调
export const activeDevice = request('sadp', 'activeDevice', false);
//getDeviceList, 长连接
export const getDeviceList = request('sadp', 'getDeviceList', true);
```
方法调用
```xxx.vue
import {getDeviceList} from './api.js';
method:{
showDevice() {
//调用方式为,方法(参数,回调函数)
getDeviceList({type:1},(deviceList)=>{
//操作deviceList
})
}
}
之前封装的js方法存在一个问题,就是当出现镶套调用移动端方法时,由于是通过回调函数的写法,会出现回调地狱的情况。此外移动端两端也反映,定义不同的模块意义不大,而且每次调用callback方法写起来也麻烦。
于是一不做,二不休,重新约定了协议,并对js方法重新进行了封装。
新协议如下:主要是只设置了唯一的模块名App,并且android也通过cmd传递所调用的方法,而调用时统一使用postMessage
//ios
window.webkit.messageHandlers.App.postMessage({
cmd: "方法名",
sequence: sequence,
data: data
});
//android
window.App.postMessage(JSON.stringify({
cmd: "方法名",
sequence:sequence,
data:data
}));
并且约定app通过window.App.reviceMessage方法返回回调数据。app回调时传参为一个res对象,res对象包括cmd、sequence和回调data三个参数。实际上sequence就可以确定唯一的回调,而cmd一般用作console数据,便于程序运行时的调试分析
window.App.reviceMessage = (res) => {
}
同时,为了解决回调地狱的问题,重新封装了js的调用方法。
封装的方法为callNative,每次调用callNative方法,都会先通过app注入的方法向app对应的cmd进行请求,然后返回一个promise,并将该promise的resolve方法存储到resolves对象中,key值为sequence。
app回调前,promise的状态为pendding.
app回调时,会调用js定义好的reviceMessage方法,进而会调用resolves[sequence],而resolves[sequence]被赋值为promise的resolve方法,因此promise会变为resolved状态,同时回调的参数通过resolves[sequence]方法传递给then方法,然后我们在then中删除resolves[sequence]解除资源占用,并结合promise的链式调用,将res传递给下一个then方法(或者通过await获取到res)
长连接方法类似,同样定义了一个promise,并通过await使得promise状态变为resolved后,才被yield返回出去,调用时由于每次返回promise,因此使用for await of
const resolves = {};
const reviceMessage = ({cmd,sequence,data})=>{
const res = isIOS? data:JSON.parse(data);
resolves[sequence]?.(res);
console.log(`native call js cmd:${cmd}`, res)
}
if (!window.App) {
window.App = {
receiveMessage
};
} else if (!window.App.receiveMessage) {
window.App.receiveMessage = receiveMessage;
}
const postMessage = (cmd,sequence,data) => {
if(IOS) {
window.webkit.messageHandlers.App.postMessage({
cmd: cmd,
sequence: sequence,
data: data
});
} else if(Android) {
window.App.postMessage(JSON.stringify({
cmd: cmd,
sequence:sequence,
data:data
}));
}
console.log(`js call native cmd:${cmd}`, data)
}
//单次调用
const callNative = (cmd,data) => {
const sequence = getUUID();
postMessage(cmd,sequence,data);
return new Promise(resolve=>{
resolves[sequence] = resolve;
}).then(res=>{
delete resolves[sequence];
return res; //利用then链式
})
}
//多次调用
const callNativeKeepalive = *(cmd,data) => {
const sequence = getUUID();
postMessage(cmd,sequence,data);
do {
const p = new Promise(resolve=>{
resolves[sequence] = resolve;
}).then(res=>{
delete resolves[sequence];
return res; //利用then链式
});
yield await p;
} while(true); //可以对true添加控制,方便在beforeDestroy销毁
}
module.exports = {
callNative,
callNativeKeepalive
}
api封装
export function getDeviceList(data) {
return callNative('getDeviceList', data)
}
export function getDeviceList_L(data) {
return callNativeKeepalive('getDeviceList', data);
}
方法调用
import api from '.api'
methods: {
//单次调用
async getDeviceList() {
const data = await api.getDeviceList();
//操作devicelist
},
//长连接
async getDeviceList_L(){
for await (let data of api.getDeviceList_L()) {
//操作devicelist
}
}
}
重新封装后,单次调用的,调用方式变成了async/await的形式,避免了回调地狱,长连接用for await of调用更具语义性。
对于ios,通过safe-area-inset-xxx 设置安全距离xxx为top、left、right和bottom
对于android,通过getAndroidSafeArea回调返回安全区域距离,然后覆盖到css变量的值,使得样式在body下生效
body {
--safe-top: 0px;
--safe-left: 0px;
--safe-right: 0px;
--safe-bottom: 0px;
padding-top: calc(10px + constant(safe-area-inset-top) + var(--safe-top));
padding-top: calc(10px + env(safe-area-inset-top) + var(--safe-top));
padding-left: calc(10px + constant(safe-area-inset-left) + var(--safe-left));
padding-left: calc(10px + env(safe-area-inset-left) + var(--safe-left));
padding-right: calc(10px + constant(safe-area-inset-right) + var(--safe-right));
padding-right: calc(10px + env(safe-area-inset-right) + var(--safe-right));
padding-bottom: calc(10px + constant(safe-area-inset-bottom) + var(--safe-bottom));
padding-bottom: calc(10px + env(safe-area-inset-bottom) + var(--safe-bottom));
}
const data = await getAndroidSafeArea();
if(data.safeTop) {
window.document.body.style.setProperty('--safe-top',data.safeTop);
window.document.body.style.setProperty('--safe-left',data.safeLeft);
window.document.body.style.setProperty('--safe-right',data.safeRight);
window.document.body.style.setProperty('--safe-bottom',data.safeBottom);
}
4.打包封装
之前也说了,该项目是作为之后H5开发的基座,因此还应具有对应用进行打包的能力,而且最终的目标是将所有H5开发的应用都并入到该项目下,因此该项目需要能打包多个应用。
将src/Page作为应用目录,目录下APP1、APP2、APP3则分别是一个个独立的H5应用
function getConfig() {
let config;
if (process.env.NODE_ENV === 'development') {
console.log('开发模式');
const list = fs.readdirSync('./src/Page')
const pages = {}
list.map((folder) => {
pages[folder] = {
entry: `src/Page/${folder}/main.js`,
template: 'public/index.html',
filename: `${folder}.html`,
title: folder
}
})
config = {
pages
}
} else {
console.log('生产模式');
const folder = process.argv[3];
config = {
outputDir: `dist/${folder}`,
publicPath: `./`,
pages: {
index: {
entry: `src/Page/${folder}/main.js`,
title: folder,
},
}
};
}
return config;
}
module.exports = {
...getConfig(),
configureWebpack: () => {
const plugins = [...];
return {
resolve: {
alias: {
...
},
},
plugins
}
},
}
对vue.config.js进行如上配置。
开发环境中,NODE_ENV为development,生成multi-page模式的cli配置,每个APP作为一个单页面应用被创建于缓存中,并可以通过/{folderName}.html访问,即APP1.html访问APP1的单页面应用,APP2.html访问APP2,方便进行调试。
生产环境中,通过process.argv[3]获取npm run build XXX中的XXX,想要打包APP1就运行npm run build APP1,打包APP2就运行npm run build APP2……因为设置了outputDir,对应的应用会打包到dist下对应的文件夹中,因此不用担心不同的应用出现路径冲突的情况,并且方便使用npm-run-all的run-p命令进行并行打包。
5.顺便说说多语言,和自定义webpack plugin进行关键字替换,还有最长多语言的实现
多语言使用vue-i18n实现。通过new VueI18n创建多语言实例,结合axios和setLocaleMessage进行多语言懒加载
由于使用的axios,多语言并不会被打包到bundle包中,多语言文件存放于public文件夹下,会直接被打包到dist目录下
import VueI18n from 'vue-i18n';
import Vue from 'vue';
import axios from 'axios';
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {}
});
export const changeLanguage = (language) => {
return new Promise((resolve, reject) => {
if (i18n.messages[language]) {
i18n.locale = language;
resolve();
} else {
axios.get(`./locals/${language}.json`)
.then(({ data }) => {
console.log('加载多语言:', language);
i18n.setLocaleMessage(language, data);
i18n.locale = language;
resolve();
}).catch(() => {
console.warn('加载多语言失败:', language)
reject();
});
}
})
}
const init = () => {
changeLanguage('en')
}
init();
export default i18n;
由于打包完成后,多语言直接放置在dist目录下,因此可以在afterEmit时进行处理,通过nodejs的文件系统方法fs
,在afterEmit时访问dist/应用名/多语言文件夹
并对多语言进行关键字替换和最长多语言提取操作。
插件实现如下,先定义插件类,然后封装apply方法,并调用afterEmit钩子,然后借由fs
访问多语言文件,并通过正则匹配对要替换的字符串进行选中和替换。同时在遍历多语言文件时,存放各个key值对应的最长字符串,并最后写入到lang.json中
const path = require('path');
const folder = process.argv[3];
const localsDir = path.resolve(`dist/${folder}/locals`)
const fs = require('fs');
const wordMap = {
'str1': '产品'
}
function replaceLanguage(name) {
const pathanme = localsDir + '/' + name;
const res = fs.readFileSync(pathanme, { encoding: 'utf8' });
const reStr = '(' + Object.keys(wordMap).join('|') + ')';
const re = new RegExp(reStr, 'g');
const newStr = res.replace(re, (it) => wordMap[it]);
fs.writeFileSync(pathanme, newStr, 'utf8');
return newStr;
}
class MyPlugin {
apply(compiler) {
compiler.hooks.afterEmit.tapAsync('MyPlugin', (compilation, callback) => {
const files = fs.readdirSync(localsDir)
const longStr = {};
files.forEach(file => {
const str = replaceLanguage(file);
const obj = JSON.parse(str);
//获取最长多语言
Object.keys(obj).forEach(key => {
if(key.includes('------')){
return;
}
if (!longStr[key]) {
longStr[key] = obj[key]
} else {
if (longStr[key].length < obj[key].length) {
longStr[key] = obj[key]
}
}
})
})
fs.writeFileSync(localsDir + '/lang.json', JSON.stringify(longStr,null,4), 'utf8');
callback();
}
);
}
}
module.exports = MyPlugin;
html代码
<html>
<head>
<style>
div {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.055);
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<div id="out">
<div id="mid">
<div id="inz">
</div>
</div>
</div>
<script>
//…………
</script>
</body>
</html>
其中最里层的冒泡和捕获(即上图5和6)在同一时间片,前后顺序根据代码顺序而定。
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
out.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
mid.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
inz.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
out.addEventListener('click', function () {
console.log('捕获:' + this.id)
}, true)
mid.addEventListener('click', function () {
console.log('捕获:' + this.id)
}, true)
inz.addEventListener('click', function () {
console.log('捕获:' + this.id)
}, true)
</script>
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
out.addEventListener('click', function () {
console.log('捕获:' + this.id)
}, true)
mid.addEventListener('click', function () {
console.log('捕获:' + this.id)
}, true)
inz.addEventListener('click', function () {
console.log('捕获:' + this.id)
}, true)
out.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
mid.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
inz.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
</script>
事件冒泡、事件捕获和事件委托详细内容可参考该网页:https://www.cnblogs.com/Chen-XiaoJun/p/6210987.html
jquery常用绑定事件方法有.on()
.bind()
.one()
.one()
使用one绑定的事件只会触发一次。
.bind()
截至jQuery 3.0,.bind()已被弃用。它被.on()自jQuery 1.7以来将事件处理程序附加到文档的方法所取代,因此不鼓励使用它。
.on()
可以通过第二个参数绑定子控件实现事件委托,如
$("#mid").on('click',"#inz",function () {
console.log("jquery on:" + this.id)
})
on()
.bind()
.one()
都是可以通过事件名称绑定事件。
事件名包括click
绑定单击、dblclick
绑定双击、mousemove
绑定鼠标移动等。
jquery还有.click(f)
、.dblclick(f)
、.mousemove(f)
等方法,相当于.on('click',f)
、.on('dblclick',f)
、.on('mousemove',f)
的简写
jQuery 事件方法:http://www.runoob.com/jquery/jquery-ref-events.html
jquery的on()
bind()
click()
one()
等方法绑定的事件,都是在事件冒泡阶段触发。
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
$("#mid").on('click',function(){
console.log('on:' + this.id)
})
$("#mid").bind('click',function(){
console.log('bind:' + this.id)
})
$("#mid").one('click',function(){
console.log('one:' + this.id)
})
$("#mid").click(function(){
console.log('click:' + this.id)
})
mid.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
inz.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
</script>
用on()的参数绑定选择器虽然可以给子控件绑定事件,但是事件还是在冒泡到的父控件时才执行(即将绑定到子控件的事件委托给父控件执行);同时,该事件先于该父控件用jquery绑定的其他事件
1.当inz的冒泡事件阻止事件传输后,通过事件委托绑定到inz上的事件未触发。这是因为用$("#mid").on('click','#inz',f)虽然把事件绑定到了inz上,但是事件的执行却被委托给了父控件mid
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
$("#mid").on('click',function(){
console.log('on:' + this.id)
})
$("#mid").on('click','#inz',function(){
console.log('on:' + this.id)
})
mid.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
inz.addEventListener('click', function (e) {
console.log('冒泡:' + this.id)
e.stopPropagation()
}, false)
</script>
2.委托事件先于自身绑定事件(通过jquery绑定的)。
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
$("#mid").on('click',function(){
console.log('on:' + this.id)
})
$("#mid").on('click','#inz',function(){
console.log('on:' + this.id)
})
mid.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
inz.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
</script>
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
mid.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
inz.addEventListener('click', function () {
console.log('冒泡:' + this.id)
}, false)
$("#mid").on('click',function(){
console.log('on:' + this.id)
})
$("#mid").on('click','#inz',function(){
console.log('on:' + this.id)
})
</script>
1.this指向
2.call、apply参数区别
给DOM绑定事件时,事件函数的this是指向该DOM的,但是在事件函数中再调用其他函数,这时其他函数的this是指向window的。要想其他函数的this也指向DOM,可以使用call()
、apply()
我顺便用这个例子记一下call()
、apply()
参数的区别
<script>
var out = document.getElementById("out")
var mid = document.querySelector("#mid")
var inz = document.querySelector("#inz")
mid.addEventListener('click', function () {
ttt.call(this, '冒', '泡')
ttt.apply(this, ['冒', '泡'])
ttt('冒', '泡')
}, false)
function ttt(x, y) {
console.log(x + y + ':' + this)
}
</script>
在codepen上看到了一个好看的pen,于是把它重写成了一个vue的组件;
为了能在其他地方使用该组件,准备创建一个自己的vue组件库发布到npm上;
因为之前没有发布过,于是就先上网上搜索了一下教程
主要是参考了一下这篇教程还有iview和element这两个组件库的package.json、index.js、webpack文件。
只看创建流程可以直接看第三部分
发布包到npm库的命令是npm publish
npm publish
发布包,需要先配置webpack.json
文件,如果没有webpack.json
文件,可以通过npm init
命令初始化一个
package.json
的部分字段简介如下
name:发布的包名,默认是上级文件夹名。不得与现在npm中的包名重复。包名不能有大写字母/空格/下滑线!
version:你这个包的版本,默认是1.0.0。对于npm包的版本号有着一系列的规则,模块的版本号采用X.Y.Z的格式,具体体现为:
1、修复bug,小改动,增加z。
2、增加新特性,可向后兼容,增加y
3、有很大的改动,无法向下兼容,增加x
description:项目简介
mian:入口文件,默认是index.js
scripts:包含各种脚本执行命令
test:测试命令。
author:自己的账号名
license:开源文件协议
private:是否私有
如果要发布的话需要把private字段设为false
发布的包的资源可以通过https://unpkg.com/$name@$version/
找到。
我们可以通过创建文件npmignore
或者 pkg.files
来设置上传时过滤某些文件和文件夹,如果我们不设置的话,有些文件和文件夹也是会默认忽略上传的,比如node_modules文件夹、package-lock.json文件等
先看一下node.js中的模块调用的规则 https://nodejs.org/api/modules.html#modules_accessing_the_main_module
require(X) from module at path Y
1. If X is a core module,
a. return the core module
b. STOP
2. If X begins with '/'
a. set Y to be the filesystem root
3. If X begins with './' or '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
4. LOAD_NODE_MODULES(X, dirname(Y))
5. THROW "not found"
LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text. STOP
2. If X.js is a file, load X.js as JavaScript text. STOP
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP
LOAD_INDEX(X)
1. If X/index.js is a file, load X/index.js as JavaScript text. STOP
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon. STOP
LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. If "main" is a falsy value, GOTO 2.
c. let M = X + (json main field)
d. LOAD_AS_FILE(M)
e. LOAD_INDEX(M)
f. LOAD_INDEX(X) DEPRECATED
g. THROW "not found"
2. LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = [GLOBAL_FOLDERS]
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
b. DIR = path join(PARTS[0 .. I] + "node_modules")
c. DIRS = DIRS + DIR
d. let I = I - 1
5. return DIRS
以iview为例,我们npm i iview
下载iview组件库后,通过import iView from 'iview'
引用它,其实就是通过import iView from './node_modules/iview'
引用它,然后./node_modules/iview
是一个文件夹,会按照LOAD_AS_DIRECTORY(X)
中的规则寻找,先是找到./node_modules/iview/package.json
这个文件,然后找到其中的main
字段,最后通过main
字段找到./node_modules/iview/dist/iview.js
这个文件。
iview.js
这个文件是通过webpack打包得到的,直接看代码基本上是看不懂的,我们看看它是怎么打包出来的。
先找到iview.js的webpack配置文件
https://github.com/iview/iview/blob/2.0/build/webpack.dist.dev.config.js
……
process.env.NODE_ENV = 'production';
module.exports = merge(webpackBaseConfig, {
devtool: 'source-map',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
filename: 'iview.js',
library: 'iview',
libraryTarget: 'umd',
umdNamedDefine: true
},
……
});
上面是在生产环节下打包iview.js的配置,我们找到它的入口文件
https://github.com/iview/iview/blob/2.0/src/index.js
import Affix from './components/affix';
……
const components = {
Affix,
……
};
const iview = {
...components,
iButton: Button,
……
};
const install = function(Vue, opts = {}) {
if (install.installed) return;
……
Object.keys(iview).forEach(key => {
Vue.component(key, iview[key]);
});
……
};
// auto install
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
const API = {
……
install,
……
};
……
module.exports.default = module.exports = API; // eslint-disable-line no-undef
从该文件可以看出来,它先是将components文件夹中的组件都通过import引入,然后定义了一个components对象来存放所有组件,然后又对部分组件增加了第二个key定义了iview对象(例如<Col><Col>
组件,也可以通过<i-col><i-col>
来使用),然后定义了一个install函数来遍历注册组件。最后将install函数作为输出对象的install方法。
然后这个输出对象其实就是上面import iView from 'iview'
得到的iView
对象,因此通过Vue.use(iView)
来调用iView.install
方法就能成功的把iView库的组件注册到我们的Vue对象上。
另外,如果是直接通过CDN引用js文件的话,会触发auto install注释下的内容,会将组件注册到当前window.Vue
上(需要先通过CDN引入vue.js)
import XXX from './src/components/XXX'
import YYY from './src/components/YYY'
import ZZZ from './src/components/ZZZ'
……
const components = [
XXX,YYY,ZZZ,……
];
const install = function (Vue) {
components.forEach(component => {
Vue.component(component.name, component);
});
};
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
XXX,
YYY,
ZZZ,
……
};
这里说明一下,我是用组件内部的name属性做的组件标签名
5. 配置webpack.config.js
var path = require('path')
var webpack = require('webpack')
const NODE_ENV = process.env.NODE_ENV
module.exports = {
//入口这里一个是测试组件时npm run dev的入口文件,一个是npm run build的入口文件
entry: NODE_ENV === 'development' ? './src/main.js' : './index.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/', //输出的文件夹
filename: 'filename.js',//输出的文件名
library: 'objectname', //引用js文件时创建的全局变量名
libraryTarget: 'umd',
umdNamedDefine: true
},
……
}
……
package.json
{
"name": "package-name",
"description": "描述",
"version": "1.0.0",
"author": "XXXXXXXXXXX",
"license": "MIT",
"private": false,
"main": "dist/filename.js",
……
}
//npm导入
npm i package-name
import xxx from 'package-name'
Vue.use(xxx)
//cdn导入
<script src="//vuejs.org/js/vue.min.js"></script>
<script src="//https://unpkg.com/[email protected]/dist/filename.js"></script>
<input type="submit" value="Submit Form">
<input type="reset" value="Reset Form">
elements 该属性是表单中所有表单元素的集合
//每个表单字段
focus() 获得焦点
blur() 失去焦点
<input type="text" size=" " maxlength=" " value="init value">
<textarea rows=" " clos=" ">init value</textarea>
通过设置keypress事件
HTML5约束验证API
required 必填项
输入类型
新增pattern属性,可以使用正则表达式进行约束
<input type="text" pattern="\d+" name="count">
使用
checkValidity()
方法可以检测表单中的某个字段是否有效。是否有效的判断依据是一些<input>
的约束条件。
常用绑定事件
==DOM选区在红宝书12章 DOM范围==
下面使用的的Range就是DOM选区
范围选择()
window.getSelection()或者document.getSelection() 建
Selection对象属性:
Selection对象方法:
当一个HTML文档切换到设计模式时,document暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
一个 Boolean ,如果是 false 则表示操作==不被支持==或==未被启用==。
注意:在调用一个命令前,不要尝试使用返回值去校验浏览器的兼容性
比如,复制选区(选区就是上面介绍的 ==window.getSelection()== 或者==document.getSelection()== )
document.execCommand('copy')
属性和方法具体见:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand
在document中可以使用document.createRange()创建一个选区,也可以new Range()创建
也可以用Selection对象的getRangeAt方法取得
我们没有在此列举继承方法。
通过以下方法,可以从 Range 中获得节点,改变 Range 的内容。
选中全文(相当于在浏览器按ctrl+a)
var select = document.getSelection()
var range = document.createRange()
range.selectNode(document.body)
select.addRange(range)
或者
必须要已经有选中区域才行,不然
var range = select.getRangeAt(document.body)
会报错,但是省去了插入区域到选区的步骤
var select = document.getSelection()
var range = select.getRangeAt(document.body)
range.selectNode(document.body)
Ajax是Asynchronous Javascript + XML的简写
核心技术XMLHttpRequest对象
(旧版本 remote scripting)
var xhr = new XMLHttpRequesr();
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("get", url, false);
xmlhttp.send(null);
···
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
if(xmlhttp.status ···){
···
}else{
···
}
}
xmlhttp.open("get", url, true);
xmlhttp.send(null);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("post", url, false);
xmlhttp.send(···);
···
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
if(xmlhttp.status ···){
···
}else{
···
}
}
xmlhttp.open("post", url, true);
xmlhttp.send(···);
FormData
超时
timeout 属性
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型
跨域资源共享
比如客户端 请求头 Origin:www.xxx.cn
如果服务器 的Access-Control-Allow-Origin是'*',或者包扩 www.xxx.cn,那服务器就会接受请求
跨域请求不带cookie
对CORS的实现
IE使用 XDomainRequest
其他浏览器 对XMLHttpRequest实现了CORS原生支持
跨域XHR对象有一些限制(为了安全)
貌似有些请求头可以自定义了
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*')
就成功了
图像Ping
惰性载入函数
//举个栗子
var xxx = function(){
if(···){
xxx = function(){··a··}
}else{
xxx = function(){··b··}
}
}
函数绑定
bind()用闭包怎么实现?
函数柯里化
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
优点:
缺点:
防篡改对象
不可扩展对象<密封的对象<冻结的对象
高级定时器
javascript是单线程的,js中定时器不是线程,仅仅只是计划在未来的某个时间执行(加入队列),执行时间也并不能保证(浏览器负责排序)
javascript 中没有任何代码是立刻执行的,但一旦进入进程空闲则尽快执行
每次执行完一套代码后,javascript会返回很短的时间,防止页面锁定
函数节流(看起来是防抖)
DOM操作比非DOM操作需要更多内存和CUP时间
例子:节流
//红宝书写法
function throttle(method,context){
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context)
},100)
}
自己写的
let throttle = function () {
let flag = true
return function (fn, x) {
if (flag) {
fn(x)
}
flag = false
setTimeout(function () {
flag = true
}, 500)
}
}()
window.onresize = function () {
throttle(console.log, window.innerHeight)
}
例子:防抖
//借鉴红宝书节流的写法
function debounce(method,context){
if(!method.tId){
method.tId = setTimeout(function(){
method.call(context)
method.tId = null
},100)
}
}
自己写的
let debounce = function () {
let settimer;
return function (fn, x) {
clearTimeout(settimer)
settimer = setTimeout(function () {
fn(x)
}, 500)
}
}()
window.onresize = function () {
debounce(console.log, window.innerHeight)
}
节流:间隔n执行一次
防抖:在间隔n内执行的操作,只有最后一次有效
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
Web Storage规范包含两种对象的定义:sessionStorage
和globalStorage
。这两个对象在支持的浏览器中都是以windows对象属性的形式存在。
localStorage
取代了globalStorage
么?
//???
'globalStorage' in window
//false
'sessionStorage' in window
//true
Storage类型提供最大的存储空间来存储名值对。
sessionStorage对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在sessionStorage中的数据==可以跨越页面刷新==而存在。
//使用方法存储数据
sessionStorage.setItem("name", "Name");
//使用属性存储数据
sessionStorage.book = "JavaScript";
//使用方法读取数据
var name = sessionStorage.getItem("name");
//使用属性读取数据
var book = sessionStorage.book;
//使用delete 删除一个值——在WebKit 中无效
delete sessionStorage.name;
//使用方法删除一个值
sessionStorage.removeItem("book");
可以通过结合length属性和key()方法来迭代sessionStorage中的值:
for (var i = 0, len = sessionStorage.length; i < len; i++) {
var key = sessionStorage.key(i);
var value = sessionStorage.getItem(key);
console.log(key + ' = ' + value);
}
//只输出本身的属性
还可以使用for-in循环来迭代sessionStorage中的值:
for (var key in sessionStorage) {
var value = sessionStorage.getItem(key);
console.log(key + ' = ' + value);
}
//把原型的属性都输出来了
这个对象的目的是跨越会话存储数据,,但有特定的访问限制。要使用globalStorage,首先要指定哪些域可以访问该数据。
使用全局变量和函数的开销要比局部的开销大,因为要涉及作用域链上的查找。
function(){
var node1 = document.getElementById(xxx)
···
var node1 = document.getElementById(xxx)
···
}
上面这段代码多次引用全局document对象,==每次都会进行作用域链接查找==,如果将document对象存在作用域链接本地的变量中,然后在余下代码中使用本地变量替换原来的doc,与原来相比就只会进行==一次作用域链接查找==,可以减少开销。
function(){
var doc = document
var node1 = doc.getElementById(xxx)
···
var node1 = doc.getElementById(xxx)
···
}
==将一个函数中会多次用到的全局对象储存为局部变量总是没错的==
with会增加代码作用域链长度
必须使用with语句的情况很少,大多数时候可以用局部变量完成相同的事情
script标签
async :异步脚本,建议不要在加载时修改DOM 一定在load事件前执行,但是可能在DOMContentLoaded事件触发之前或之后执行
defer :脚本延迟到整个页面解析完毕再运行
‘use strict’
关键字(语言保留的不能用作标识符)
break、else、new、var、 case、 finally 、 return、 void 、 catch 、for 、switch 、 while 、 continue、 function 、this 、 with 、default 、 if 、 throw 、 delete 、 in 、 try 、do 、 instranceof、 typeof
let 和 const定义已经定义过的标识符会报错
a=1
let a = 2
VM1053:1 Uncaught SyntaxError: Identifier 'a' has already been declared
at <anonymous>:1:1
保留字(目前无用途,将来可能用作关键字)
abstract 、 enum 、int 、 short 、 boolean 、export 、interface、 static、 byte 、extends 、 long 、 super 、 char 、 final 、native 、synchronized 、 class 、float 、 package 、throws 、 const 、goto 、private 、transient 、 debugger 、 implements 、protected 、 volatile 、 double 、import 、public
未定义的初始化值为 undefined
简单数据类型:Undefined、Null、Bool、Number、String
复杂数据类型:Object
函数:Function
基本的数据类型有:`undefined,boolean,number,string,null.基本类型的访问是按值访问的
javascript中除了上面的基本类型(number,string,boolean,null,undefined)之外就是引用类型了
ECMAScript中所有类型都有与Boolean值等价的值,可用Boolean()转化
数据类型 | 转化为true的值 | 转化为false的值 |
---|---|---|
Boolean | true | false |
String | 非空字符串 | 空字符串 |
Number | 非零数字 | 0或者NaN |
Object | 任何对象 | null |
Undefined | n/a | undefined |
10进制中 e的用法
toString(x)
在调用数值的toString方法,可以传递一个参数:输出数值的基数。没有参数则返回该值的10进制量
==!浮点数有舍入误差: console.log(0.1+0.2==0.3) //false
==
isFinite()
测试一个数值是不是无穷值。
Number()
a = Number(b) 等同 a = +b
parseInt()
可以使用第二个参数,表示被解析值的基数
parseInt('111',2) // 7
parseFloat()
只解析十进制
转义字符 | 意义 |
---|---|
\b | 退格(BS) ,将当前位置移到前一列 |
\f | 换页(FF),将当前位置移到下页开头 |
\n | 换行(LF) ,将当前位置移到下一行开头 |
\r | 回车(CR) ,将当前位置移到本行开头 |
\t | 水平制表(HT) (跳到下一个TAB位置) |
\ | 代表一个反斜线字符''' |
' | 代表一个单引号(撇号)字符 |
" | 代表一个双引号字符 |
\unnn | 以16进制代码nnnn表示一个Unicode字符 |
\xhh | 1到2位十六进制所代表的任意字符 |
字符
charCodeAt() : 返回的字符的unicode编码。
'9'.charCodeAt() //57
在调用数值的toString方法,可以传递一个参数:输出数值的基数。没有基数的则返回该值的字面量
若a有toString方法,则String(a)
与 a.toString()
返回相同结果
还可以通过+""
实现转化为字符串
Object类型的属性方法:
~ 按位非
& 按位与
| 按位或
^ 按位异或
<<
左移
>>
有符号位右移
>>>
无符号位右移
== 转换后比较
=== 直接比较
true == 1 //true
true === 1 //false
NaN<3 //false
NaN>=3 //false
arguments 在箭头函数中不能用
ECMAScript中所以函数的参数都是按值传递的
callee 与 caller
function diff_caller_callee(k) {
return function (k) {
console.log(arguments.callee)
console.log(arguments.callee.caller)
if (k) {
arguments.callee(k - 1)
}
}(k)
}
diff_caller_callee(1)
迭代方法
ECMAScript5为数组定义了5个迭代方法。
归并方法
ECMAScript 5 还新增了两个归并数组的方法。
reduce()和reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。
values.reduce((prev, cur, index, array)
使用+可以直接获取Date对象的时间戳
var a = new Date()
a.toDateString()
//"Wed May 15 2019"
a.toTimeString()
//"14:30:28 GMT+0800 (**标准时间)"
a.toLocaleDateString()
//"2019/5/15"
a.toLocaleTimeString()
//"下午2:30:28"
a.toUTCString()
//"Wed, 15 May 2019 06:30:28 GMT"
3个标志符
所有RegExp实例含有下列属性
函数名实际上是一个指向函数对象的指针
解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
// ok
alert(sum(10,10));
function sum(num1, num2){
return num1 + num2;
}
// unexpected identifier(意外标识符)
alert(sum(10,10));
var sum = function(num1, num2){
return num1 + num2;
};
arguments具有一个callee属性,该属性是一个指针,指向拥有这个arguments对象的函数。
ECMAScript 5也规范了一个函数对象属性:caller(看着很像callee),这个属性中保存着调用当前函数的函数的引用,如果实在全局作用域中调用当前函数,它的值为null。
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
inner.caller
指向outer()
。为了实现更松散的耦合,也可以通过argument.callee.caller
来访问相同的信息。
function outer() {
inner();
}
function inner() {
alert(arguments.callee.caller);
}
outer();
严格模式 arguments.callee、arguments.caller 不能使用
function factorial(num){
"use strict"
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
factorial(55)
//VM221880:6 Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
this引用的是函数执行的环境对象
使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。
var value = '25';
var number = Number(value); // 转型函数
console.log(typeof number); // 'number'
var obj = new Number(value); // 构造函数
console.log(typeof obj); // 'object'
toFixed() 按指定小数位返回数值的字符串表示
var num = 10
num.toFixed(2) //10.00
toPrecision()
var num = 99
num.toPrecision(1) //1e+2
String 类型
charAt() 返回 字符串 指定位置的字符 //也可以直接方括号+下标访问,区别如下:
a='aafaadsf'
a.charAt(9) //""
a[9] //undefined
charCodeAt() :返回字符串指定位置的字符编码
fromCharCode()方法 :这个方法的任务是接收一个或多个字符编码,然后将它们转换成一个字符串。相当于charCodeAt()反操作。
var stringValue = "hello ";
var result = stringValue.concat("world", "!");
alert(result); //"hello world!"
var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substring(3,7)); //"lo w"
alert(stringValue.substr(3, 7)); //"lo worl"
// 参数是负值的情况下,它们的行为就不尽相同了。
// 其中,slice()方法会将传入的负值与字符串的长度相加,
// substr()方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0。
// 最后,substring()方法会把所有负值参数都转换为0。
alert(stringValue.slice(-3)); //"rld"
alert(stringValue.substring(-3)); //"hello world"
alert(stringValue.substr(-3)); //"rld"
alert(stringValue.slice(3, -4)); //"lo w"
alert(stringValue.substring(3, -4)); //"hel"
alert(stringValue.substr(3, -4)); //""(空字符串)
//substring() 两个参数可以调换位置,其总是显示两个参数之间的差值的部分
stringValue.substring(1,6); //"ello "
stringValue.substring(6,1); //"ello "
字符串位置方法
trim() 删除字符串前置和后缀的所有字符
--
toUpperCase() //转大写
toLowerCase() //转小写
--
字符串的模式匹配方法
match()
方法,在字符串上调用这个方法,本质上和调用RegExp的exec()
方法相同。match()
方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。var text = 'cat, bat, sat, fat';
var pattern = /.at/;
// 等价于 pattern.exec(text)
var matches = text.match(pattern);
alert(matches.index); //0
alert(matches[0]); //"cat"
alert(pattern.lastIndex); //0
search()
方法的参数与match()
方法相同,该方法返回字符串中第一个匹配项的索引,没有匹配项返回-1;个人认为serch()
就是正则版的indexOf()
。
var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
aler t(pos); //1
ECMAScript提供了replace()方法,该方法接受两个参数,第一个参数可以是RegExp对象或者是一个字符串,第二个参数可以是一个字符串或者一个函数。
var text = "cat, bat, sat, fat";
var result = text.replace("at", "ond");
alert(result); //"cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
aler t(result); //"cond, bond, sond, fond"
Global对象
URI编码方法
encodeURI()、encodeURIComponent() 编码
decodeURI()、decodeURICompoent() 解码
==eval()==
Math对象
min、max
var a =[1,23,4]
Math.max.apply(Math,a) //23
Math.max(...a) //23
一个常用于取范围随机整数的方式 [n,m]
Math.floor(Math.random()*(m-n)+n)
ceil 向上取整、floor 向下取整、round 四舍五入
其他常用:abs 绝对值、pow 幂运算、sqrt 平方根
Node类型
每个节点都有一个nodeType属性,用于表明节点的类型。
为了确保跨浏览器兼容,将nodeType属性与数字值进行比较:
对于元素节点,nodeName
中保存的始终是元素的标签名,nodeValue
的值始终为null
childNodes
属性
每个节点都有一个childNodes属性,其中保存着一个NodeList
对象,该对象是一种==类数组对象==。==不是快照,会实时改变==
Array.prototype.slice.call(someNode.childNodes)
可以将其转换为数组(IE8及其之前无效)
用于向childNodes列表的末尾添加一个节点。
把节点放在指定位置,该方法接受两个参数:要插入的节点和作为参考的节点。插入节点后,被插入的节点会变成参照节点的前一个兄弟节点。参照节点是null的话,insertBefore与appendChild执行相同的操作,都插入列表末尾。
替换节点,接受两个参数:要插入的节点和要替换的节点。
移除节点,接受一个参数:要被移除的节点。
复制节点,接受一个布尔值,表示是否深复制。复制后返回的节点没有父节点,可以通过插入等操作手动指定。
处理文档树的文本节点
document.all
document.bdoy -> body
==document.documentElement== -> html
document.head -> head
document.domain 域名
document.URL
操作特性的方法:
对于style属性和onclick类型属性在通过getAttribute访问和直接访问时不同
someNode.getAttribute('style') !== someNode.style
someNode.getAttribute('onclick') !== someNode.onclick
someNode.getAttribute('class') === someNode.className
// create table
var table = document.createElement('table');
table.border = 1;
table.width = '100%';
// create tbody
var tbody = document.createElement('tbody');
table.appendChild(tbody);
// create row1
var row1 = document.createElement('tr');
tbody.appendChild(row1);
var cell1_1 = document.createElement('td');
cell1_1.appendChild(document.createTextNode('Cell 1,1'));
row1.appendChild(cell1_1);
var cell2_1 = document.createElement('td');
cell2_1.appendChild(document.createTextNode('Cell 2,1'));
row1.appendChild(cell2_1);
// create row2
var row2 = document.createElement('tr');
tbody.appendChild(row2);
var cell1_2 = document.createElement('td');
cell1_2.appendChild(document.createTextNode('Cell 1,2'));
row2.appendChild(cell1_2);
var cell2_2 = document.createElement('td');
cell2_2.appendChild(document.createTextNode('Cell 2,2'));
row2.appendChild(cell2_2);
document.body.appendChild(table);
querySelector()方法接受一个CSS选择符,返回与该模式匹配的第一个元素,若没有,返回null。
可以通过Document类型调用,也可以通过Element类型调用,后者只会在该元素后代元素的范围内查找匹配的元素。
querySelectorAll()方法返回的是所有匹配的元素,是一个NodeList实例。
getElementsByClassName()方法
classList属性,这个属性是新集合类型DOMTokenList的实例。
div.classList.remove("user")
有了classList属性,除非你需要全部删除所有类名,或者完全重写元素的class属性,否则也就用不到className了
document.activeElement
属性,始终会引用DOM中前端获得了焦点的元素。
dataset
document.querySelector('address')
>> <address>address</address>
document.querySelector('address').dataset.myid = '123'
>> "123"
document.querySelector('address')
>> <address data-myid="123">address</address>
document.querySelector('address').attributes
>> NamedNodeMap {0: data-myid, data-myid: data-myid, length: 1}
0: data-myid
length: 1
data-myid: data-myid
__proto__: NamedNodeMap
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");
//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");
//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");
//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");
尽量一次性完成innerHTML类操作,可以减少性能损失
scrollIntoView
方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果这个方法传入true作为参数,或者不传参数,那么窗口滚动之后就会让调用元素的顶部与视口顶部 尽可能平齐,如果传入false,调用元素会尽可能全部出现在视口中,不过顶部不一定平齐。
访问元素样式
css属性 | JavaScript属性 |
---|---|
background-image | style.backgroundImage |
color | style.color |
display | style.display |
font-family | style.fontFamily |
多数情况下都可以通过简单地转化属性名的格式来实现转换。其中一个不能直接转换的css属性是==float==。这个是由于float是JavaScript中的保留字,因此不能作为属性名。与其对应的JavaScript属性是==cssFloat==。
offsetHeight:元素在垂直方向上占用的空间大小。包括元素的高度,(可见的)水平滚动条的高度,上边框高度和下边框高度
offsetWidth:元素在水平方向上占用的空间大小。包括元素的宽度,(可见的)垂直滚动条的宽度,左边框宽度和右边框宽度
offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。
offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。
offsetParent 定位最近的祖先元素
offsetParent
属性不一定与parentNode
的值相等。
document.createNodeIterator()
let iterator = document.createNodeIterator(
document.querySelector(".v-content"),
NodeFilter.SHOW_ALL,
null,
false
);
let node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName);
node = iterator.nextNode();
}
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段
、处于目标阶段
和事件冒泡阶段
。
通过event可以直接访问事件对象
this可以访问目标元素
<input type="button" onclick="console.log(event.type+' '+this.type)">
//> click button
通过html指定事件处理的最后一个缺点是html和javascript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:html代码和javascript代码
DOM 0级方法指定的事件处理程序被认为是元素的方法,因此,这个时候的事件处理程序是在元素的作用域中运行,也就是说程序中的this可以引用当前元素。
var btn = document.getElementById('myBtn');
btn.onclick = function () {
console.log(this.id); // 'myBtn'
}
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
定义了两个方法用于处理指定和删除事件处理程序的操作。所有的DOM节点中都包含这两个方法,接受三个参数:事件名、事件处理程序和布尔值。最后这个布尔值如果是==true==,表示在==捕获阶段==调用事件处理程序;==false==表示在==冒泡阶段==调用事件处理程序,默认是==false==。
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。如果通过addEventListener()添加的匿名函数将无法移除。
btn.addEventListener('click', function () { //匿名函数
aler(this.id);
},false);
btn.removeEventListener('click', function () { //没有用!
aler(this.id);
},false);
var handler = function () {
aler(this.id);
};
btn.addEventListener('click',handler ,false);
btn.removeEventListener('click',handler,false); //有效!
注:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段(false),这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。
在触发DOM上的某个事件时,会产生一个事件对象event。
event对象成员
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
bubbles | Boolean | 只读 | 表明事件是否冒泡 |
cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 |
currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 |
defaultPrevented | Boolean | 只读 | 为true表示已经调用preventDefault() |
detail | Integer | 只读 | 与事件相关的细节信息 |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1 捕获,2 处于目标,3 冒泡 |
preventDefault() | Function | 只读 | 取消事件的默认行为。如果cancelable 是true,则可以使用这个方法 |
stopImmediatePropagation() | Function | 只读 | 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用 |
stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 |
target | Element | 只读 | 事件的目标 |
trusted | Boolean | 只读 | 为true表示事件是浏览器生成,false是开发人员创建 |
type | String | 只读 | 被触发的事件类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |
document.documentElement.onclick=function(){
console.log("html")
}
>> VM74756:2 MouseEvent {isTrusted: true, screenX: 217, screenY: 255, clientX: 217, clientY: 152, …}
>> VM114375:2 html
document.body.onclick=function(){
console.log("body")
event.stopPropagation()
}
>> VM74756:2 MouseEvent {isTrusted: true, screenX: 279, screenY: 266, clientX: 279, clientY: 163, …}
>> VM114489:2 body
load
window.onload
body标签加上onload特性
document.body.onmouseout = function(){
let el = event.target
el.style.border = '2px solid blue'
el.onmouseleave = function(){
el.style.border = '2px solid red'
setTimeout(function(){
el.style.border = ''
el.onmouseleave = null
},500)
}
}
页面上的所有元素都支持鼠标事件。除了mouseenter 和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。
只有在==同一个元素上==相继触发==mousedown== 和==mouseup== 事件,才会触发click 事件;如果mousedown 或mouseup 中的一个被取消,就不会触发click 事件。
<body>
<div ……>……</div>
</body>
从div移出
div的mouseout会触发,相关元素为body
与此同时body的mouseover也会触发,相关元素为div
document.onkeyup = function(){
console.log(event.keyCode)
}
//可以方便你看你按下的键的键码
document.oncontextmenu = function(){ ///禁用上下文菜单
event.preventDefault()
}
var event = document.createEvent('MouseEvents')
event.initMouseEvent('click',true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null)
document.body.dispatchEvent(event)
最近在github上搭个人主页,因为要使用github的api,必须用到owner和repo的值。
https://api.github.com/repos/:owner/:repo/issues
在开发环境下,可以自己赋值;在生产环境下,我则希望直接通过window.location.pathname获得。
在创建vue-cli时会自动配置环境变量,生产环境是production,开发环境是development
然后在.js和.vue中都可以通过process.env.NODE_ENV获得环境变量的值。
因此如果要设置一个在不同环境下不同的变量,可以通过process.env.NODE_ENV作为key值。
比如:
<script>
export default {
name: '',
data() {
return {
repo: {
"development": "pma934.github.io",
"production": window.location.pathname,
}
}
},
methods: {
xxx: function () {
console.log(this.repo[process.env.NODE_ENV])
}
}
}
</script>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.