JavaScript 学习记录

在此以做记录,有什么问题还望大家指正。
关键字:算法、模块化、闭包、随机数、浮动、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);
} 

当你输入一个网址的时候,实际会发生什么?

英文地址
翻译地址
stackoverflow

  • 第一步当然是输入网址
  • 第二步浏览器查找域名对于的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 即可。

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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 弘丹参考的是钱穆的《论语新解》以及傅佩荣的《人能弘道-傅佩荣谈论语》,绿窗幽梦参考的是朱熹的《四书章句集注》,由弘...
    弘丹阅读 696评论 0 5
  • (1.14)子曰:君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉,可谓好学也已。 孔子说:一个君子,饮食不求...
    瀚王阅读 946评论 0 1
  • 今天就是个失控的一天。工作、情绪似乎就跟着一个字走一一背! 做为一个物流业者,跟司机、客户都似乎要斗智斗勇斗体力!...
    狮子座秋秋阅读 133评论 3 2