在此以做记录,有什么问题还望大家指正。
关键字:算法、模块化、闭包、随机数、浮动、bind、正则、DOM分析
闭包理解和问题解决
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
</ul>
下面的这种写法会始终弹出 4
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
lists[ i ].onmouseover = function(){
alert(i);
};
}
解决方法一:
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
(function(index){
lists[ index ].onmouseover = function(){
alert(index);
};
})(i);
}
解决方法二:
var lists = document.getElementsByTagName('li');
for(var i = 0, len = lists.length; i < len; i++){
lists[ i ].$$index = i; //通过在Dom元素上绑定$$index属性记录下标
lists[ i ].onmouseover = function(){
alert(this.$$index);
};
}
解决方法三:
function eventListener(list, index){
list.onmouseover = function(){
alert(index);
};
}
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
eventListener(lists[ i ] , i);
}
当你输入一个网址的时候,实际会发生什么?
- 第一步当然是输入网址
- 第二步浏览器查找域名对于的IP地址
- 第三步浏览器给web服务器发送一个HTTP请求
- 第四步 facebook服务的永久重定向响应 服务器给浏览器响应一个301永久重定向响应,这样浏览器就会访问“http://www.facebook.com/” 而非“http://facebook.com/”。
- 第五步浏览器跟踪重定向地址 现在,浏览器知道了“http://www.facebook.com/”才是要访问的正确地址,所以它会发送另一个获取请求
- 第六步服务器"处理"请求 服务器接收到获取请求,然后处理并返回一个响应。
- 第七步服务器发回一个HTML响应 报头中把Content-type设置为“text/html”。报头让浏览器将该响应内容以HTML形式呈现,而不是以文件形式下载它。浏览器会根据报头信息决定如何解释该响应,不过同时也会考虑像URL扩展内容等其他因素。
- 第八步浏览器开始显示HTML 在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了:
- 第九步浏览器发送获取嵌入在HTML中的对象
- 第十步浏览器发送异步(AJAX)请求
算法说明
- 时间复杂度指的是一个算法执行所耗费的时间
- 空间复杂度指运行完一个程序所需内存的大小
- 稳定指,如果a=b,a在b的前面,排序后a仍然在b的前面
- 不稳定指,如果a=b,a在b的前面,排序后可能会交换位置
//数组去重
方法1:
function unique(arr){
var obj = {};
var temp = [];
for(var i = 0; i < arr.length; i++){
if(!obj[arr[i]]){
obj[arr[i]] = true;
temp.push(arr[i]);
}
}
return temp;
}
方法2:
[...new Set([2,1,2,1,2,3,1])]
//冒泡排序 两两比较 最大或者最小的排在了最后
function bubbleSort(arr){
for(let i = 0; i < arr.length;i++){
for(let j = 0; j < arr.length - 1 - i;j++){
let temp;
if(arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
//快速排序 元素为基准, 小的放左边 大的放右边
function quickSort(arr){
if(arr.length <= 1) return arr;
var pivotIndex = Math.floor(arr.length/2);
//找基准,并把基准从原数组删除 从index开始删除1个元素
var pivot = arr.splice(pivotIndex,1)[0];
var left = [];
var right = [];
// 比基准小的放在left 大的放在right
for(var i = 0; i < arr.length; i++){
if(arr[i] <= pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot],quickSort(right));
}
// 选择排序 把每一个数都与第一个数比较,如果小于第一个数,就把它们交换位置;这样一轮下来,最小的数就排到了最前面;重复n-1轮
function selectSort(arr){
let len = arr.length;
let temp;
for(let i = 0; i < len -1;i++){
let minIndex = i;
for(let j = i + 1; j < len; j++){
if(arr[j] < arr[minIndex]){ //寻找最小的数
minIndex = j; //将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
// 插入排序:
// 解析:
// (1) 从第一个元素开始,该元素可以认为已经被排序
// (2) 取出下一个元素,在已经排序的元素序列中从后向前扫描
// (3) 如果该元素(已排序)大于新元素,将该元素移到下一位置
// (4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
// (5)将新元素插入到下一位置中
// (6) 重复步骤2
function insertSort(arr){
// 假设第0个元素是一个有序的数列,第1个以后是无序的序列
// 所以第一个元素开始讲无序序列的元素插入到有序数列中
for(let i = 1; i < arr.length;i++){
// 升序
if(arr[i] < arr[i - 1]){
// 取出无序序列的第i个元素作为被插入元素
var guard = arr[i];
// 记住有序序列的最后一个位置 并且将有序序列位置扩大一个
var j = i - 1;
arr[i] = arr[j];
while(j > 0 && guard < arr[j]){
arr[j+1] = arr[j];
j--;
}
arr[j+1] = guard;
}
}
return arr;
}
清除浮动
- 01
.classfix{
clear:both;
}
<div class="clearfix"></div>
- 02:
clearfix的方法:使用overflow进行包裹所有的浮动子元素。有误伤。
Ckearfix的使用场景:
1、父盒子要把所有的子盒子包裹住。
2、父盒子是包裹正行的 div元素,需要前后进行清除浮动。
.clearfix {
display:table;//触发bfc,div具有的包裹性
}
.clearfix:before,.clearfix:after{
conotent:'';
display: block;
clear:both;
height:0;
visibility:hidden;
}
.clearfix {
_zoom:1;
}
模块化
// Javascript模块化编程
// 1. 对于早期来说 我们一般函数的写法如下
function m1() {}
function m2() {}
// 函数m1 m2组成了一个模块, 直接使用调用即可, 但是
// 这样的话会污染全局变量,无法保证不和其他模块的变量
// 名冲突,而且也看不出和其他模块成员的关系
// 2. 那我们将模块写成对象,所以模块成员放到这个对象当中
var modul1 = new Object({
_count: 0,
m1: function() {},
m2: function() {}
})
// 这里讲m1 m2封装到module1里面,使用的时候调用就可以
module.m1();
// 这样的话会暴露所有模块的成员,内部状态可以被改写,比如
// 外部可以直接修改内部计数器的数值
modul1._count = 5;
// 3. 立即执行函数可以达到不暴露私有成员的目的
var module1 = (function() {
var _count = 1;
var m1 = function() {};
var m2 = function() {};
return {
m1: m1,
m2: m2
}
})();
// 这种写法 外部代码就无法访问内部_count 变量
console.info(modul1._count);
// 4. 那么接下来, 如果模块很大的话, 可能继承自其他模块,
// 这个时候可以采用"放大模式"
var module1 = (function(mod) {
mod.m3 = function() {};
return mod;
})(module1);
// 在不影响原来模块的基础上, 添加新的方法, 返回新的module1模块
// 5. 但是上面的这种写法有一个弊病, 在浏览器环境中, 模块的各个部分通常都是从网上获取的, 有时无法知道哪个部分会先加载, 如果采用上面的方法, 第一个执行的部分有可能加载一个不存在空对象, 这时就要采用 "宽放大模式"。
var module1 = (function(mod) {
// ...
return mod;
})(window.modul1 || {});
// 与放大模式相比, 宽放大模式就是立即执行函数的参数可能是空对象
// 6. 输入全局变量
// 独立性是模块的重要特点,模块内部最好不和程序其他部分直接交互
// 为了在模块内部调用全局变量,必须显示将其他变量输入模块
var module1 = (function($, YAHOO) {
//...
})(jQuery, YAHOO);
// 上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块
作用域
一般会问的是 es6 中 let 与 var 的区别, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便.
- let所声明的变量,只在let命令所在的代码块内有效。
- var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
- 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。
typeof x; // ReferenceError
let x;
上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError。
作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。
typeof undeclared_variable // "undefined"
上面代码中,undeclared_variable是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
- let不允许在相同作用域内,重复声明同一个变量。
// 报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
因此,不能在函数内部重新声明参数。
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
AMD CMD CommonJs
AMD/CMD/CommonJs是JS模块化开发的标准,目前对应的实现是RequireJs/SeaJs/nodeJs.
CommonJs主要针对服务端,AMD/CMD主要针对浏览器端,所以最容易混淆的是AMD/CMD。(顺便提一下,
针对服务器端和针对浏览器端有什么本质的区别呢?服务器端一般采用同步加载文件,也就是说需要某个模块,
服务器端便停下来,等待它加载再执行。这里如果有其他后端语言,如java,经验的‘玩家’应该更容易理解。
而浏览器端要保证效率,需要采用异步加载,这就需要一个预处理,提前将所需要的模块文件并行加载好。)
AMD/CMD区别,虽然都是并行加载js文件,但还是有所区别,AMD是预加载,在并行加载js文件同时,还会
解析执行该模 块(因为还需要执行,所以在加载某个模块前,这个模块的依赖模块需要先加载完成);
而CMD是懒加载,虽然会一开 始就并行加载js文件,但是不会执行,而是在需要的时候才执行。
AMD/CMD的优缺点.一个的优点就是另一个的缺点, 可以对照浏览。
AMD优点:加载快速,尤其遇到多个大文件,因为并行解析,所以同一时间可以解析多个文件。
AMD缺点:并行加载,异步处理,加载顺序不一定,可能会造成一些困扰,甚至为程序埋下大坑。
CMD优点:因为只有在使用的时候才会解析执行js文件,因此,每个JS文件的执行顺序在代码中是有体现的,是可控的。
CMD缺点:执行等待时间会叠加。因为每个文件执行时是同步执行(串行执行),因此时间是所有文件
解析执行时间之和,尤其在文件较多较大时,这种缺点尤为明显。
https://www.zhihu.com/question/20351507/answer/14859415
如何使用?CommonJs的话,因为nodeJs就是它的实现,所以使用node就行,也不用引入其他包。
AMD则是通过<script>标签引入RequireJs,具体语法还是去看官方文档或者百度一下吧。
CMD则是引入SeaJs。
exports 和 module.exports 的区别:
- module.exports 初始值为一个空对象 {}
- exports 是指向的 module.exports 的引用
- require() 返回的是 module.exports 而不是 exports
我们经常看到这样的写法:
exports = module.exports = somethings
上面的代码等价于:
module.exports = somethings
exports = module.exports
原理很简单,即 module.exports 指向新的对象时,exports 断开了与 module.exports 的引用,那么通过 exports = module.exports 让 exports 重新指向 module.exports 即可。
CommonJS是服务器端模块的规范,Node.js采用了这个规范。
根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。
CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。
随机抽奖
Knuth-Durstenfeld Shuffle
Fisher-Yates 洗牌算法的一个变种是 Knuth Shuffle
每次从未处理的数组中随机取一个元素,然后把该元素放到数组的尾部,即数组的尾部放的就是已经处理过的元素,这是一种原地打乱的算法,每个元素随机概率也相等,时间复杂度从 Fisher 算法的 O(n2)提升到了 O(n)
选取数组(长度n)中最后一个元素(arr[length-1]),将其与n个元素中的任意一个交换,此时最后一个元素已经确定
选取倒数第二个元素(arr[length-2]),将其与n-1个元素中的任意一个交换
重复第 1 2 步,直到剩下1个元素为止
functuin shuffle(arr){
var length = arr.length, temp, random;
while(0 != length){
random = Math.floor(Math.random()*length);
length--;
temp = arr[length];
arr[length] = arr[random]l
arr[random] = temp;
}
return arr;
}
Array.sort()
利用Array的sort方法可以更简洁的实现打乱,对于数量小的数组来说足够。因为随着数组元素增加,随机性会变差。
[1,3,14,11,2,4,5].sort(()=>{
return .5 - Math.random();
})
Math.round(Math.random() * 1000)
表示产生一个随机数并把它放大1000倍再取整,即生成0~1000之间的随机整数。
((Math.random() * 10 + 5).toFixed(1) - 0)
表示产生一个5到15之间,包含一位小数的随机数。
分步解释一下,先产生一个随机数把它乘以10再加上5,即生成5~15的随机数,然后调用toFixed转换成保留1位小数的字符串形式,最后减0是做了隐式转换,把字符串再转换为数字。
不需要洗所有的牌
function draw(amount,n=1){
const cards = Array(amount).fill().map((_,i)=>i+1);
for(let i = amount - 1, stop = amount - n - 1;i > stop;i--){
let rand = Math.floor((i+1)*Math.random());
[cards[rand],cards[i]] = [cards[i],cards[rand]];
}
return cards.slice(-n);
}
console.log(draw(62,10));
function shuffle(amount,n=1){
const arr = Array(amount).fill().map((_,i)=>i+1); 建立长度为amount的数组
for(var i = arr.length - 1,stop = amount - n - 1;i >stop;i--){
var random = Math.floor((i+1)*Math.random());
[arr[random],arr[i]] = [arr[i],arr[random]];
}
return arr.slice(-n);
}
shuffle(12,10);
Array.prototype.shuffle = function() {
var input = this;
for (var i = input.length-1; i >=0; i--) {
var randomIndex = Math.floor(Math.random()*(i+1));
[input[randomIndex], input[i]] = [input[i], input[randomIndex]] ;
}
return input;
}
var tempArray = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
tempArray.shuffle();
console.log(tempArray);
上面这个版本是优化过的版本,显然如果取 10 个数,只需要循环 10 次即可,不需要把 64 张牌都洗了。
要解决可以连续抽奖的问题,就需要把 cards 提取出来(就像方案 1 的随机抽取一样),但是那样的话就使得函数有副作用,虽说是临时写一个抽奖,也不喜欢设计得太糙。或者,那就加一个构造器执行初始化?
function Box(amount){
this.cards = Array(amount).fill().map((_,i)=>i+1);
}
Box.prototype.draw = function(n = 1){
let cards = this.cards,amount = this.cards.length;
for(let i = amount - 1, stop = amount - n - 1; i > stop; i--){
let rand = Math.floor((i + 1) * Math.random());
[cards[rand], cards[i]] = [cards[i], cards[rand]];
}
let ret = cards.slice(-n); //截取最后的十个数字
cards.length = amount - n; //数组长度减少 去掉部分值
return ret;
}
var box = new Box(62);
console.log(box.draw(5),box.draw(5));
JS bind
bind()方法创建一个新的函数, 当被调用时,它的this关键字被设置为提供的值 ,在调用新函数时,提供任何一个给定的参数序列。
语法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
参数
thisArg
当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
arg1, arg2, ...
当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
返回值
返回由指定的this值和初始化参数改造的原函数拷贝
描述
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
示例
创建绑定函数
bind() 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的 this 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 this 是原来的对象。(比如在回调中传入这个方法。)如果不做特殊处理的话,一般会丢失原来的对象。从原来的函数和原来的对象创建一个绑定函数,则能很漂亮地解决这个问题:
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 返回 81
var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域
// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81
偏函数(Partial Functions)
bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。这些参数(如果有的话)作为bind()的第二个参数跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
配合 setTimeout
在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window (或全局)对象。当使用类的方法时,需要 this 引用类的实例,你可能需要显式地把 this 绑定到回调函数以便继续使用实例。
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后, 调用'declare'方法
bind和call以及apply一样,都是可以改变上下文的this指向的。不同的是,call和apply一样,直接引用在方法上,而bind绑定this后返回一个方法,但内部核心还是apply。
var obj = {
a: 1,
b: 2,
getCount: function(c, d) {
return this.a + this.b + c + d;
}
};
c = obj.getCount;
console.log(func(3, 4)); // 7
为何会这样?因为func在上下文中的this是window!bind的存在正是为了改变this指向获取想要的值:
var obj = {
a: 1,
b: 2,
getCount: function(c, d) {
return this.a + this.b + c + d;
}
};
window.a = window.b = 0;
var func = obj.getCount.bind(obj);
console.log(func(3, 4)); // 10
低版本ie6~8兼容写法:
var obj = {
a:1,
b:2,
getCount:function(c,d){
return this.a + this.b + c + d;
}
}
Function.prototype.bind = Function.prototype.bind || function(context){
var that = this;
return function(){
return that.apply(context,arguments);
}
}
window.a = window.b = 0;
var func = obj.getCount.bind(obj);
console.log(func(3,4));
bind的核心是返回一个未执行的方法,如果直接使用apply或者call:
var ans = obj.getCount.apply(obj, [3, 4]);
console.log(ans); // 10
函数前加上波浪号,其作用是把函数声明转换为表达式,这样就可以直接运行。
for(var i = 0;i < 10; i++){
~function(i){
setTimeout(function(){
console.log(i);
},i*1000);
}(i)
}
bind实现
for(var i = 0; i < 10;i++){
setTimeout(console.log.bind(console,i),i*1000)
}
bind是和apply、call一样,是Function的扩展方法,所以应用场景是func.bind(),而传的参数形式和call一样,第一个参数是this指向,之后的参数是func的实参,fun.bind(thisArg[, arg1[, arg2[, ...]]])。
正则表达式
简写形式的元字符:
//
// \w 文字 \W 代表的是非文字 word 文字 默认表示英文和下划线
// \d 数字 \D 代表的是非数字 digit 数字
// \s 空白 \S 代表的是非空白 spcae 空白
. 代表除了换行符以外的任何字符
// 有些开发者使用 [\s\S] 表示增强的 . ---> [\s\S] 代表任意的一个字符
var get = function ( str ) {
// 1 2 3
var r = /^(?:#([\w\-]+)|\.([\w\-]+)|([\w\-]+))$/;
// var r = /^(?:#([\w-]+)|.([\w-]+)|([\w-]+)|(*)+)$/;
var m = r.exec( str );
if ( m[ 1 ] ) {
return [ document.getElementById( m[ 1 ] ) ];
} else if ( m[ 2 ] ) {
return document.getElementsByClassName( m[ 2 ] );
} else if ( m[ 3 ] ){
return document.getElementsByTagName( m[ 3 ] );
}
};
// 分组
// 1> 基本元字符
// . [] () |
. 代表任意的非 换行字符
[]
() 代表分组 和 提高优先级 , 如果仅仅是为了提高优先级 不捕获的话 , 需要使用(?:) 取消捕获
| 代表二者中的一个
// 2> 限定元字符
// + 前面紧跟的字符或组至少 1 个 {1,}
// * 前面紧跟的字符或组至少 0 个 {0,}
// ? 前面紧跟的字符或组出现0次或1次; 如果跟在其他限定符后面表示取消贪婪模式
// {0,1}
// {n} 前面紧跟的字符或组 n 个
// {n,} 前面紧跟的字符或组至少 n 个
// {n,m} 前面紧跟的字符或组 n 到 m 个
// 3> 首尾元字符
// ^ hat 字符串里表示以什么什么开头 方括号里面表示以什么开头
// $ 字符串以什么结尾 引导符
// 4> 简写元字符
// \w \W
// \s \S
// \d \D
性能分析
DOM 设置属性
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
.c {
border: 1px solid red;
width: 400px;
height: 150px;
}
</style>
<script type="text/javascript">
// 2, 在页面中创建 3 个 div, 要求 设置其边框与颜色以及大小
// 1> 直接设置 style 属性
// 2> 使用 类样式
// -> setAttribute
// -> .语法
// onload = function () {
// var i, node;
// for ( i = 0; i < 3; i++ ) {
// node = document.createElement( 'div' );
// // node.setAttribute( 'class', 'c' );
// node.className = 'c';
// document.body.appendChild( node );
// }
// };
// 1, 方法比较多, 练习的过程的中每一个做法都要熟练
// 2, 由于每次循环都使用 document.body.appenChild 因此
// 会导致每次 for 都要刷新页面结构. 应该采用一个临时的数据
// 存储这些 dom 对象, 在 全部创建完成以后, 再一并加入
// 只有创建一个 节点标签, 才可以不影响 整个页面布局, 同时允许存储其他标签
// onload = function () {
// var i, node, container = document.createElement( 'div' );
// for ( i = 0; i < 3; i++ ) {
// node = document.createElement( 'div' );
// // node.setAttribute( 'class', 'c' );
// node.className = 'c';
// container.appendChild( node );
// }
// document.body.appendChild( container );
// };
// 用于缓存文档片段的 DOM 对象 DocumentFragment
onload = function () {
var i, node,
container = document.createDocumentFragment();
for ( i = 0; i < 3; i++ ) {
node = document.createElement( 'div' );
// node.setAttribute( 'class', 'c' );
node.className = 'c';
container.appendChild( node );
}
document.body.appendChild( container );
};
</script>
</head>
<body>
</body>
</html>
或者使用下面的innerHTML:
<script type="text/javascript">
onload = function () {
var i,s = '';
for (var i = 0; i < 10; i++) {
s += '<div>' + i + '</div>';
}
document.body.innerHTML = s;
}
</script>
性能分析:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script>
// 在页面中添加 1000 个div
var count = 50000;
var test1 = function () {
var start = +new Date(),
i, end, node, docfrag;
docfrag = document.createDocumentFragment();
for ( i = 0; i < count; i++ ) {
node = document.createElement( 'div' );
docfrag.appendChild( node );
}
document.body.appendChild( docfrag );
end = +new Date();
console.log( 'test1 = ' + ( end - start ) );
};
var test2 = function () {
var start = +new Date(),
i, end, s;
s = '';
for ( i = 0; i < 1000; i++ ) {
s += '<div></div>';
// document.body.innerHTML += '<div></div>';
}
document.body.innerHTML = s;
end = +new Date();
console.log( 'test2 = ' + ( end - start ) );
};
onload = function() {
// test1();
test2()
};
</script>
</head>
<body>
</body>
</html>
dom中创建代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
// jq 中
// $( '<div style=""></div>' );
// itcast -> I
// var dom = I( '<a href="http://www.baidu.com">一个链接</a>' )
var cElem = function ( html ) {
// 1, 在内部创建一个 docfrag
var docfrag = document.createDocumentFragment();
// 2, 创建真正的 div, 然后设置其 innerHTMl 为出入的字符串
// 然后在遍历该子元素, 将内容加入到 docfrag 中
var div = document.createElement( 'div' );
// 3, 将字符串设置为 它的 innerHTML
div.innerHTML = html;
// 4, 遍历div的子元素, 加入 docfrag 下面为什么这么写呢
// 因为在 DOM 元素中默认有一个特征, 即元素只允许有一个 父节点
// 如果添加元素到另一个节点中, 该元素会自动的离开原来的父节点
//最好不用for循环
while( div.firstChild ) {
docfrag.appendChild( div.firstChild );
}
// 5, 获得其子元素返回
return docfrag;
};
</script>
</head>
<body>
</body>
<script>
var dom = cElem( '<a href="http://www.baidu.com">一个链接</a><br />' +
'<a href="http://www.itcast.cn">传智播客</a>' );
document.body.appendChild( dom );
// I( ... ).appendTo( 'body' );
</script>
</html>
解析while
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var id = function ( id ) {
return document.getElementById( id );
};
onload = function () {
var d1 = id( 'dv1' );
var d2 = id( 'dv2' );
var list = d1.getElementsByTagName("p");
var len;
// dom 节点只能有一个父节点 你把这个节点append到了
//另一个节点上 ,当前这个节点就少了一个节点
//i继续累加 节点本身length也在减少 总在变化
//所以这里让他固定不动就可以了
//这种写法不太好
//比较好使用while循环
// for ( var i = 0, len = list.length; i < len; i++ ) {
//
// d2.appendChild( list[ 0 ] );
//
// }
// while ( list[ 0 ] ) {
// d2.appendChild( list[ 0 ] );
// }
while ( d1.firstChild ) {
d2.appendChild( d1.firstChild );
}
};
</script>
</head>
<body>
<div id="dv1">
<p>p1</p>
<p>p2</p>
<p>p3</p>
<p>p4</p>
</div>
<div id="dv2">
</div>
</body>
</html>
抽离出一种方法, 分析:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
// 将字符串转换成 DOM 对象
var cElem = function ( html ) {
var docfrag = document.createDocumentFragment();
var div = document.createElement( 'div' );
div.innerHTML = html;
while( div.firstChild ) {
docfrag.appendChild( div.firstChild );
}
return {
element: docfrag,
appendTo: function ( dom ) {
dom.appendChild( this.element );
}
};
};
// cElem( '...' ).appendTo( document.body );
// 函数返回 DOM 对象, 没有该方法
// 但是现在需要该方法. 在原型中添加??? 在哪一个原型中添加呢?
// 首先不确定 dom 对象的共有原型, 同时可能引起原型链搜索性能问题
// 其次开发的原则是不影响内置对象成员
// 因此不应该直接在 DOM 对象上添加成员
// 给 DOM 对象提供一个包装对象
// 可以考虑将 cELem 函数返回的对象做一个修改, 然后其是一个 自定义对象
// 该对象中有 appendTo 方法
onload = function () {
cElem( '<div style="border: 1px solid red; width: 200px; height: 100px;"></div>' )
.appendTo( document.body );
};
</script>
</head>
<body>
</body>
</html>