共计 65741 个字符,预计需要花费 165 分钟才能阅读完成。
js 篇
==js 基本数据类型 ==
5 中基本数据类型:null、undefined、string、number、boolean
==setTimeout 和 setInterval==
《JavaScript 高级程序设计》这本书里面,介绍了很多关于 setTimeout 函数的神奇使用,今天来介绍下第一个——使用 setTimeout 代替 setInterval 进行间歇调用。
“在开发环境下,很少使用间歇调用(setInterval),原因是后一个间歇调用很可能在前一个间歇调用结束前启动”。
这话怎么理解呢?
首先我们来看一下一般情况下的 setInterval 函数的使用,以及如何使用 setTimeout 代替 setInterval
var executeTimes = 0;
var intervalTime = 500;
var intervalId = null;
// 放开下面的注释运行 setInterval 的 Demo
intervalId = setInterval(intervalFun,intervalTime);
// 放开下面的注释运行 setTimeout 的 Demo
// setTimeout(timeOutFun,intervalTime);
function intervalFun(){
executeTimes++;
console.log("doIntervalFun——"+executeTimes);
if(executeTimes==5){clearInterval(intervalId);
}
}
function timeOutFun(){
executeTimes++;
console.log("doTimeOutFun——"+executeTimes);
if(executeTimes<5){setTimeout(arguments.callee,intervalTime);
}
}
代码比较简单,我们只是在 setTimeout 的方法里面又调用了一次 setTimeout,就可以达到间歇调用的目的。
重点来了,为什么作者建议我们使用 setTimeout 代替 setInterval 呢?setTimeout 式的间歇调用和传统的 setInterval 间歇调用有什么区别呢?
- 区别在于,setInterval 间歇调用,是在前一个方法执行前,就开始计时,比如间歇时间是 500ms,那么不管那时候前一个方法是否已经执行完毕,都会把后一个方法放入执行的序列中。这时候就会发生一个问题,假如前一个方法的执行时间超过 500ms,加入是 1000ms,那么就意味着,前一个方法执行结束后,后一个方法马上就会执行,因为此时间歇时间已经超过 500ms 了。
var executeTimes = 0;
var intervalTime = 500;
var intervalId = null;
var oriTime = new Date().getTime();
// 放开下面的注释运行 setInterval 的 Demo
// intervalId = setInterval(intervalFun,intervalTime);
// 放开下面的注释运行 setTimeout 的 Demo
setTimeout(timeOutFun,intervalTime);
function intervalFun(){
executeTimes++;
var nowExecuteTimes = executeTimes;
var timeDiff = new Date().getTime() - oriTime;
console.log("doIntervalFun——"+nowExecuteTimes+", after" + timeDiff + "ms");
var delayParam = 0;
sleep(1000);
console.log("doIntervalFun——"+nowExecuteTimes+"finish !");
if(executeTimes==5){clearInterval(intervalId);
}
}
function timeOutFun(){
executeTimes++;
var nowExecuteTimes = executeTimes;
var timeDiff = new Date().getTime() - oriTime;
console.log("doTimeOutFun——"+nowExecuteTimes+", after" + timeDiff + "ms");
var delayParam = 0;
sleep(1000);
console.log("doTimeOutFun——"+nowExecuteTimes+"finish !");
if(executeTimes<5){setTimeout(arguments.callee,intervalTime);
}
}
function sleep(sleepTime){var start=new Date().getTime();
while(true){if(new Date().getTime()-start>sleepTime){break;}
}
}
(这里使用大牛提供的 sleep 函数来模拟函数运行的时间)
执行 setInterval 的 Demo 方法,看控制台
doIntervalFun——1, after 500ms
VM2854:19 doIntervalFun——1 finish !
VM2854:16 doIntervalFun——2, after 1503ms
VM2854:19 doIntervalFun——2 finish !
VM2854:16 doIntervalFun——3, after 2507ms
VM2854:19 doIntervalFun——3 finish !
VM2854:16 doIntervalFun——4, after 3510ms
VM2854:19 doIntervalFun——4 finish !
VM2854:16 doIntervalFun——5, after 4512ms
VM2854:19 doIntervalFun——5 finish !
可以发现,fun2 和 fun1 开始的间歇接近 1000ms,刚好就是 fun1 的执行时间,也就意味着 fun1 执行完后 fun2 马上就执行了,和我们间歇调用的初衷背道而驰。
我们注释掉 setInterval 的 Demo 方法,放开 setTimeout 的 Demo 方法,运行,查看控制台
doTimeOutFun——1, after 500ms
VM2621:32 doTimeOutFun——1 finish !
VM2621:29 doTimeOutFun——2, after 2001ms
VM2621:32 doTimeOutFun——2 finish !
VM2621:29 doTimeOutFun——3, after 3503ms
VM2621:32 doTimeOutFun——3 finish !
VM2621:29 doTimeOutFun——4, after 5004ms
VM2621:32 doTimeOutFun——4 finish !
VM2621:29 doTimeOutFun——5, after 6505ms
VM2621:32 doTimeOutFun——5 finish !
这下终于正常了,fun1 和 fun2 相差了 1500ms = 1000 + 500,fun2 在 fun1 执行完的 500ms 后执行。
== 闭包 ==
闭包的定义:
在 js 高级教程中的定义是:有权访问另一个函数作用域中的变量的函数。通俗地讲,如果一个函数执行完以后,这个函数中还存在一部分在内存当中,没有被垃圾回收机制回收,这个函数就称为闭包。
闭包的作用
1. 实现私有变量
如果我们写一个函数,里面有一个 name 值,我们可以允许任何人访问这个 name 属性,但是只有少部分人,可以修改这个 name 属性,我们就可以使用闭包,可以在 setName 值中,写哪些人具有修改的权限。
var person = function(){
// 变量作用域为函数内部,外部无法访问,不会与外部变量发生重名冲突
var name = "FE";
return {
// 管理私有变量
getName : function(){return name;},
setName : function(newName){name = newName;}
}
};
2. 数据缓存
假如说我们执行一个计算量很大函数,返回一个值,而这个值在其他函数中还有应用,这种情况下使用闭包,可以将该数据保存在内存中,供其他的函数使用(这是在其他博客中看到的,具体不是很清楚,如果有兴趣,可以自己查阅相关文献)。
缺点:
造成内存消耗过大,如果处理不当,会造成内存泄漏
== 事件防抖和事件节流 ==
- 事件防抖:debounce 的作用是在让在用户动作停止后延迟 x ms 再执行回调。
- 事件节流:throttle 的作用是在用户动作时没隔一定时间(如 200ms)执行一次回调。
使用原因:
浏览器的一些事件,如:resize,scroll,keydown,keyup,keypress,mousemove 等。这些事件触发频率太过频繁,绑定在这些事件上的回调函数会不停的被调用。这样浏览器的目的是为了保证信息的一致性,而对于我们来说就是一种资源的浪费了。
代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<style>
#mydiv{
border-bottom: 1px solid;
width: 100%;
height: 300px;
background-color: #999999;
}
.mouseMoveDisplay{
border-top: 1px solid rosybrown;
width: 100%;
height: 200px;
background-color: #bbbbbb;
}
</style>
<body>
<div id="mydiv"></div>
<table class="mouseMoveDisplay">
<tr>
<th>
一共移动了 <span class="mouseMove all">0</span> 次
</th>
</tr>
<tr>
<th>
防抖移动了 <span class="mouseMove debounce">0</span> 次
</th>
</tr>
<tr>
<th>
节流移动了 <span class="mouseMove throttle">0</span> 次
</th>
</tr>
</table>
<script>
// 防抖模式
function debounceFunction(fn,delay){
var delay=delay||200;
var timer;
return function(){
var th=this;
var args=arguments;
if (timer) {clearTimeout(timer);
}
timer=setTimeout(function () {
timer=null;
fn.apply(th,args);
}, delay);
};
}
// 事件节流
function throttleFunction(fn,interval){
var last;
var timer;
var interval=interval||200;
return function(){
var th=this;
var args=arguments;
var now=+new Date();
if(last&&now-last<interval){clearTimeout(timer);
timer=setTimeout(function(){
last=now;
fn.apply(th,args);
},interval);
}else{
last=now;
fn.apply(th,args);
}
}
}
var mydiv=document.getElementById("mydiv");
// 所有的移动次数
var allCount = 0;// 所有的次数
var all = document.getElementsByClassName('all')[0];
mydiv.addEventListener("mousemove",function () {
allCount++;
all.innerHTML = allCount;
})
// 防抖的移动次数
var debounceCount = 0;// 所有的次数
var debounce = document.getElementsByClassName('debounce')[0];
mydiv.addEventListener("mousemove",debounceFunction(function () {
debounceCount++;
debounce.innerHTML = debounceCount;
}))
// 节流的移动次数
var throttleCount = 0;// 所有的次数
var throttle = document.getElementsByClassName('throttle')[0];
mydiv.addEventListener("mousemove",throttleFunction(function () {
throttleCount++;
throttle.innerHTML = throttleCount;
}))
</script>
</body>
</html>
== 数组中的 forEach 和 map 的区别 ==
大多数情况下,我们都要对数组进行遍历,然后经常用到的两个方法就是 forEach 和 map 方法。
先来说说它们的共同点
相同点
- 都是循环遍历数组中的每一项
- forEach 和 map 方法里每次执行匿名函数都支持 3 个参数,参数分别是 item(当前每一项),index(索引值),arr(原数组)
- 匿名函数中的 this 都是指向 window
- 只能遍历数组
- 都不会改变原数组
区别
==map 方法 ==
1.map 方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
2.map 方法不会对空数组进行检测,map 方法不会改变原始数组。
3. 浏览器支持:chrome、Safari1.5+、opera 都支持,IE9+,
array.map(function(item,index,arr){},thisValue)
var arr = [0,2,4,6,8];
var str = arr.map(function(item,index,arr){console.log(this); //window
console.log("原数组 arr:",arr); // 注意这里执行 5 次
return item/2;
},this);
console.log(str);//[0,1,2,3,4]
若 arr 为空数组,则 map 方法返回的也是一个空数组。
==forEach 方法 ==
1.forEach 方法用来调用数组的每个元素,将元素传给回调函数
2.forEach 对于空数组是不会调用回调函数的。
Array.forEach(function(item,index,arr){},this)
var arr = [0,2,4,6,8];
var sum = 0;
var str = arr.forEach(function(item,index,arr){
sum += item;
console.log("sum 的值为:",sum); //0 2 6 12 20
console.log(this); //window
},this)
console.log(sum);//20
console.log(str); //undefined
无论 arr 是不是空数组,forEach 返回的都是 undefined。这个方法只是将数组中的每一项作为 callback 的参数执行一次。
==for in 和 for of 的区别 ==
遍历数组通常使用 for 循环,ES5 的话也可以使用 forEach,ES5 具有遍历数组功能的还有 map、filter、some、every、reduce、reduceRight 等,只不过他们的返回结果不一样。但是使用 foreach 遍历数组的话,使用 break 不能中断循环,使用 return 也不能返回到外层函数。
Array.prototype.method=function(){console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="数组"
for (var index in myArray) {console.log(myArray[index]);
}
使用 for in 也可以遍历数组,但是会存在以下问题:
1.index 索引为字符串型数字,不能直接进行几何运算
2. 遍历顺序有可能不是按照实际数组的内部顺序
3. 使用 for in 会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法 method 和 name 属性
所以 for in 更适合遍历对象,不要使用 for in 遍历数组。
那么除了使用 for 循环,如何更简单的正确的遍历数组达到我们的期望呢(即不遍历 method 和 name),ES6 中的 for of 更胜一筹.
Array.prototype.method=function(){console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="数组";
for (var value of myArray) {console.log(value);
}
记住,for in 遍历的是数组的索引(即键名),而 for of 遍历的是数组元素值。
for of 遍历的只是数组内的元素,而不包括数组的原型属性 method 和索引 name
遍历对象 通常用 for in 来遍历对象的键名
Object.prototype.method=function(){console.log(this);
}
var myObject={
a:1,
b:2,
c:3
}
for (var key in myObject) {console.log(key);
}
for in 可以遍历到 myObject 的原型方法 method, 如果不想遍历原型方法和属性的话,可以在循环内部判断一下,==hasOwnPropery== 方法可以判断某属性是否是该对象的实例属性
for (var key in myObject) {if(myObject.hasOwnProperty(key)){console.log(key);
}
}
同样可以通过 ES5 的 Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性。
Object.prototype.method=function(){console.log(this);
}
var myObject={
a:1,
b:2,
c:3
}
Object.keys(myObject).forEach(function(key,index){console.log(key,myObject[key])
})
== 实现 EventEmitter 方法 ==
class EventEmitter {constructor() {this.events = {};
}
on(eventName, fn) {let fnList = this.events[eventName] || [];
fnList.push(fn)
if (eventName) {this.events[eventName] = fnList;
}
}
emit(eventName, ...agr) {let funcs = this.events[eventName];
if (funcs && funcs.length) {for (let j = 0; j < funcs.length; j++) {funcs[j](...agr);
}
}
}
off(eventName, fn) {let funcs = this.events[eventName];
if (fn) {this.events[eventName].splice(fn, 1);
} else {delete this.events[eventName]
}
}
}
==let、var、const 区别 ==
var
第一个就是作用域的问题,var 不是针对一个块级作用域,而是针对一个函数作用域。举个例子:
function runTowerExperiment(tower, startTime) {
var t = startTime;
tower.on("tick", function () {... code that uses t ...});
... more code ...
}
这样是没什么问题的,因为回调函数中可以访问到变量 t,但是如果我们在回调函数中再次命名了变量 t 呢?
function runTowerExperiment(tower, startTime) {
var t = startTime;
tower.on("tick", function () {
... code that uses t ...
if (bowlingBall.altitude() <= 0) {var t = readTachymeter();
...
}
});
... more code ...
}
后者就会将前者覆盖。
第二个就是循环的问题。
看下面例子:
var messages = ["Meow!", "I'm a talking cat!","Callbacks are fun!"];
for (var i = 0; i < messages.length; i++) {setTimeout(function () {document.write(messages[i]);
},i*1500);
}
输出结果是:undefined
因为 for 循环后,i 置为 3,所以访问不到其值。
let
为了解决这些问题,ES6 提出了 let 语法。let 可以在{},if,for 里声明,其用法同 var,但是作用域限定在块级。但是 javascript 中不是没有块级作用域吗?这个我们等会讲。还有一点很重要的就是 let 定义的变量 == 不存在变量提升 ==。
变量提升
这里简单提一下什么叫做变量提升。
var v='Hello World';
(function(){alert(v);
var v='I love you';
})()
上面的代码输出结果为:undefined。
为什么会这样呢?这就是因为变量提升,变量提升就是把变量的声明提升到函数顶部,比如:
(function(){
var a='One';
var b='Two';
var c='Three';
})()
实际上就是:
(function(){
var a,b,c;
a='One';
b='Two';
c='Three';
})()
所以我们刚才的例子实际上是:
var v='Hello World';
(function(){
var v;
alert(v);
v='I love you';
})()
所以就会返回 undefined 啦。
这也是 var 的一个问题,而我们使用 let 就不会出现这个问题。因为它会报语法错误:
{console.log( a); // undefined
console.log(b); // ReferenceError!
var a;
let b;
}
再来看看 let 的块级作用域。
function getVal(boo) {if (boo) {
var val = 'red'
// ...
return val
} else {
// 这里可以访问 val
return null
}
// 这里也可以访问 val
}
而使用 let 后:
function getVal(boo) {if (boo) {
let val = 'red'
// ...
return val
} else {
// 这里访问不到 val
return null
}
// 这里也访问不到 val
}
同样的在 for 循环中:
function func(arr) {for (var i = 0; i < arr.length; i++) {// i ...}
// 这里访问得到 i
}
使用 let 后:
function func(arr) {for (let i = 0; i < arr.length; i++) {// i ...}
// 这里访问不到 i
}
也就是说,==let 只能在花括号内部起作用 ==。
const
再来说说 const,==const 代表一个值的常量索引 ==。
const aa = 11;
alert(aa) //11
aa = 22;
alert(aa) //11
但是常量的值在垃圾回收前永远不能改变,所以需要谨慎使用。
还有一条需要注意的就是和其他语言一样,== 常量的声明必须赋予初值 ==。即使我们想要一个 undefined 的常量,也需要声明:
const a = undefined;
块级作用域
最后提一下刚才说到的块级作用域。
在之前,javascript 是没有块级作用域的,我们都是通过 () 来模拟块级作用域。
(function(){// 这里是块级作用域})();
但是在 ES6 中,{}就可以直接代码块级作用域。所以 {} 内的内容是不可以在 {} 外访问得到的。
我们可以看看如下代码:
if (true) {function foo() {document.write( "1");
}
}
else {function foo() {document.write( "2");
}
}
foo(); // 2
在我们所认识的 javascript 里,这段代码的输出结果为 2。这个叫做函数声明提升,不仅仅提升了函数名,也提升了函数的定义。如果你基础不扎实的话,可以看看这篇文章:深入理解 javascript 之 IIFE
但是在 ES6 里,这段代码或抛出 ReferenceErroe 错误。因为 {} 的块级作用域,导致外面访问不到 foo(),也就是说函数声明和 let 定义变量一样,都被限制在块级作用域中了。
== 事件循环 ==
从 promise、process.nextTick、setTimeout 出发,谈谈 Event Loop 中的 Job queue
简要介绍:谈谈 promise.resove,setTimeout,setImmediate,process.nextTick 在 EvenLoop 队列中的执行顺序
1. 问题的引出
event loop 都不陌生,是指主线程从“== 任务队列 ==”中循环读取任务,比如
例 1:
setTimeout(function(){console.log(1)},0);
console.log(2)
// 输出 2,1
在上述的例子中,我们明白首先执行主线程中的同步任务,当主线程任务执行完毕后,再从 event loop 中读取任务,因此先输出 2,再输出 1。
event loop 读取任务的先后顺序,取决于任务队列(Job queue)中对于不同任务读取规则的限定。比如下面一个例子:
例 2:
setTimeout(function () {console.log(3);
}, 0);
Promise.resolve().then(function () {console.log(2);
});
console.log(1);
// 输出为 1 2 3
先输出 1,没有问题,因为是同步任务在主线程中优先执行,这里的问题是 setTimeout 和 Promise.then 任务的执行优先级是如何定义的。
2 . Job queue 中的执行顺序
在 Job queue 中的队列分为两种类型:==macro-task 和 microTask==。我们举例来看执行顺序的规定,我们设
macro-task 队列包含任务: a1, a2 , a3
micro-task 队列包含任务: b1, b2 , b3
执行顺序为,首先执行 marco-task 队列开头的任务,也就是 a1 任务,执行完毕后,在执行 micro-task 队列里的所有任务,也就是依次执行 b1, b2 , b3,执行完后清空 micro-task 中的任务,接着执行 marco-task 中的第二个任务,依次循环。
了解完了 macro-task 和 micro-task 两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以 node V8 引擎为例),在 node V8 中,这两种类型的真实任务顺序如下所示:
macro-task(宏任务)队列真实包含任务:
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task(微任务)队列真实包含任务:
process.nextTick, Promises, Object.observe, MutationObserver
由此我们得到的执行顺序应该为:
==script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering==
在 ES6 中 macro-task 队列又称为 ScriptJobs,而 micro-task 又称 PromiseJobs
3 . 真实环境中执行顺序的举例
(1)setTimeout 和 promise
例 3:
setTimeout(function () {console.log(3);
}, 0);
Promise.resolve().then(function () {console.log(2);
});
console.log(1);
我们先以第 1 小节的例子为例,这里遵循的顺序为:
script(主程序代码)——>promise——>setTimeout
对应的输出依次为:1 ——>2————>3
(2)process.nextTick 和 promise、setTimeout
例子 4:
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){console.log(2);
resolve();}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
// 输出 2,6,5,3,4,1
这个例子就比较复杂了,这里要注意的一点在定义 promise 的时候,promise 构造部分是同步执行的,这样问题就迎刃而解了。
首先分析 Job queue 的执行顺序:
==script(主程序代码)——>process.nextTick——>promise——>setTimeout==
I) 主体部分:定义 promise 的构造部分是同步的,
因此先输出 2,主体部分再输出 6(同步情况下,就是严格按照定义的先后顺序)
II)process.nextTick: 输出 5
III)promise:这里的 promise 部分,严格的说其实是 promise.then 部分,输出的是 3,4
IV) setTimeout:最后输出 1
综合的执行顺序就是:2——>6——>5——>3——>4——>1
(3)更复杂的例子
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){console.log(2);
setTimeout(function(){resolve()},0)
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
// 输出的是 2 6 5 1 3 4
种情况跟我们(2)中的例子,区别在于 promise 的构造中,没有同步的 resolve,因此 promise.then 在当前的执行队列中是不存在的,只有 promise 从 pending 转移到 resolve,才会有 then 方法,而这个 resolve 是在一个 setTimout 时间中完成的,因此 3,4 最后输出。
== 浏览器内核 ==
- IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;
- Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink 内核;
- Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;
- Safari 浏览器内核:Webkit 内核;
- Opera 浏览器内核:最初是自己的 Presto 内核,后来是 Webkit,现在是 Blink 内核;
- 360 浏览器、猎豹浏览器内核:IE+Chrome 双内核;
- 搜狗、遨游、QQ 浏览器内核:Trident(兼容模式)+Webkit(高速模式);
- 百度浏览器、世界之窗内核:IE 内核;
- 2345 浏览器内核:以前是 IE 内核,现在也是 IE+Chrome 双内核;
== 懒加载的原理 ==
- 原理:先将 img 标签中的 src 链接设为同一张图片(空白图片),将其真正的图片地址存储再 img 标签的自定义属性中(比如 data-src)。当 js 监听到该图片元素进入可视窗口时,即将自定义属性中的地址存储到 src 属性中,达到懒加载的效果。
- 这样做能防止页面一次性向服务器响应大量请求导致服务器响应慢,页面卡顿或崩溃等问题。
代码实现
既然懒加载的原理是基于判断元素是否出现在窗口可视范围内,首先我们写一个函数判断元素是否出现在可视范围内:
function isVisible($node){var winH = $(window).height(),
scrollTop = $(window).scrollTop(),
offSetTop = $(window).offSet().top;
if (offSetTop < winH + scrollTop) {return true;} else {return false;}
}
再添加上浏览器的事件监听函数,让浏览器每次滚动就检查元素是否出现在窗口可视范围内:
$(window).on("scroll", function{if (isVisible($node)){console.log(true);
}
})
我们已经很接近了,现在我们要做的是,让元素只在第一次被检查到时打印 true,之后就不再打印了
var hasShowed = false;
$(window).on("sroll",function{if (hasShowed) {return;} else {if (isVisible($node)) {
hasShowed = !hasShowed;
console.log(true);
}
}
})
咦,我们好像已经实现了懒加载。
懒加载实例
- 1. 简单懒加载:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
img{
display: block;
max-height: 300px;
}
</style>
</head>
<body>
<div class="container">
<h1> 懒加载页面 </h1>
<img src="1.png" data-src='1.jpg' alt="">
<img src="1.png" data-src='2.jpg' alt="">
<img src="1.png" data-src='3.jpg' alt="">
<img src="1.png" data-src='4.jpg' alt="">
<img src="1.png" data-src='5.jpg' alt="">
<img src="1.png" data-src='6.jpg' alt="">
<img src="1.png" data-src='7.jpg' alt="">
<img src="1.png" data-src='8.jpg' alt="">
<img src="1.png" data-src='9.jpg' alt="">
</div>
</body>
</html>
<script>
var scrollTop = window.scrollY;
var imgs = Array.from(document.querySelectorAll('img'));
lazyLoad();
window.onscroll = () => {
scrollTop = window.scrollY;
lazyLoad();}
function lazyLoad(){imgs.forEach((item,index)=>{if( item.offsetTop < window.innerHeight + scrollTop){console.log(item.offsetTop)
item.setAttribute('src',item.dataset.src)
}
})
}
</script>
这里有坑请注意!!! 而且这个问题不好百度 – . –
如果复制上面的代码, 首次加载进页面发现所有图片均已经加载完毕, 没有实现懒加载的效果
因为函数调用时 img.onload 没有完成,img 元素没有高度!!!
解决办法是在外层套一个 window.onload
window.onload = function(){lazyLoad();
}
- 2. 函数节流 throttle 懒加载
推荐!!! 高频滚动模式下, 每隔一段时间才会实现渲染~~
实现原理是 加入一个开关变量, 控制每隔固定的一段时间, 函数才可能被触发~
window.onload = function(){
var scrollTop = window.scrollY;
var imgs = Array.from(document.querySelectorAll('img'));
lazyLoad();
// 函数节流模式
var canRun = true;
window.onscroll = () => {if( !canRun){return}
canRun = false;
setTimeout(()=>{
scrollTop = window.scrollY;
lazyLoad();
canRun = true;
},1000)
}
function lazyLoad(){imgs.forEach((item,index)=>{if( item.offsetTop < window.innerHeight + scrollTop){console.log(item.offsetTop)
item.setAttribute('src',item.dataset.src)
}
})
}
}
为了逻辑清晰 , 打包成函数调用:
window.onload = function(){
var scrollTop = window.scrollY;
var imgs = Array.from(document.querySelectorAll('img'));
lazyLoad();
let canRun = true;// 开关变量用于函数节流
window.addEventListener('scroll',throttle(lazyLoad,500));
// 定义懒加载函数 , 从上到下懒加载 , 从下到上也是懒加载
function lazyLoad(){imgs.forEach((item,index)=>{if( scrollTop===0 && item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop){alert()
item.setAttribute('src',item.dataset.src)
item.setAttribute('data-src','')
}else if(item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop && item.offsetTop > scrollTop){item.setAttribute('src',item.dataset.src)
item.setAttribute('data-src','')
}
})
}
// 定义函数节流函数
function throttle(fun,delay){return function(){// fun();
if(!canRun){return}
console.log('!!!')
canRun = false;
setTimeout(()=>{
scrollTop = window.scrollY;
fun(imgs);
canRun = true
},delay)
}
}
}
- 3. 函数防抖 debounce
原理是设置 clearTimeout 和 setTimeout, 的 dalayTime 控制一个事件如果频繁触发, 将只会执行最近的一次… 可以用在用户注册时候的手机号码验证和邮箱验证。只有等用户输入完毕后,前端才需要检查格式是否正确,如果不正确,再弹出提示语。以下还是以页面元素滚动监听的例子
效果: 一直滑动的时候, 将不会有图片加载, 停下后 300ms 会加载
window.onload = function(){
var scrollTop = window.scrollY;
var imgs = Array.from(document.querySelectorAll('img'));
lazyLoad();
// 函数防抖模式
var timer = null;
window.onscroll = () => {clearTimeout(timer);
timer = setTimeout(()=>{
scrollTop = window.scrollY;
lazyLoad();},300)
}
function lazyLoad(){imgs.forEach((item,index)=>{if( item.offsetTop < window.innerHeight + scrollTop){console.log(item.offsetTop)
item.setAttribute('src',item.dataset.src)
}
})
}
}
- 4. 最终版 throttle + debounce
完美懒加载
注意点: 在滚动条下拉状态下刷新页面, 页面实现更新渲染之后会立马触发滚动条事件, 回到上一次页面的停留点, 但是并不是从 scrollTop 为 0 的位置出发~
window.onload = function(){
var scrollTop = window.scrollY;
var imgs = Array.from(document.querySelectorAll('img'));
lazyLoad();
// 采用了节流函数
window.addEventListener('scroll',throttle(lazyLoad,500,1000));
function throttle(fun, delay, time) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
console.log(curTime - startTime)
if (curTime - startTime >= time) {fun();
startTime = curTime;
// 没达到触发间隔,重新设定定时器
} else {timeout = setTimeout(fun, delay);
}
};
};
// 实际想绑定在 scroll 事件上的 handler
// 需要访问到 imgs , scroll
function lazyLoad(){
scrollTop = window.scrollY;
imgs.forEach((item,index)=>{if( scrollTop===0 && item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop){// alert()
item.setAttribute('src',item.dataset.src)
item.setAttribute('data-src','')
}else if(item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop && item.offsetTop > scrollTop){item.setAttribute('src',item.dataset.src)
item.setAttribute('data-src','')
}
})
}
}
==typeof 和 instanceof==
ECMAScript 是松散类型的,一次需要一种手段来检测给定变量的数据类型,typeof 操作符(注意不是函数哈!)就是负责提供这方面信息的,
typeof 可以用于检测基本数据类型和引用数据类型。
语法格式如下:
typeof variable
返回 6 种 String 类型的结果:
- “undefined” – 如果这个值未定义
- “boolean” – 如果这个值是布尔值
- “string” – 如果这个值是字符串
- “number” – 如果这个值是数值
- “object” – 如果这个值是对象或 null
- “function” – 如果这个值是函数
示例:
console.log(typeof 'hello'); // "string"
console.log(typeof null); // "object"
console.log(typeof (new Object())); // "object"
console.log(typeof(function(){})); // "function"
typeof 主要用于检测基本数据类型:数值、字符串、布尔值、undefined,因为 typeof 用于检测引用类型值时,== 对于任何 Object 对象实例(包括 null),typeof 都返回 ”object” 值,没办法区分是那种对象,对实际编码用处不大。==
instanceof 用于判断一个变量是否某个对象的实例
在检测基本数据类型时 typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大,通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。此时我们可以使用 ECMAScript 提供的 instanceof 操作符。
语法格式如下:
result = variable instanceof constructor
返回布尔类型值:
- true – 如果变量(variable)是给定引用类型的实例,那么 instanceof 操作符会返回 true
- false – 如果变量(variable)不是给定引用类型的实例,那么 instanceof 操作符会返回 false
示例:
function Person(){}
function Animal(){}
var person1 = new Person();
var animal1 = new Animal();
console.log(person1 instanceof Person); // true
console.log(animal1 instanceof Person); // false
console.log(animal1 instanceof Object); // true
console.log(1 instanceof Person); //false
var oStr = new String("hello world");
console.log(typeof(oStr)); // object
console.log(oStr instanceof String);
console.log(oStr instanceof Object);
// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo);
// instanceof 在继承中关系中的用法
console.log('instanceof 在继承中关系中的用法');
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();
var fo = new Foo();
console.log(fo instanceof Foo);
console.log(fo instanceof Aoo)
根据规定,所有引用类型的值都是 Object 的实例。因此,在检测一个引用类型值和 Object 构造函数时,instanceof 操作符会始终返回 true。如果使用 instanceof 操作符检测基本类型值时,该操作符会始终返回 false,因为基本类型不是对象。
console.log(Object.prototype.toString.call(null));
// [object Null]
undefined
console.log(Object.prototype.toString.call([1,2,3]));
//[object Array]
undefined
console.log(Object.prototype.toString.call({}));
// [object Object]
== 原型和原型链 ==
参考 js 高级教程
== 数组的使用方法 ==
push、pop、shift、unshift、splice、join、reverse、sort、slice、map every some fliter forEach、reduce…..
作为最常用的类型,JavaScript 中的数组还是和其他语言中有很大的区别的。
主要体现在两点:
- 数组中的每一项都可以保存任何类型的数据
- 数组的大小可以动态调整
首先来介绍创建数组的两种方法
- 第一种方式
var arr1 = new Array();
var arr2 = new Array(3);
var arr3 = new Array('jerry');
可以看到这种方式建立数组,arr1 是一个空数组,arr2 是一个长度为 3 的数组,arr3 是一个包含‘jerry’一个元素的数组。同时通过这种方式创建的数组,new 操作符可以省略。
- 第二种方式称为数组字面量表示法。
var a = [];
var arr = ['tom','jack']
数组的长度是可动态调整,导致我们直接就可以设置它的长度
var a = [123,423];
a.length = 10;
a[9]='123';
console.log(a[8])//undefined
a[10] = '123'
console.log(a.length)//10
从上面的代码中我们可以看出:
- 如果我们设置的长度大于原来的数组的长度的时候,数组后面的元素自动设置为 undefined。
- 如果我们对大于当前数组长度的位置赋值的时候,那么就会导致数组的长度自动变为你所赋值位置 +1.
改变数组的方法
栈方法
pop 和 push 很简单,也很容易理解。pop 就是从数组的末尾删除一个元素并返回。push 是在数组的末尾添加一个元素。
var arr = [1,3,4];
arr.pop();
console.log(arr);//[1,3]
arr.push(5);
console.log(arr);//[1,3,5]
队列方法
shift 和 unshift 是和栈方法是相对的,它俩是从数组的头部进行操作。shift 是从头部删除一个元素,unshift 是从同步加入一个元素。
var arr = [1,3,4];
arr.shift();
console.log(arr);//[3,4]
arr.unshift(5);
console.log(arr);//[5,3,4]
重排序方法
reverse 是对数组进行翻转。
var arr = [1,3,4];
arr.reverse();
console.log(arr);//[4,3,1]
sort 是对数组进行排序。
var arr = [1,3,5,4];
arr.sort();
console.log(arr);//[1,3,4,5];
sort 默认的对数组进行升序排序。sort 可以接收一个自定义的比较函数,自定义排序规则。
sort 方法会调用每个元素的 toString()方法,从而通过字符串进行比较大小。即使是数值,依然要变换成字符串,从而就会带来一些问题。比如
var arr = [1,3,15,4];
arr.sort()
console.log(arr);//[1,15,3,4];
转换为字符串之后,‘15’是排在‘3’,‘4’的前面的。这就带来了问题,所以在进行数值数组的排序,必须进行自定义排序规则。
var arr = [1,3,15,4];
function compare(v1,v2){if(v1 > v2)
return 1;
if(v1 < v2)
return -1;
return 0;
}
arr.sort(compare)
console.log(arr);//[1,3,4,15]
splice 方法
splice 方法可以说是数组中功能最强大的方法,集多项功能于一身。主要的用途就是用来向数组的中部插入元素。
splice 方法主要有三种用法。
splice 的返回值为删除的元素组成的数组。如果删除的元素为空,返回空数组。
- 删除元素
splice(index,count),index 表示删除的位置,count 表示删除的项数。
var arr = [1,3,4];
console.log(arr.splice(2,1));//[4]
// 删除元素
console.log(arr);[1,3];
- 插入元素
splice(index,0,element,….)
index 表示要插入的位置,0 代表删除 0 个元素,element 要插入的元素, 如果要插入多个元素,可以继续添加。
var arr = [1,3,4];
console.log(arr.splice(2,0,'tom'));//[ ]
console.log(arr);//[1,3,'tom',4]
如果 index 的值大于数组本身的长度,那么就在最后位置添加。且数组的长度只会加 1.
var arr = [1,3,4];
console.log(arr.splice(5,0,'tom'));//[ ]
console.log(arr);//[1,3,4,'tom']
console.log(arr.length);//4
如果 index 的值为负数, 那么就从(arr.length+index)位置开始插入,如果(arr.length+index)的值小于 0,那么就从数组的开始位置进行插入。
var arr = [1,3,4,4,7,6];
console.log(arr.splice(-1,0,'tom'));//[ ]
console.log(arr);//[1,3,4,4,7,'tom',6]
console.log(arr.length);//7
console.log(arr.splice(-7,0,'tom'));//[ ]
console.log(arr);//['tom',1,3,4,4,7,'tom',6]
console.log(arr.length);//8
console.log(arr.splice(-10,0,'jack'));//[ ]
console.log(arr);//['jack','tom',1,3,4,4,7,'tom',6]
console.log(arr.length);//9
- 替换元素
splice(index,count,element,….).index 代表替换开始的位置,count > 0,element 表示要替换成的元素。其实替换过程包含两个过程:1. 删除. 2 插入. 也就是上面的两个过程的融合。
var arr = [1,3,4];
console.log(arr.splice(1,1,'tom'));//[3]
console.log(arr);//[1,'tom',4]
如果 index 大于数组的长度,或者小于 0,处理的结果同上面插入元素处理的方式一样。
不改变数组的方法
转换方法
join 方法主要是用来将数组的元素通过规定的方式连接成字符串。
var arr = [1,3,4,5];
console.log(arr.join(','))//1,3,4,5
console.log(arr.join('+'))//1+3+4+5
console.log(arr.join('?'))//1?3?4?5
console.log(arr)//[1,3,4,5]
操作方法
slice 和 concat 方法。
slice 方法主要用来返回指定位置的数组的子数组。slice(start,end)。end 省略,返回的是从开始位置到数组的末尾。end 不省略,返回的是从 start 到 end 之间的子数组,包括 start 位置但不包括 end 位置的数组。
var arr = [1,3,4,5];
console.log(arr.slice(1));//[3,4,5]
console.log(arr.slice(1,2));//[3]
如果 slice 方法的参数中有一个负数,则用数组长度加上该数来确定相应的位置。例如在一个长度为 5 的数组上调用 slice(-2,-1)与调用 slice(3,4)得到的结果相同。如果结束位置小于起始位置,则返回空数组。
concat 方法,主要是连接多个数组。
var arr = [1,3,4,5];
var testArr = [1,23,4];
console.log(arr.concat(testArr));//[1,3,4,5,1,23,4]
console.log(arr.concat('tom'));//[1,3,4,5,'tom']
迭代方法
ES5 新增加的迭代方法主要包括如下几种
map
every
some
fliter
forEach
这几个方法有一下共同点,都接收两个参数,一个是要在数组上每一项运行的函数,一个是运行该函数作用域的对象,改变 this 的指向(可选)。其中函数需要传入三个参数,一个是每个元素的值,每个元素的 index,数组本身。
function(value,index,array)
{}
下面一个一个的来介绍
- map
map 返回数组中每一个数组元素经过传入的函数处理后组成的新数组
var arr = [1,3,4];
var newArr = arr.map(function(value,index,array){return value*2;})
console.log(newArr);//[2,6,8]
console.log(arr);//[1,3,4]
- some 和 every
some 和 every 比较相像。some 是对每一个数组中的元素运行传入的函数,如果有一个返回 true,那么就返回 true;every 是对每一个数组中的元素运行传入的函数,如果所有的都返回 true,那么就返回 true。
var arr = [1,3,4];
var result1 = arr.some(function(value,index,array){return value > 2;})
var result2 = arr.every(function(value,index,array){return value > 2;})
console.log(result1);// true
console.log(result2);// false
- filter
从名字可以看出,这是一个过滤的方法,返回的一个数组,这个数组是满足传入的参数的函数的元素所组成的。
var arr = [1,3,4];
var result = arr.filter(function(value,index,array){return value > 2;})
console.log(result);// [3,4]
- forEach
forEach 主要用来遍历,遍历数组中每一个元素,对其进行操作。该方法没有返回值。
var arr = [1,3,4];
arr.forEach(function(value,index,array){console.log('arr['+index+']='+value);
})
// 结果
arr[0]=1
arr[1]=3
arr[2]=4
缩小方法
reduce 和 reduceRight. 这两个方法接收两个参数,一个是每项都运行的函数,一个是缩小基础的初始值(可选)。reduce 和 reduceRight 返回的是一个值。其中每项都运行的函数包含四个参数,
funciton(prev,cur,index,array){}
下面通过一个例子就可以说明这个函数是干嘛的。
var arr = [1,3,4];
var result = arr.reduce(function(prev,cur,index,array){return prev+cur;},10);
console.log(result)//18
var result1 = arr.reduce(function(prev,cur,index,array){return prev+cur;});
console.log(result1)//8
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
参数 描述
function(total,currentValue, index,arr) 必需。用于执行每个数组元素的函数。total 必需。初始值, 或者计算结束后的返回值。currentValue 必需。当前元素
currentIndex 可选。当前元素的索引
arr 可选。当前元素所属的数组对象。initialValue 可选。传递给函数的初始值
var arr = [11,22,33,44,55,66];
var arr1 = arr.reduce(function(total, currentValue, currentIndex, arr){return total+"-"+currentValue;},"00");
console.log(arr1);//00-11-22-33-44-55-66
var arr = [11,22,33,44,55,66];
var arr1 = arr.reduceRight(function(total, currentValue, currentIndex, arr){return total+"-"+currentValue;},"00");
console.log(arr1);//00-66-55-44-33-22-11
reduceRight 和 reduce 一样,无非他开始的位置是从数组的后面。
其他方法
- indexOf()
- lastIndexOf()
这两个主要是用来判断元素在数组中的位置, 未找到返回 -1,接收两个参数,indexOf(searchElement[, fromIndex]),lastIndexOf(searchElement[, fromIndex])。fromIndex 可选。其中 formIndex 也可以指定字符串。
var arr = [1,3,4,4,1,5,1];
var value = arr.indexOf(1)
console.log(value)//0
value = arr.indexOf(1,4)
console.log(value)//4
value = arr.indexOf(1,5)
console.log(value)//6
value = arr.lastIndexOf(1)
console.log(value)//6
value = arr.lastIndexOf(1,3)
console.log(value)//0
- toString()
- toLocalString()
- valueOf()
这三个方法是所有对象都具有的方法。
toString()返回的是一个字符串,toLocaleString 同它类似。valueOf()返回的是一个数组
var arr= [1,3,4]
console.log(arr.toString());//1,3,4
console.log(arr.valueOf());//[1,3,4]
console.log(arr.toLocaleString());//1,3,4
可以复写 toString(),toLocaleString()返回不同的结果。
==callee 和 caller==
1:caller 返回一个调用当前函数的引用 如果是由顶层调用的话 则返回 null
(举个栗子哈 ==caller 给你打电话的人 == 谁给你打电话了 谁调用了你 很显然是下面 a 函数的执行 只有在打电话的时候你才能知道打电话的人是谁 所以对于函数来说 只有 caller 在函数执行的时候才存在)
var callerTest = function() {console.log(callerTest.caller) ;
} ;
function a() {callerTest() ;
}
a() ;// 输出 function a() {callerTest();}
callerTest() ;// 输出 null
2:callee 返回一个正在被执行函数的引用(这里常用来递归匿名函数本身 但是在严格模式下不可行)
callee 是 arguments 对象的一个成员 表示对函数对象本身的引用 它有 == 个 length 属性(代表形参的长度)==
var c = function(x,y) {console.log(arguments.length,arguments.callee.length,arguments.callee)
} ;
c(1,2,3) ;// 输出 3 2 function(x,y) {console.log(arguments.length,arguments.callee.length,arguments.callee)}
==new 的执行原理 ==
1、创建一个新对象;[var o = new Object();]
2、将构造函数的作用域赋给新对象(因此 this 指向了这个新对象);
3、执行构造函数中的代码(为这个新对象添加属性);
4、返回新对象。
==get 和 post 的区别 ==
get 请求传参长度的误区
误区:我们经常说 get 请求参数的大小存在限制,而 post 请求的参数大小是无限制的。
实际上 HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对 get 请求参数的限制是来源与浏览器或 web 服务器,浏览器或 web 服务器限制了 url 的长度。为了明确这个概念,我们必须再次强调下面几点:
HTTP 协议 未规定 GET 和 POST 的长度限制
GET 的最大长度显示是因为 浏览器和 web 服务器限制了 URI 的长度
不同的浏览器和 WEB 服务器,限制的最大长度不一样
要支持 IE,则最大长度为 2083byte,若只支持 Chrome,则最大长度 8182byte
补充 get 和 post 请求在缓存方面的区别
补充补充一个 get 和 post 在缓存方面的区别:
- get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
- post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此 get 请求适合于请求缓存。
HTTP 协议中 GET 和 POST 到底有哪些区别
HTTP 定义了与服务器交互的不同方法,最常用的有 4 种,Get、Post、Put、Delete, 如果我换一下顺序就好记了,Put(增),Delete(删),Post(改),Get(查),即增删改查,下面简单叙述一下:
- 1)Get,它用于获取信息,注意,他只是获取、查询数据,也就是说它不会修改服务器上的数据,从这点来讲,它是数据安全的,而稍后会提到的 Post 它是可以修改数据的,所以这也是两者差别之一了。
- 2)Post,它是可以向服务器发送修改请求,从而修改服务器的,比方说,我们要在论坛上回贴、在博客上评论,这就要用到 Post 了,当然它也是可以仅仅获取数据的。
- 3)Delete 删除数据。可以通过 Get/Post 来实现。用的不多,暂不多写,以后扩充。
- 4)Put,增加、放置数据,可以通过 Get/Post 来实现。用的不多,暂不多写,以后扩充。
下面简述一下 Get 和 Post 区别:
1)GET 请求的数据是放在 HTTP 包头中的,也就是 URL 之后,通常是像下面这样定义格式的,(而 Post 是把提交的数据放在 HTTP 正文中的)。
login.action?name=hyddd&password=idontknow&verify=%E4%BD%E5%A5%BD
- a,以?来分隔 URL 和数据;
- b,以 & 来分隔参数;
- c,如果数据是英文或数字,原样发送;
- d,如果数据是中文或其它字符,则进行 BASE64 编码。
2)GET 提交的数据比较少,最多 1024B,因为 GET 数据是附在 URL 之后的,而 URL 则会受到不同环境的限制的,比如说 IE 对其限制为 2K+35,而 POST 可以传送更多的数据(理论上是没有限制的,但一般也会受不同的环境,如浏览器、操作系统、服务器处理能力等限制,IIS4 可支持 80KB,IIS5 可支持 100KB)。
3)Post 的安全性要比 Get 高,因为 Get 时,参数数据是明文传输的,而且使用 GET 的话,还可能造成 Cross-site request forgery 攻击。而 POST 数据则可以加密的,但 GET 的速度可能会快些。
所以综上几点,总结成下表:
操作方式 | 数据位置 | 明文密文 | 数据安全 | 长度限制 | 应用场景 |
---|---|---|---|---|---|
GET | http 包头 | 明文 | 不安全 | 长度较小 | 查询数据 |
POST | http 正文 | 可明可密 | 安全 | 支持较大的数据传输 | 修改数据 |
== 点击 li 能够实现弹出当前 li 索引 ==
经典的 js 问题 实现点击 li 能够弹出当前 li 索引与 innerHTML 的函数
按照我们平常的想法,代码应该是这样写的:
var myul = document.getElementsByTagName("ul")[0];
var list = myul.getElementsByTagName("li");
function foo(){for(var i = 0, len = list.length; i < len; i++){list[i].onclick = function(){alert(i + "----" + this.innerHTML);
}
}
}
foo();
但是不巧的是产生的结果是这样的:
索引 index 为什么总是 4 呢,这是 js 中没有块级作用域导致的。这里有三种解决思路
- 使用闭包
<script type="text/javascript">
var myul = document.getElementsByTagName("ul")[0];
var list = myul.getElementsByTagName("li");
function foo(){for(var i = 0, len = list.length; i < len; i++){var that = list[i];
list[i].onclick = (function(k){
var info = that.innerHTML;
return function(){alert(k + "----" + info);
};
})(i);
}
}
foo();
</script>
2. 使用 ES6 中的新特性 let 来声明变量
用 let 来声明的变量将具有块级作用域,很明显可以达到要求,不过需要注意的是得加个 ’use strict’(使用严格模式)才会生效
<script type="text/javascript">
var myul = document.getElementsByTagName("ul")[0];
var list = myul.getElementsByTagName("li");
function foo(){'use strict'
for(let i = 0, len = list.length; i < len; i++){list[i].onclick = function(){alert(i + "----" + this.innerHTML);
}
}
}
foo();
</script>
3. 事件委托
<script type="text/javascript">
var myul = document.querySelector('ul');
var list = document.querySelectorAll('ul li');
myul.addEventListener('click', function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElemnt;
for(var i = 0, len = list.length; i < len; i++){if(list[i] == target){alert(i + "----" + target.innerHTML);
}
}
});
</script>
4. 引入 jquery, 使用其中的 on 或 delegate 进行事件绑定(它们都有事件代理的特性)
<script type=”text/javascript” src=”jquery-1.8.2.min.js”></script>
<script type="text/javascript">
$("ul").delegate("li", "click", function(){var index = $(this).index();
var info = $(this).html();
alert(index + "----" + info);
});
</script>
<script type="text/javascript">
$("ul").on("click", "li", function(){var index = $(this).index();
var info = $(this).html();
alert(index + "----" + info);
});
</script>
==js 添加事件 - 兼容各种环境 ==
JS 中添加事件 兼容各种环境
var EventUtil = {
// 添加
addHandler : function (element , type, handler {if ( element.addEventListener){element.addEventListener(type, handler, false);
}else if (element.attachEvent) {element.attachEvent("on"+type,handler);
}else {element["on" + type] = handler;
}
},
// 移除
removeHandler : function (element , type , handler){if(element.removeEventListener){element.removeEventListener(type , handler , false);
}else if(element.detachEvent){element.detachEvent("on" + type , handler);
}else{element["on" + type] = handler;
}
}
}
==this 指向的问题 ==
首先必须要说的是,this 的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定 this 到底指向谁,实际上 this 的最终指向的是 == 那个调用它的对象 ==(这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解 this 的时候会有种琢磨不透的感觉),那么接下来我会深入的探讨这个问题。
例子 1:
function a(){
var user = "追梦子";
console.log(this.user); //undefined
console.log(this); //Window
}
a();
按照我们上面说的 this 最终指向的是调用它的对象,这里的函数 a 实际是被 Window 对象所点出来的,下面的代码就可以证明。
function a(){
var user = "追梦子";
console.log(this.user); //undefined
console.log(this); //Window
}
window.a();
和上面代码一样吧,其实 alert 也是 window 的一个属性,也是 window 点出来的。
例子 2:
var o = {
user:"追梦子",
fn:function(){console.log(this.user); // 追梦子
}
}
o.fn();
这里的 this 指向的是对象 o,因为你调用这个 fn 是通过 o.fn()执行的,那自然指向就是对象 o,这里再次强调一点,this 的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。
其实例子 1 和例子 2 说的并不够准确,下面这个例子就可以推翻上面的理论。
如果要彻底的搞懂 this 必须看接下来的几个例子
例子 3:
var o = {
user:"追梦子",
fn:function(){console.log(this.user); // 追梦子
}
}
window.o.fn();
这段代码和上面的那段代码几乎是一样的,但是这里的 this 为什么不是指向 window,如果按照上面的理论,最终 this 指向的是调用它的对象,这里先说个而外话,window 是 js 中的全局对象,我们创建的变量实际上是给 window 添加属性,所以这里可以用 window 点 o 对象。
这里先不解释为什么上面的那段代码 this 为什么没有指向 window,我们再来看一段代码。
var o = {
a:10,
b:{
a:12,
fn:function(){console.log(this.a); //12
}
}
}
o.b.fn();
这里同样也是对象 o 点出来的,但是同样 this 并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解 this 的指向的问题。
- 情况 1:如果一个函数中有 this,但是它没有被上一级的对象所调用,那么 this 指向的就是 window,这里需要说明的是在 js 的严格版中 this 指向的不是 window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
- 情况 2:如果一个函数中有 this,这个函数有被上一级的对象所调用,那么 this 指向的就是上一级的对象。
- 情况 3:如果一个函数中有 this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this 指向的也只是它上一级的对象,例子 3 可以证明,如果不相信,那么接下来我们继续看几个例子。
var o = {
a:10,
b:{
// a:12,
fn:function(){console.log(this.a); //undefined
}
}
}
o.b.fn();
尽管对象 b 中没有属性 a,这个 this 指向的也是对象 b,因为 this 只会指向它的上一级对象,不管这个对象中有没有 this 要的东西。
还有一种比较特殊的情况,例子 4:
var o = {
a:10,
b:{
a:12,
fn:function(){console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
这里 this 指向的是 window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。
this 永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子 4 中虽然函数 fn 是被对象 b 所引用,但是在将 fn 赋值给变量 j 的时候并没有执行所以最终指向的是 window,这和例子 3 是不一样的,例子 3 是直接执行了 fn。
this 讲来讲去其实就是那么一回事,只不过在不同的情况下指向的会有些不同,上面的总结每个地方都有些小错误,也不能说是错误,而是在不同环境下情况就会有不同,所以我也没有办法一次解释清楚,只能你慢慢地的去体会。
构造函数版 this:
function Fn(){this.user = "追梦子";}
var a = new Fn();
console.log(a.user); // 追梦子
这里之所以对象 a 可以点出函数 Fn 里面的 user 是因为 new 关键字可以改变 this 的指向,将这个 this 指向对象 a,为什么我说 a 是对象,因为用了 new 关键字就是创建一个对象实例,理解这句话可以想想我们的例子 3,我们这里用变量 a 创建了一个 Fn 的实例(相当于复制了一份 Fn 到对象 a 里面),此时仅仅只是创建,并没有执行,而调用这个函数 Fn 的是对象 a,那么 this 指向的自然是对象 a,那么为什么对象 a 中会有 user,因为你已经复制了一份 Fn 函数到对象 a 中,用了 new 关键字就等同于复制了一份。
除了上面的这些以外,我们还可以自行改变 this 的指向,关于自行改变 this 的指向请看 JavaScript 中 call,apply,bind 方法的总结这篇文章,详细的说明了我们如何手动更改 this 的指向。
更新一个小问题当 this 碰到 return 时
function fn()
{
this.user = '追梦子';
return {};}
var a = new fn;
console.log(a.user); //undefined
再看一个
function fn()
{
this.user = '追梦子';
return function(){};
}
var a = new fn;
console.log(a.user); //undefined
再来
function fn()
{
this.user = '追梦子';
return 1;
}
var a = new fn;
console.log(a.user); // 追梦子
function fn()
{
this.user = '追梦子';
return undefined;
}
var a = new fn;
console.log(a.user); // 追梦子
什么意思呢?
== 如果返回值是一个对象,那么 this 指向的就是那个返回的对象,如果返回值不是一个对象那么 this 还是指向函数的实例。==
function fn()
{
this.user = '追梦子';
return undefined;
}
var a = new fn;
console.log(a); //fn {user: "追梦子"}
还有一点就是虽然 null 也是对象,但是在这里 this 还是指向那个函数的实例,因为 null 比较特殊。
function fn()
{
this.user = '追梦子';
return null;
}
var a = new fn;
console.log(a.user); // 追梦子
知识点补充:
1. 在严格版中的默认的 this 不再是 window,而是 undefined。
2.new 操作符会改变函数 this 的指向问题,虽然我们上面讲解过了,但是并没有深入的讨论这个问题,网上也很少说,所以在这里有必要说一下。
function fn(){this.num = 1;}
var a = new fn();
console.log(a.num); //1
为什么 this 会指向 a?首先 new 关键字会创建一个空的对象,然后会自动调用一个函数 apply 方法,将 this 指向这个空对象,这样的话函数内部的 this 就会被这个空的对象替代。
2017-09-15 11:49:14
注意: 当你 new 一个空对象的时候,js 内部的实现并不一定是用的 apply 方法来改变 this 指向的, 这里我只是打个比方而已.
if (this === 动态的可改变的) return true;
==tcp 三次握手 ==
第一次
第一次握手:建立连接时,客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SENT 状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次
第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
第三次
第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED(TCP 连接成功)状态,完成三次握手。
== 数组去重 ==
* 数组去重【Array 原型扩展一个方法实现数组去重】,利用 hash 对象
1. 遍历,数组下标去重法
时间复杂度:O(n^2),indexOf 本身也消耗了 O(n)的复杂度,空间复杂度:O(n)
IE8 以下不支持 indexOf
Array.prototype.removeRepeat1 = function() {var res =[this[0]];
for(var i=1; i<this.length;i++){ // 从第二项开始遍历
if(this.indexOf(this[i])==i){res.push(this[i]);
}
}
return res;
};
2. 遍历,比较备用数组去重法
Array.prototype.removeRepeat2 = function() {var res =[];
for(var i=0; i<this.length;i++){if(res.indexOf(this[i])==-1){res.push(this[i]);
}
}
return res;
};
3. 遍历,hash 去重法
类似于,利用对象的属性不能相同的特点进行去重
时间复杂度:O(n),空间复杂度:O(n)
Array.prototype.removeRepeat3 = function() {var h= {}; // 哈希表
var res = [];
for(var i=0; i<this.length;i++){if(!h[this[i]]){ // 如果 hash 表中没有当前项
h[this[i]]=true; // 存入 hash 表
res.push(this[i]);
}
}
return res;
};
4. 遍历,Set 去重法(ES6 的 Set)
时间复杂度:O(n),空间复杂度:O(n)
Set 兼容性不好,IE11 以下不支持
Array.prototype.removeRepeat4 = function(){var result = new Set();
for(var i=0; i<this.length; i++){result.add(this[i]);
}
return result;
}
//Set 的方法二:Array.from(array)把 Set 转化为数组
Array.prototype.removeRepeat41 = function(){return Array.from(new Set(this));;
}
5. 排序后相邻去重法
Array.prototype.removeRepeat5 = function() {this.sort();
var res=[this[0]];
for(var i = 1; i< this.length; i++){if(this[i]!=this[i-1]){res.push(this[i]);
}
}
return res;
}
==apply、call、bind 区别 ==
call apply bind
第一个传的参数都是对象,不能传入构造函数,构造函数的 typeof 是 function
传 null
或undefined
时,将是 JS 执行环境的全局变量。浏览器中是 window,其它环境(如 node)则是 global
call 方法
语法:call(thisObj,Object)
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
apply 方法
语法:apply(thisObj,[argArray])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj,并且无法被传递任何参数。
- 相同点
调用函数时,改变当前传入的对象为函数中 this 指针的引用
当第一个参数 thisObj 传入 null/undefined 的时候将执行 js 全局对象浏览器中是 window,其他环境是 global。
- 不同点:
call, apply 方法区别是, 从第二个参数起, call 方法参数将依次传递给借用的方法作参数, 而 apply 直接将这些参数放到一个数组中再传递, 最后借用方法的参数列表是一样的.
bind 相同点和 call apply 相同
而 bind 是返回一个新函数,这个函数的上下文,为传入的对象。需要再次调用才能时候用.
apply、call、bind 比较
那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个栗子:
ar obj = {x: 81,};
var foo = {getX: function() {return this.x;}
}
console.log(foo.getX.bind(obj)()); //81
console.log(foo.getX.call(obj)); //81
console.log(foo.getX.apply(obj)); //81
三个输出的都是 81,但是注意看使用 bind() 方法的,他后面多了对括号。
也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。
== 再总结一下 ==:
- apply、call、bind 三者都是用来改变函数的 this 对象的指向的;
- apply、call、bind 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文;
- apply、call、bind 三者都可以利用后续参数传参;
- bind 是返回对应函数,便于稍后调用;apply、call 则是立即调用。
== 深拷贝和浅拷贝 ==
JavaScript 有两种数据类型,基础数据类型和引用数据类型。基础数据类型都是按值访问的,我们可以直接操作保存在变量中的实际的值。而引用类型如 Array,我们不能直接操作对象的堆内存空间。引用类型的值都是按引用访问的,即保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。
一、深拷贝和浅拷贝的区别
- 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
- 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。
var a = 25;
var b = a;
b = 10;
console.log(a);//25
console.log(b);//10
// 浅拷贝
var obj1 = {a: 10, b: 20, c: 30};
var obj2 = obj1;
obj2.b = 40;
console.log(obj1);// {a: 10, b: 40, c: 30}
console.log(obj2);// {a: 10, b: 40, c: 30}
// 深拷贝
var obj1 = {a: 10, b: 20, c: 30};
var obj2 = {a: obj1.a, b: obj1.b, c: obj1.c};
obj2.b = 40;
console.log(obj1);// {a: 10, b: 20, c: 30}
console.log(obj2);// {a: 10, b: 40, c: 30}
二、浅拷贝的实现
var json1 = {"a":"name","arr1":[1,2,3]}
function copy(obj1) {var obj2 = {};
for (var i in obj1) {obj2[i] = obj1[i];
}
return obj2;
}
var json2 = copy(json1);
json1.arr1.push(4);
alert(json1.arr1); //1234
alert(json2.arr1) //1234
三、深拷贝的实现
- 1、Object.assign()
let foo = {
a: 1,
b: 2,
c: {d: 1,}
}
let bar = {};
Object.assign(bar, foo);
foo.a++;
foo.a === 2 //true
bar.a === 1 //true
foo.c.d++;
foo.c.d === 2 //true
bar.c.d === 1 //false
bar.c.d === 2 //true
Object.assign()是一种可以对 == 非嵌套对象 == 进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝。
- 2、转成 JSON
用 JSON.stringify 把对象转成字符串,再用 JSON.parse 把字符串转成新的对象。
var obj1 = {body: { a: 10} };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1); // {body: { a: 10} }
console.log(obj2); // {body: { a: 20} }
console.log(obj1 === obj2); // false
console.log(obj1.body === obj2.body); // false
但这种方法的缺陷是会 == 破坏原型链 ==,并且无法拷贝属性值为 function 的属性
- 3、递归
采用递归的方法去复制拷贝对象
var json1={
"name":"shauna",
"age":18,
"arr1":[1,2,3,4,5],
"string":'got7',
"arr2":[1,2,3,4,5],
"arr3":[{"name1":"shauna"},{"job":"web"}]
};
var json2={};
function copy(obj1,obj2){var obj2=obj2||{};
for(var name in obj1){if(typeof obj1[name] === "object"){obj2[name]= (obj1[name].constructor===Array)?[]:{};
copy(obj1[name],obj2[name]);
}else{obj2[name]=obj1[name];
}
}
return obj2;
}
json2=copy(json1,json2)
json1.arr1.push(6);
alert(json1.arr1); //123456
alert(json2.arr1); //12345
== 实现一个 bind 方法 ==
Function.prototype.mybind = function(context) {
var self = this;
var args = [];// 保存 bind 函数调用时传递的参数
for(var i = 1, len = arguments.length; i< len;i ++) {args.push(arguments[i]);
}
//bind()方法返回值是一个函数
return function() {
// 哇,新创建的函数传进来的参数可以在这里拿到哎!!var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs))
}
}
==ajax==
代码
var xmlhttp;
if (window.XMLHttpRequest){
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();}
else{
// IE6, IE5 浏览器执行代码
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("GET","/try/ajax/ajax_info.txt",true);
xmlhttp.send();
xmlhttp.onreadystatechange=function()
{if (xmlhttp.readyState==4 && xmlhttp.status==200)
{document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
}
优缺点:
-
优点
- 通过异步模式,提升用户体验
- 优化了浏览器和服务器之间的传输,减少了不必要的数据往返,减少了带宽的使用
- Ajax 在客户端运行,承担了一部分本来由服务器承担的工作,减少了大量用户下的服务器负载
- Ajax 是通过异步通信实现的局部刷新
-
缺点
- ajax 不支持浏览器的 back 按钮
- 安全问题,Ajax 暴露了与服务器交互的细节
- 对搜索引擎的支持比较弱
- 破坏了程序的异常机制
- 不容易调试
== 跨域 ==
一 跨域的原因
很多朋友不知道为什么要跨域 其实跨域请求存在的原因:由于浏览器的同源策略,即属于不同域的页面之间不能相互访问各自的页面内容。
那么什么是 == 同源策略 == 呢?
简单说来就是同协议,同域名,同端口。
二 跨域的场景
1. 域名不同 www.yangwei.com 和 www.wuyu.com 即为不同的域名)
2. 二级域名相同,子域名不同(www.wuhan.yangwei.com www.shenzheng.yangwei.com 为子域不同)
3. 端口不同,协议不同(http://www.yangwei.com 和 https://www.yangwei.com 属于跨 …:8888 和 www.yangwei.con:8080)
三 跨域的方式
1. 前端的方式: possMessage,window.name,document.domain,image.src(得不到数据返回),jsonP(script.src 后台不配合得不到数据返回),style.href(得不到数据返回)
一.==imge.src==,==script.src==,==style.href== 不受同源策略的影响可以加载其他域的资源,可以用这个特性,向服务器发送数据。最常用的就是使用 image.src 向服务器发送前端的错误信息。image.src 和 style.href 是无法获取服务器的数据返回的,script.src 服务器端配合可以得到数据返回。
二 possMessage,window.name,document.domain 是两个窗口直接相互传递数据。
(1)possMessage 是 HTML5 中新增的,使用限制是 必须获得窗口的 window 引用。IE8+ 支持,firefox,chrome,safair,opera 支持
(2)window.name,在一个页面中打开另一个页面时,window.name 是共享的,所以可以通过 window.name 来传递数据,window.name 的限制大小是 2M,这个所有浏览器都支持, 且没有什么限制。
3)document.domain 将两个页面的 document.domain 设置成相同,document.domain 只能设置成父级域名,既可以访问,使用限制:这顶级域名必须相同,document.domain + iframe 跨域
此方案仅限主域相同,子域不同的跨域应用场景。
- 实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。
1.)父窗口:(www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
2.)子窗口:(child.domain.com/b.html)
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent --->' + window.parent.user);
</script>
2. 纯后端方式: CORS,服务器代理
CORS 是 w3c 标准的方式,通过在 web 服务器端设置:响应头 Access-Control-Alow-Origin 来指定哪些域可以访问本域的数据,ie8&9(XDomainRequest),10+,chrom4,firefox3.5,safair4,opera12 支持这种方式。
服务器代理,同源策略只存在浏览器端,通过服务器转发请求可以达到跨域请求的目的,劣势:增加服务器的负担,且访问速度慢。
前端代码 –script.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
// var getvalue = function (data) {// alert(JSON.stringify(data));
// };
// var url = "http://127.0.0.1:3000/cors?callback=getvalue";
// var script = document.createElement('script');
// script.setAttribute('src', url);
// document.getElementsByTagName('head')[0].appendChild(script);
var xhr = new XMLHttpRequest(); // IE8/ 9 需用 window.XDomainRequest 兼容
// 前端设置是否带 cookie
xhr.withCredentials = false;
xhr.open('get', 'http://127.0.0.1:3000/cors.js?callback=getvalue', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send();
xhr.onreadystatechange = function() {if (xhr.readyState == 4 && xhr.status == 200) {alert(xhr.responseText);
}
};
</script>
<!--<script src="http://127.0.0.1:3000"></script>-->
</head>
<body>
ffffffffffffffffffffffffffff
</body>
</html>
后端代码 –demo2.js:
var express = require('express');
var app = express();
var http = require('http');
var fs = require('fs');
var url = require('url');
app.get('/', function (req, res) {res.sendfile(__dirname+"/index.html");
})
app.get('/cors.js', function(req, res) {var pathname = url.parse(req.url).pathname;
console.log("req for" + pathname + "received.");
fs.readFile(pathname.substr(1), function (err, data) {if (err) {console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/plain
res.writeHead(404, {'Content-Type': 'text/html'});
}else{res.header("Access-Control-Allow-Origin", "*"); // 设置请求来源不受限制
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); // 请求方式
res.header("X-Powered-By", '3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
var data = {
name: '- server 3001 cors process',
id: '- server 3001 cors process'
}
console.log(data);
// "getvalue(data)"
res.send("getvalue({ name:'5'})");
}
// 发送响应数据
res.end();});
})
var server = app.listen(3000, function () {var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:3000/');
3. 前后端结合:JsonP
script.src 不受同源策略的限制,所以可以动态的创建 script 标签,将要请求数据的域写在 src 中参数中附带回调的方法,服务器端返回回调函数的字符串,并带参数。
如 script.src=”http://www.yangwei.com/?id=001&callback=getInfoCallback, 服务器端返回 getInfoCallBack(“name:yangwei;age:18”) 这段代码会直接执行,在前面定义好 getInfoCallBack 函数,既可以获得数据并解析。这种是最长见的方式。
前端代码 –script.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
var getvalue = function (data) {alert(JSON.stringify(data));
};
var url = "http://127.0.0.1:3000/person.js?callback=getvalue";
var script = document.createElement('script');
script.setAttribute('src', url);
document.getElementsByTagName('head')[0].appendChild(script);
</script>
<!--<script src="http://127.0.0.1:3000"></script>-->
</head>
<body>
ffffffffffffffffffffffffffff
</body>
</html>
后端代码 –demo2.js
var express = require('express');
var app = express();
var http = require('http');
var fs = require('fs');
var url = require('url');
app.get('/', function (req, res) {res.sendfile(__dirname+"/index.html");
})
app.get('/person.js',function (req,res) {var pathname = url.parse(req.url).pathname;
// 输出请求的文件名
console.log("req for" + pathname + "received.");
// fs.readFile(pathname.substr(1), function (err, data) {// if (err) {// console.log(err);
// // HTTP 状态码: 404 : NOT FOUND
// // Content Type: text/plain
// res.writeHead(404, {'Content-Type': 'text/html'});
// }else{
// // HTTP 状态码: 200 : OK
// // Content Type: text/plain
// res.writeHead(200, {'Content-Type': 'text/html'});
//
// // 响应文件内容
// res.write(data.toString());
// }
// // 发送响应数据
// res.end();
// });
res.send("getvalue({ name:'5'})")
})
app.get('/message.js',function (req,res) {var pathname = url.parse(req.url).pathname;
// 输出请求的文件名
console.log("req for" + pathname + "received.");
fs.readFile(pathname.substr(1), function (err, data) {if (err) {console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/plain
res.writeHead(404, {'Content-Type': 'text/html'});
}else{
// HTTP 状态码: 200 : OK
// Content Type: text/plain
res.writeHead(200, {'Content-Type': 'text/html'});
// 响应文件内容
res.write(data.toString());
}
// 发送响应数据
res.end();});
})
app.get('/cors.js', function(req, res) {var pathname = url.parse(req.url).pathname;
console.log("req for" + pathname + "received.");
fs.readFile(pathname.substr(1), function (err, data) {if (err) {console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/plain
res.writeHead(404, {'Content-Type': 'text/html'});
}else{res.header("Access-Control-Allow-Origin", "*"); // 设置请求来源不受限制
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); // 请求方式
res.header("X-Powered-By", '3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
var data = {
name: '- server 3001 cors process',
id: '- server 3001 cors process'
}
console.log(data);
// "getvalue(data)"
res.send("getvalue({ name:'5'})");
}
// 发送响应数据
res.end();});
})
var server = app.listen(3000, function () {var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:3000/');
== 继承的几种方法 ==
字面量创建
- 代码
person = {
name:'FE',
age:23
}
- 优缺点
使用同一个接口创建很多对象,会产生大量重复的代码
工厂模式
- 代码
function creatPerson(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 = creatPerson('FE',20,'teacher');
- 优缺点
虽然解决了创建相似对象的问题,但是没有解决对象识别的问题(即怎样知道一个对象的类型)
构造函数模式
- 代码
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName=function(){alert(this.name);
}
}
var person1 = Person('FE',20,'teacher');
- 优缺点
创建自定义函数意味着将来可以将它的实例标识为一种特定的数据类型。但是每个方法都要在实例上重新创建一遍。
原型模式
- 代码
function Person(){};
Person.prototype.name = "FE";
Person.prototype.age = 20;
Person.prototype.sayName = function(){alert(this.name);
};
var person1 = new Person();
person1.sayName(); //'FE'
- 优缺点
可以让所有的实例共享它所包含的属性和方法。原型中的属性和方法是共享的,但是实例一般要有单独的属性和方法,一般很少单独使用原型模式。
混合模式
- 代码
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends=['aa','ss','dd'];
}
Person.prototype.sayName = function(){alert(this.name);
}
var person1 = new Person('FE',20,'teacher');
- 优缺点
构造函数模式定义实例的属性,原型模式定义公共的属性和方法
== 创建字面量的几种方法 ==
原型链继承
- 定义
利用原型让一个引用类型继承另外一个引用类型的属性和方法
- 代码
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();
alert(instance.getSuperValue());//true
- 优点
简单明了,容易实现,在父类新增原型属性和方法,子类都能访问到。
- 缺点
包含引用类型值的函数,所有的实例都指向同一个引用地址,修改一个,其他都会改变。不能像超类型的构造函数传递参数
构造函数继承
- 定义
在子类型构造函数的内部调用超类型的构造函数
- 代码
function SuperType(){this.colors = ['red','yellow'];
}
function SubType(){SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('black');
var instance2 = new SubType();
instance2.colors.push('white');
alert(instance1.colors);//'red','yellow','black'
alert(instance2.colors);//'red','yellow','white'
- 优点
简单明了,直接继承了超类型构造函数的属性和方法
- 缺点
方法都在构造函数中定义,因此函数复用就无从谈起了,而且超类型中的原型的属性和方法,对子类型也是不可见的,结果所有的类型只能使用构造函数模式。
组合继承
- 定义
使用原型链实现多原型属性和方法的继承,使用构造函数实现实例的继承
- 代码
function SuperType(name){
this.name = name;
this.colors = ['red','black'];
}
SuperType.prototype.sayName = function()
{alert(this.name);
}
function SubType(name,age){SuperType.call(this,name);
this.age = age;
}
SubType.protptype = new SuperType();
SubType.protptype.sayAge = function(){alert(this.age);
}
- 优点
解决了构造函数和原型继承中的两个问题
- 缺点
无论什么时候,都会调用两次超类型的构造函数
还有其他实现继承的方法,这里就不再赘述了,可参考 js 高级教程
==promise==
ES6 Promise 用法讲解
Promise 是一个构造函数,自己身上有 ==all、reject、resolve== 这几个眼熟的方法,原型上有 ==then、catch== 等同样很眼熟的方法。
那就 new 一个
var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
Promise 的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve 是将 Promise 的状态置为 fulfilled,reject 是将 Promise 的状态置为 rejected。不过在我们开始阶段可以先这么理解,后面再细究概念。
在上面的代码中,我们执行了一个异步操作,也就是 setTimeout,2 秒后,输出“执行完成”,并且调用 resolve 方法。
运行代码,会在 2 秒后输出“执行完成”。注意!我只是 new 了一个对象,并没有调用它,我们传进去的函数就已经执行了,这是需要注意的一个细节。所以我们用 Promise 的时候一般是包在一个函数中,在需要的时候去运行这个函数,如:
function runAsync(){var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
return p;
}
runAsync()
这时候你应该有两个疑问:1. 包装这么一个函数有毛线用?2.resolve(‘ 随便什么数据 ’); 这是干毛的?
我们继续来讲。在我们包装好的函数最后,会 return 出 Promise 对象,也就是说,执行这个函数我们得到了一个 Promise 对象。还记得 Promise 对象上有 then、catch 方法吧?这就是强大之处了,看下面的代码:
runAsync().then(function(data){console.log(data);
// 后面可以用传过来的数据做些其他操作
//......
});
在 runAsync()的返回上直接调用 then 方法,then 接收一个参数,是函数,并且会拿到我们在 runAsync 中调用 resolve 时传的的参数。运行这段代码,会在 2 秒后输出“执行完成”,紧接着输出“随便什么数据”。
这时候你应该有所领悟了,原来 then 里面的函数就跟我们平时的回调函数一个意思,能够在 runAsync 这个异步任务执行完成之后被执行。这就是 Promise 的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。
你可能会不屑一顾,那么牛逼轰轰的 Promise 就这点能耐?我把回调函数封装一下,给 runAsync 传进去不也一样吗,就像这样:
function runAsync(callback){setTimeout(function(){console.log('执行完成');
callback('随便什么数据');
}, 2000);
}
runAsync(function(data){console.log(data);
});
效果也是一样的,还费劲用 Promise 干嘛。那么问题来了,有多层回调该怎么办?如果 callback 也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个 callback2,然后给 callback 传进去吧。而 Promise 的优势在于,可以在 then 方法中继续写 Promise 对象并返回,然后继续调用 then 来进行回调操作。
链式操作的用法
所以,从表面上看,Promise 只是能够简化层层回调的写法,而实质上,Promise 的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递 callback 函数要简单、灵活的多。所以使用 Promise 的正确场景是这样的:
runAsync1()
.then(function(data){console.log(data);
return runAsync2();})
.then(function(data){console.log(data);
return runAsync3();})
.then(function(data){console.log(data);
});
这样能够按顺序,每隔两秒输出每个异步回调中的内容,在 runAsync2 中传给 resolve 的数据,能在接下来的 then 方法中拿到。运行结果如下:
猜猜 runAsync1、runAsync2、runAsync3 这三个函数都是如何定义的?没错,就是下面这样
function runAsync1(){var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){console.log('异步任务 1 执行完成');
resolve('随便什么数据 1');
}, 1000);
});
return p;
}
function runAsync2(){var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){console.log('异步任务 2 执行完成');
resolve('随便什么数据 2');
}, 2000);
});
return p;
}
function runAsync3(){var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){console.log('异步任务 3 执行完成');
resolve('随便什么数据 3');
}, 2000);
});
return p;
}
在 then 方法中,你也可以直接 return 数据而不是 Promise 对象,在后面的 then 中就可以接收到数据了,比如我们把上面的代码修改成这样:
runAsync1()
.then(function(data){console.log(data);
return runAsync2();})
.then(function(data){console.log(data);
return '直接返回数据'; // 这里直接返回数据
})
.then(function(data){console.log(data);
});
那么输出就变成了这样:
reject 的用法
到这里,你应该对“Promise 是什么玩意”有了最基本的了解。那么我们接着来看看 ES6 的 Promise 还有哪些功能。我们光用了 resolve,还没用 reject 呢,它是做什么的呢?事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject 的作用就是把 Promise 的状态置为 rejected,这样我们在 then 中就能捕捉到,然后执行“失败”情况的回调。看下面的代码。
function getNumber(){var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){var num = Math.ceil(Math.random()*10); // 生成 1 -10 的随机数
if(num<=5){resolve(num);
}
else{reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(function(data){console.log('resolved');
console.log(data);
},
function(reason, data){console.log('rejected');
console.log(reason);
}
);
getNumber 函数用来异步获取一个数字,2 秒后执行完成,如果数字小于等于 5,我们认为是“成功”了,调用 resolve 修改 Promise 的状态。否则我们认为是“失败”了,调用 reject 并传递一个参数,作为失败的原因。
运行 getNumber 并且在 then 中传了两个参数,then 方法可以接受两个参数,第一个对应 resolve 的回调,第二个对应 reject 的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:
或者
catch 的用法
我们知道 Promise 对象除了 then 方法,还有一个 catch 方法,它是做什么用的呢?其实它和 then 的第二个参数一样,用来指定 reject 的回调,用法是这样:
getNumber()
.then(function(data){console.log('resolved');
console.log(data);
})
.catch(function(reason){console.log('rejected');
console.log(reason);
});
效果和写在 then 的第二个参数里面一样。不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 方法中。请看下面的代码:
getNumber()
.then(function(data){console.log('resolved');
console.log(data);
console.log(somedata); // 此处的 somedata 未定义
})
.catch(function(reason){console.log('rejected');
console.log(reason);
});
在 resolve 的回调中,我们 console.log(somedata); 而 somedata 这个变量是没有被定义的。如果我们不用 Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:
也就是说进到 catch 方法里面去了,而且把错误原因传到了 reason 参数中。即便是有错误的代码也不会报错了,这与我们的 try/catch 语句有相同的功能。
all 的用法
Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的 runAsync1、runAsync2、runAsync3 这三个函数,看下面的例子:
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){console.log(results);
});
用 Promise.all 来执行,all 接收一个数组参数,里面的值最终都算返回 Promise 对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到 then 里面。那么,三个异步操作返回的数据哪里去了呢?都在 then 里面呢,all 会把所有异步操作的结果放进一个数组中传给 then,就是上面的 results。所以上面代码的输出结果就是:
有了 all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash 以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
race 的用法
all 方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是 race 方法,这个词本来就是赛跑的意思。race 的用法与 all 一样,我们把上面 runAsync1 的延时改为 1 秒来看一下:
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){console.log(results);
});
这三个异步操作同样是并行执行的。结果你应该可以猜到,1 秒后 runAsync1 已经执行完了,此时 then 里面的就执行了。结果是这样的:
你猜对了吗?不完全,是吧。在 then 里面的回调开始执行时,runAsync2()和 runAsync3()并没有停止,仍旧再执行。于是再过 1 秒后,输出了他们结束的标志。
这个 race 有什么用呢?使用场景还是很多的,比如我们可以用 race 给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
// 请求某个图片资源
function requestImg(){var p = new Promise(function(resolve, reject){var img = new Image();
img.onload = function(){resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
// 延时函数,用于给请求计时
function timeout(){var p = new Promise(function(resolve, reject){setTimeout(function(){reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){console.log(results);
})
.catch(function(reason){console.log(reason);
});
requestImg 函数会异步请求一张图片,我把地址写为 ”xxxxxx”,所以肯定是无法成功请求到的。timeout 函数是一个延时 5 秒的异步操作。我们把这两个返回 Promise 对象的函数放进 race,于是他俩就会赛跑,如果 5 秒之内图片请求成功了,那么遍进入 then 方法,执行正常的流程。如果 5 秒钟图片还未成功返回,那么 timeout 就跑赢了,则进入 catch,报出“图片请求超时”的信息。运行结果如下:
== 几种常见的 http 状态码 ==
2XX—- 成功
- 200 ok 成功
- 204 No Content 请求处理成功,但是没有资源返回
- 206 Partial Content 对资源某一部分的请求
3XX—- 重定向
- 301 永久重定向
- 302 临时重定向
- 304 资源已找到,但是没有符合条件的请求
4XX—- 客户端错误
- 400 请求报文中存在语法错误
- 401 需要 http 认证
- 403 对请求资源的访问被服务器给拒绝了
- 404 页面找不到了
5XX—- 服务器错误
- 500 内部资源出故障了
- 503 服务器正在超负载的工作或者停机维护
http 方法
- get—- 获取资源
- post—- 修改数据
- put—- 传输文件
- head—- 获取报文的头部
- delete—- 删除文件
- options—- 询问支持的方法
- trace—- 追踪路径
html&css 篇
== 语义化的 html==
一、什么是语义化的 HTML?
语义化的 HTML 就是正确的标签做正确的事情,能够便于开发者阅读和写出更优雅的代码的同时让网络爬虫很好地解析。
二、为什么要做到语义化?
1、有利于 SEO,有利于搜索引擎爬虫更好的理解我们的网页,从而获取更多的有效信息,提升网页的权重。
2、在没有 CSS 的时候能够清晰的看出网页的结构,增强可读性。
3、便于团队开发和维护,语义化的 HTML 可以让开发者更容易的看明白,从而提高团队的效率和协调能力。
4、支持多终端设备的浏览器渲染。
三、语义化 HTML 该怎么做呢?
在做前端开发的时候要记住:HTML 告诉我们一块内容是什么(或其意义),而不是它长的什么样子,它的样子应该由 CSS 来决定。(结构与样式分离!)
写语义化的 HTML 结构其实很简单,首先掌握 HTML 中各个标签的语义,在看到内容的时候想想用什么标签能更好的描述它,是什么就用什么标签。
<h1>~<h6>,作为标题使用,并且依据重要性递减,<h1> 是最高的等级。<p> 段落标记,知道了 <p>
作为段落,你就不会再使用 <br />
来换行了,而且不需要 <br />
来区分段落与段落。<p>
中的文字会自动换行,而且换行的效果优于 <br />。段落与段落之间的空隙也可以利用 CSS 来控制,很容易而且清晰的区分出段落与段落。<ul>、<ol>、<li>,<ul> 无序列表,这个被大家广泛的使用,<ol>
有序列表也挺常用。在 web 标准化过程中,<ul>
还被更多的用于导航条,本来导航条就是个列表,这样做是完全正确的,而且当你的浏览器不支持 CSS 的时候,导航链接仍然很好使,只是美观方面差了一点而已。<dl>、<dt>、<dd>,<dl> 就是“定义列表”。比如说词典里面的词的解释、定义就可以用这种列表。<em>、<strong>,<em> 是用作强调,<strong> 是用作重点强调。<q> 也不仅仅只是为文字增加双引号,而是代表这些文字是引用来的。<table>、<td>、<th>、<caption>,(X)HTML 中的表格不再是用来布局。
补充:网络爬虫,SEO 等概念
SEO:Search Engine Optimization
——搜索引擎优化,这是一种利用搜索引擎的搜索规则,采取优化策略或程序,提高网站在搜索结果中的排名。
网络爬虫:
又称网络蜘蛛、网络机器人,是一种搜索引擎用于自动抓取网页资源的程序或者说叫机器人。从某一个网址为起点,去访问,然后把网页存回到数据库中,如此不断循环,一般认为搜索引擎爬虫都是靠链接爬行的,所以管他叫爬虫。这个只有开发搜索引擎才会用到。对于网站而言,只要有链接指向我们的网页,爬虫就会自动提取我们的网页。
h5 新增标签
==position==
1.position 中 relative 和 absolute,fix 的区别
- fixed 属性会固定不动,不会随着屏幕的滚动滚动
- absolute:
温馨提示的《CSS 彻底研究》对绝对定位描述如下:1、使用绝对定位的盒子以它的“最近”的一个“已定位”(position 属性被设置,并且被设置为不是 static 的任意一种)的“祖先元素”为基准进行偏移。如果没有已经定位的祖先元素,那么会以浏览器窗口为基准进行定位。2、绝对定位的框从标准流中脱离,这意味着它们对其后的兄弟盒子的定位没有影响,其他的盒子就好像这个盒子不存在一样。
3. 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。4. 子元素:position:absolute 也可以 == 相对于父元素 ==position:absolute
- relative: 这个是相对于,他不加定位之前的位置,定位之后,他原来的位置不清空,其他元素不能占用,会影响其他盒子的位置。
==css 垂直水平居中(不知宽高)==
方法一
- 思路:显示设置父元素为:table,子元素为:cell-table,这样就可以使用 vertical-align: center,实现水平居中
- 优点:父元素(parent)可以动态的改变高度(table 元素的特性)
- 缺点:IE8 以下不支持
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 未知宽高元素水平垂直居中 </title>
</head>
<style>
.parent1{
display: table;
height:300px;
width: 300px;
background-color: #FD0C70;
}
.parent1 .child{
display: table-cell;
vertical-align: middle;
text-align: center;
color: #fff;
font-size: 16px;
}
</style>
<body>
<div class="parent1">
<div class="child">hello world-1</div>
</div>
</body>
</html>
方法二:
- 思路:使用一个空标签 span 设置他的 vertical-align 基准线为中间,并且让他为 inline-block,宽度为 0
- 缺点:多了一个没用的空标签,display:inline-blockIE 6 7 是不支持的(添加上:_zoom1;*display:inline)。
- 当然也可以使用伪元素来代替 span 标签,不过 IE 支持也不好,但这是后话了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 未知宽高元素水平垂直居中 </title>
</head>
<style>
.parent2{
height:300px;
width: 300px;
text-align: center;
background: #FD0C70;
}
.parent2 span{
display: inline-block;;
width: 0;
height: 100%;
vertical-align: middle;
zoom: 1;/*BFC*/
*display: inline;
}
.parent2 .child{
display: inline-block;
color: #fff;
zoom: 1;/*BFC*/
*display: inline;
}
</style>
<body>
<div class="parent1">
<div class="child">hello world-1</div>
</div>
<div class="parent2">
<span></span>
<div class="child">hello world-2</div>
</div>
</body>
</html>
方法三
- 思路:子元素绝对定位,距离顶部 50%,左边 50%,然后使用 css3 transform:translate(-50%; -50%)
- 优点:高大上, 可以在 webkit 内核的浏览器中使用
- 缺点:不支持 IE9 以下不支持 transform 属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 未知宽高元素水平垂直居中 </title>
</head>
<style>
.parent3{
position: relative;
height:300px;
width: 300px;
background: #FD0C70;
}
.parent3 .child{
position: absolute;
top: 50%;
left: 50%;
color: #fff;
transform: translate(-50%, -50%);
}
</style>
<body>
<div class="parent3">
<div class="child">hello world-3</div>
</div>
</body>
</html>
方法四:
- 思路:使用 css3 flex 布局
- 优点:简单 快捷
- 缺点:兼容不好吧,详情见:http://caniuse.com/#search=flex
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 未知宽高元素水平垂直居中 </title>
</head>
<style>
.parent4{
display: flex;
justify-content: center;
align-items: center;
width: 300px;
height:300px;
background: #FD0C70;
}
.parent4 .child{color:#fff;}
</style>
<body>div> <div class="parent4">
<div class="child">hello world-4</div>
</div>
</body>
</html>
==margin 折叠 ==
margin 折叠 –margin 值合并
代码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title> 测试 </title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
#father{
width: 2000px;
height: 2000px;
background: #0016d9;
overflow: hidden;
}
#first-child{
margin-top: 20px;
background: chocolate;
width: 60px;
height: 60px;
}
#second-child{
background: chartreuse;
width: 60px;
height: 60px;
margin-bottom: 20px;
}
#three-child{
margin-top:40px;
background: fuchsia;
width: 60px;
height: 60px;
display: inline-block;
}
</style>
<body>
<div id="father">
<div id="first-child">box1</div>
<div id="second-child">box2</div>
<div id="three-child">box3</div>
</div>
</body>
</html>
- 子元素的父元素的间距问题:
- 在父层 div 加上:overflow:hidden;
- 把 margin-top 外边距改成 padding-top 内边距;
- 父元素产生边距重叠的边有不为 0 的 padding 或宽度不为 0 且 style 不为 none 的 border
父层 div 加:padding-top: 1px, 或者 border-top:1px ;
- 设置父元素或子元素浮动(left/right)
- 设置父元素 dispaly:inline-block 或者 display:table-cell;
- 给父元素加上绝对定位
- 相邻元素之间的 margin
- 给最后面的元素加上浮动(left/right)
- 给最后一个元素加上 display:inline-block; 但是 IE6 和 IE7 下不完全支持 display:inline-block,所以要加上 *display:inline;zoom:1 即可解决 IE6、7 的 bug;
== 清除浮动 ==
1、父级 div 定义伪类:after 和 zoom
复制代码
<style type="text/css">
.div1{background:#000080;border:1px solid red;}
.div2{background:#800080;border:1px solid red;height:100px;margin-top:10px}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
/* 清除浮动代码 */
.clearfloat:after{display:block;clear:both;content:"";visibility:hidden;height:0}
.clearfloat{zoom:1}
</style>
<div class="div1 clearfloat">
<div class="left">Left</div>
<div class="right">Right</div>
</div>
<div class="div2">
div2
</div>
- 原理:IE8 以上和非 IE 浏览器才支持:after,原理和方法 2 有点类似,zoom(IE 转有属性)可解决 ie6,ie7 浮动问题
- 优点:浏览器支持好,不容易出现怪问题(目前:大型网站都有使用,如:腾迅,网易,新浪等等)
- 缺点:代码多,不少初学者不理解原理,要两句代码结合使用,才能让主流浏览器都支持
- 建议:推荐使用,建议定义公共类,以减少 CSS 代码
- 评分:★★★★☆
2. 在结尾处添加空 div 标签 clear:both
<style type="text/css">
.div1{background:#000080;border:1px solid red}
.div2{background:#800080;border:1px solid red;height:100px;margin-top:10px}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
/* 清除浮动代码 */
.clearfloat{clear:both}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
<div class="clearfloat"></div>
</div>
<div class="div2">
div2
</div>
- 原理:添加一个空 div,利用 css 提高的 clear:both 清除浮动,让父级 div 能自动获取到高度
- 优点:简单,代码少,浏览器支持好,不容易出现怪问题
- 缺点:不少初学者不理解原理;如果页面浮动布局多,就要增加很多空 div,让人感觉很不爽
- 建议:不推荐使用,但此方法是以前主要使用的一种清除浮动方法
- 评分:★★★☆☆
3. 父级 div 定义 height
<style type="text/css">
.div1{background:#000080;border:1px solid red;/* 解决代码 */height:200px;}
.div2{background:#800080;border:1px solid red;height:100px;margin-top:10px}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
</div>
<div class="div2">
div2
</div>
- 原理:父级 div 手动定义 height,就解决了父级 div 无法自动获取到高度的问题
- 优点:简单,代码少,容易掌握
- 缺点:只适合高度固定的布局,要给出精确的高度,如果高度和父级 div 不一样时,会产生问题
- 建议:不推荐使用,只建议高度固定的布局时使用
- 评分:★★☆☆☆
4. 父级 div 定义 overflow:hidden
<style type="text/css">
.div1{background:#000080;border:1px solid red;/* 解决代码 */width:98%;overflow:hidden}
.div2{background:#800080;border:1px solid red;height:100px;margin-top:10px;width:98%}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
</div>
<div class="div2">
div2
</div>
- 原理:必须定义 width 或 zoom:1,同时不能定义 height,使用 overflow:hidden 时,浏览器会自动检查浮动区域的高度
- 优点:简单,代码少,浏览器支持好
- 缺点:不能和 position 配合使用,因为超出的尺寸的会被隐藏
- 建议:只推荐没有使用 position 或对 overflow:hidden 理解比较深的朋友使用
- 评分:★★★☆☆
5. 父级 div 定义 overflow:auto
<style type="text/css">
.div1{background:#000080;border:1px solid red;/* 解决代码 */width:98%;overflow:auto}
.div2{background:#800080;border:1px solid red;height:100px;margin-top:10px;width:98%}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
</div>
<div class="div2">
div2
</div>
- 原理:必须定义 width 或 zoom:1,同时不能定义 height,使用 overflow:auto 时,浏览器会自动检查浮动区域的高度
- 优点:简单,代码少,浏览器支持好
- 缺点:内部宽高超过父级 div 时,会出现滚动条。
- 建议:不推荐使用,如果你需要出现滚动条或者确保你的代码不会出现滚动条就使用吧。
- 评分:★★☆☆☆
6. 父级 div 也一起浮动
<style type="text/css">
.div1{background:#000080;border:1px solid red;/* 解决代码 */width:98%;margin-bottom:10px;float:left}
.div2{background:#800080;border:1px solid red;height:100px;width:98%;/* 解决代码 */clear:both}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
</div>
<div class="div2">
div2
</div>
- 原理:所有代码一起浮动,就变成了一个整体
- 优点:没有优点
- 缺点:会产生新的浮动问题。
- 建议:不推荐使用,只作了解。
- 评分:★☆☆☆☆
7. 父级 div 定义 display:table
<style type="text/css">
.div1{background:#000080;border:1px solid red;/* 解决代码 */width:98%;display:table;margin-bottom:10px;}
.div2{background:#800080;border:1px solid red;height:100px;width:98%;}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
</div>
<div class="div2">
div2
</div>
- 原理:将 div 属性变成表格
- 优点:没有优点
- 缺点:会产生新的未知问题
- 建议:不推荐使用,只作了解
- 评分:★☆☆☆☆
8、结尾处加 br 标签 clear:both
<style type="text/css">
.div1{background:#000080;border:1px solid red;margin-bottom:10px;zoom:1}
.div2{background:#800080;border:1px solid red;height:100px}
.left{float:left;width:20%;height:200px;background:#DDD}
.right{float:right;width:30%;height:80px;background:#DDD}
.clearfloat{clear:both}
</style>
<div class="div1">
<div class="left">Left</div>
<div class="right">Right</div>
<br class="clearfloat" />
</div>
<div class="div2">
div2
</div>
- 原理:父级 div 定义 zoom:1 来解决 IE 浮动问题,结尾处加 br 标签 clear:both
- 建议:不推荐使用,只作了解
- 评分:★☆☆☆☆
==box-sizing==
box-sizing:content-box
box-sizing:content-box 情况下,元素的宽度 =width+pading+border;
解释:box-sizing:content-box,相当于你从网上买东西,content-box 为你买的实际要用的东西,假设为 A。
快递员配送快递的时候,实际上你收到的快递是带有包装的 A。
类比一下,content-box 是 A,box-sizing 是收快递的你,赋值是快递员配送,
最后你手里收到的东西就是 A + 包装盒,也就是 content+border+padding;
/ width 和 height 属性包括内容,内边距和边框,但不包括外边距 /
box-sizing:border-box
情况下,元素的宽度 =width,pading,border 都包含在 width 里面
解释:box-sizing:border-box; 相当于你从网上买东西,border-box 是带有包装的你买的东西,假设为 B。
快递员配送快递的时候,实际上你收到的快递就是 B。
类比一下,border-box 是 B,box-sizing 是收快递的你,赋值是快递员配送,
最后你手里收到的东西就是 B;
== 列举一些常见的块级元素和行内元素 ==
- 行内元素有:title lable span br a em b i strong
- 块级元素有:body form select textarea h1-h6 table button hr p ol ul dl div
- 行内块元素常见的有:img input td
==CSS 的优先级【class,id,内联,!important】==
!important > 行内样式 >ID 选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性
类选择器和属性选择器优先级相同,谁在后面谁的优先级较高
注意:通用选择器(*),子选择器(>)和相邻同胞选择器(+),他们的权值是 0,所以两个通配符选择器和一个通配符选择器的权值是相同的
- 内联样式表的权值为 1000,就是在元素内写 style
- ID 选择器的权值为 100
- Class 类选择器的权值为 10
- HTML 标签选择器的权值为 1
== 对 inline 元素设置 padding、margin 有效吗?==
- inline 元素设置 width 和 height 无效
- 设置 margin-left、margin-right、padding-left、padding-right 有效
- 设置 margin-top、margin-bottom、padding-top、padding-bottom 无效
==line-heghit==
line-height 的继承【带单位和不带单位】
line-height 是可以继承的,所以子元素就可以不用重复定义 line-height 了。我们一般也会在后面带上单位(如:line-height:22px; 或是 line-height:1.4em;),但 line-height 会给人带来麻烦的地方也就是这个继承和后面加的单位。
【Css 高级应用】line-height 带单位与不带单位的区别
有的时候,我们为了实现单行文字的垂直居中,会给 line-height 一个和 height 相同的固定的值;有的时候,我们为了调整特定的某段文字 的行间距,通常会考虑使用百分比或者相对尺寸的 em。
或许是习惯,于是我们都习惯了 line-height 是要有单位的。
这些情况下,我们都不需要考虑 line-height 的继承, 也不会发现任何问题,当然后我们在使用到 line-height 继承的时候,就会发现问题的所在。
例如下面的代码:
1、样式
<style type="text/css">
.test{line-height:1.4em; font-size:12px;}
span{font-size:30px; font-weight:bold;}
</style>
2、HTML 结构
<div class="test">
<span> 白培铭先生于 1960 年出生于中国台湾。<br/>
毕业于中国台湾省清华大学核物理系,<br/>
</span>
之后留学于美国加州大学伯克利分校和密西根大学,获得双硕士学位。<br/>
在工作之后,凭着对营销领域的浓厚兴趣,他又考入密执安大学深造 <br/>
</div>
看过例子后,你会发现,只要有单位的 line-height 继承,都发生了重叠的现象。那到底这是什么原因导致的呢?
- 如果 line-height 属性值有单位,那么继承的值则是换算后的一个具体的 px 级别的值;
- 而如果属性值没有单位,则浏览器会直接继承这个“因子(数值)”,而非计算后的具体值,此时它的 line-height 会根据本 == 身的 font-size== 值重新计算得到新的 line-height 值。
==rem 和 em 的区别?==
css 中单位 em 和 rem 的区别
在 css 中单位长度用的最多的是 px、em、rem,这三个的区别是:
- px 是固定的像素,一旦设置了就无法因为适应页面大小而改变。
- em 和 rem 相对于 px 更具有灵活性,他们是相对长度单位,意思是长度不是定死了的,更适用于响应式布局。
- 对于 em 和 rem 的区别一句话概括:em 相对于父元素,rem 相对于根元素。
- rem 中的 r 意思是 root(根源),这也就不难理解了。
em
- 子元素字体大小的 em 是相对于父元素字体大小
- 元素的 width/height/padding/margin 用 em 的话是相对于该元素的 font-size
rem
- rem 是全部的长度都相对于根元素,根元素是谁?<html> 元素。通常做法是给 html 元素设置一个字体大小,然后其他元素的长度单位就为 rem。
== 元素 position 有哪几种【static、relative、absolute、fixed】==
- static:没有定位,在正常的流中
- relative:相对于正常位置
- absolute:相当于第一个父元素进行定位
- fixed:相对于浏览器的窗口进行定位
==BFC==
块级格式上下文 Block Formatting Context(简称 BFC),这是 Web 页面的一种布局方式,通俗点说,也是指页面上一个渲染区域,里面的元素按文档流中的顺序垂直排列,并且发生垂直方向上的 margin 折叠,同时这个区域内的元素布局不会对外面的元素有任何影响,反过来,也是一样。
当元素满足一下任何一个条件是都会产生一个 BFC:
- float 属性取值不是“none”
- overflow 属性取值不是“visible”
- display 的值为“table-cell”,“table-caption”, or“inline-block”中的任何一个
- position 的值不为“static”或“relative”中的任何一个
相关文献