Javascript基础

571 阅读13分钟

replace, js没有replaceAll方法

实现替换所有匹配字符的功能:

  • 方法一
    这里字符串匹配规则dog,不用加双引号;不能用变量
    let str="dogdogdog";
    let str2 = str.replace(/dog/g, "cat");
  • 方法二
    这里g表示执行全局匹配,m表示执行多次匹配
    let str="dogdogdog";
    let str2 = str.replace(new RegExp("dog", "gm"), "cat");
  • 方法三
    给string对象添加原型方法replaceAll()
    String.prototype.replaceAll = function(s1, s2){
        return this.replace(new RegExp(s1, "gm"), s2);
    }

indexOf

stringObject.indexOf(searchValue, fromIndex);
indexOf其实是两个参数,searchValue必传参数是要搜索的内容;fromIndex是可选参数,从字符串哪个位置开始搜索。

数组操作

这里又一堆土豆,代码表示为:

var potatos = [
{id: '1001', weight: 50}, 
{id: '1002', weight: 80},
{id: '1003', weight: 20},
{id: '1004', weight: 50}]

农民:我要催熟(批量操作)

forEach我可以

  potatos.forEach(item => {
    	item.weight += 20
    }) 

map我也可以,我还可以把重量更新一份给你

    var tmp = potatos.map(item=>{
      item.weight += 20;
      return item.weight;
    });

老板:我只要大土豆

filter: 过滤

var tmp = potatos.filter(item=>item.weight>40);

商贩:你这儿有没有大的?(有符合)

some: 是否有符合条件的,不会全部循环;返回 Boolean 类型

var tmp = potatos.some(item=>item.weight>40); //true

商贩:难道全都是大的吗?(全部符合)

every: 是否全部符合条件;返回 Boolean 类型

var tmp = potatos.every(item=>item.weight>40);

顾客:给我一个大土豆(返回第一个符合的)

find: 返回第一个符合条件的数据

var tmp = potatos.find(item=>item.weight>40);

收银员:这土豆是仓库的第几个来着(返回序号)

findIndex: 返回第一个符合条件的数据的序号

var tmp = potatos.findIndex(item=>item.weight>40);

老板:今年收成如何呀?(递归累加)

 var sum = potatos.reduce((cal, cur)=>{ cal = cal+cur.weight; return cal;}, 0);

reduce()方法接收一个回调函数作为第一个参数,回调函数又接受四个参数,分别是:
1、previousValue: 初始值或上一次回调函数叠加的值;
2、currentValue: 本次回调(循环)将要执行的值;
3、index:currentValue 的索引值;
4、arr: 数组本身
segmentfault.com/a/119000001…

for of 可以用于循环字符串

return

多层循环,如果有一层return了,那么多层会被全部终止。

var testReturn = function () {
    const arr = [0,1,2,3,4,5,6,7,8];
    const size = arr.length;
    for(let i=0;i<size;i++){
    	for(let j=0;j<size;j++){
    		for(let m=0;m<size;m++){
		        console.log('m', m);
		        for(let n=0;n<size;n++){
                    if(n==2){
                    	return 'return...';
                    }
		        }
            }
        }
    }
};
console.log(testReturn());

字符串substring slice substr concat

  • stringObject.substring(start,stop)
  • stringObject.slice(start,end)
  • stringObject.substr(start,length)
  • string.concat(string1, string2, ..., stringX) 也可用来连接字符串 substring特殊的地方有2点:
    (1)如果 start 比 stop 大,那么该方法在提取子串之前会先交换这两个参数。
    (2)与 slice() 和 substr() 方法不同的是,substring() 不接受负的参数。
    let str = "hellojavascript";
    console.log(str.substring(5,9), str.substring(9,5));
    console.log(str.slice(5,9), str.slice(9,5));
    console.log(str.substr(5, 9));

值得注意的是substring slice substr 都不会改变原来字符串的长度和内容

数组concat slice splice

  • arrayObject.concat(arrayX,arrayX,......,arrayX); => 用于连接两个或多个数组
  • arrayObject.slice(start,end); => 从已有的数组中返回指定的元素
  • arrayObject.splice(index,howmany,item1,.....,itemX); => 向/从数组中添加/删除项目,然后返回被删除的项目。

slice 特别的地方是 start\end可以是负数;
如果start > end 返回的是空数组;字符串的话返回的是空字符串。

    let arr = ['j', '9', 'p'];
    console.log(arr.slice(-2,-1), arr.slice(-1,-2));
    let str = 'j9p';
    console.log(str.slice(-2,-1), str.slice(-1,-2));

值得注意的是concat slice都不会改变原来数组,splice会改变
concat slice 本质是元素组的一个浅拷贝,可以实现浅拷贝

    let obj = {name: 'zs'};
    let arr = [obj, 23];
    let cc = arr.concat([]);

    let cc2 = arr.slice();
    let tmp = arr[0];
    tmp.age = 12;
    console.log(arr, cc, cc2);

数组sort返回值

没想到吧, 数组 sort 函数有返回值,返回的是原数组的引用,不是副本。两个数组会相互影响对方。

	let arr = [3, 1, 9];
	let tmp = arr.sort();
    tmp.push(100);
    arr.push(200);
    console.log(arr, tmp);

==和===

=== 我们都知道是全相等:类型相同、值相等才算相等;
== 判断两边数据时会进行类型转换;总结来说的特点:
(1)==左右两边类型相等时 采用三等号也就是严格运算符的判定规则;
(2)类型不同时,其实用的是Number(a)==Number(b)判断;
(3)如果有一个操作数是 NaN,则相等操作符返回 false。
请看下面的例子1:

    if([] == false) {
		console.log("1");
    }

	if({} == false) {
		console.log("2");
    }
    
	if([]) {
		console.log("3");
    }

	if({}) {
		console.log("4");
	}

会输出: 1 3 4
原因:Number(false)=0; Number([])=0; 但是 Number({})=NaN;
[] 和 {} 转为Boolean的规则 都是 true
截图为《Javascript高级程序设计》 多说一些:可以看到对于对象,先调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,然后再依次依照前面的规则转换返回的字符串。
例子2:

    var obj = {
        name: '123',
        toString: function () {
                return -1;
            }
    }

    if(obj==-1){
        console.log(true);  
    }else{
        console.log(false);
    }
    console.log(Number(obj));

返回 true; Number(obj)==-1
例子3:

    console.log({}=={}, {}==!{});
    console.log([]==[], []==![]);

输出:false, false
false, true
因为:{} --> true ; !{} --> false --> 0;
右边:Number({})=NaN;
规则(3)如果有一个操作数是 NaN,则相等操作符返回 false。

因为:[] --> true ; ![] --> false --> 0;
右边:Number([])=0; 0==0

同步sleep函数和异步sleep函数

如果我写的不对,一定要喷我,让我知道

	//同步
    function sleep(delay) {
        let now = new Date();
        const exitTime = now.getTime() + delay;
        while (new Date().getTime()<=exitTime){
            
        }
        console.log("sleep...");
        return;
    }
    console.log("1");
    sleep(1000);
    console.log("2");
    console.log("3")

依次输出:1 sleep... 2 3

	//异步
    var sleep= function(secondNums){
        setTimeout(()=>{
            console.log("setTimeout...")
        }, secondNums);
    }

    console.log("1");
    sleep(1000);
    console.log("2");
    console.log("3")

依次输出:1 2 3 setTimeout...

document.getElementXXX 和 document.querySelector区别

一般来说getElementXXX获取的是 动态集合, querySelector获取的是静态集合。
www.cnblogs.com/songForU/p/…

Object.prototype.toString.call()

Object.prototype.toString.call()可以用来判断数据类型。

  • 我们知道 typeof 也可以判断数据类型,缺点是Array、Object都是 object。

    值得注意的是 typeof 函数变量 返回的是 function
    typeof null 返回 object
    typeof 未声明或者未定义的变量 返回 undefined

    var o1 = { [Symbol.toStringTag]: "A" };
    var o2 = { [Symbol.toStringTag]: null };
    console.log(Object.prototype.toString.call(o1));       // => "[object A]"
    console.log(Object.prototype.toString.call(o2));       // => "[object Object]"
    console.log(Object.prototype.toString.call(1));        // => "[object Number]"
    console.log(Object.prototype.toString.call("a"));      // => "[object String]"
    console.log(Object.prototype.toString.call(false));    // => "[object Boolean]"
    let b;
    console.log(Object.prototype.toString.call(b));        // => "[object Undefined]"
    // console.log(Object.prototype.toString.call(c));     // =>  报错
    console.log(Object.prototype.toString.call(null));     // => "[object Null]"
    let f = function () {};
    console.log(Object.prototype.toString.call(f));     // => "[object Function]"

参考:zhuanlan.zhihu.com/p/118793721

变量和函数声明的“提前”

首先搞清楚两个概念:未声明的变量、未定义的变量
未声明的变量指的是程序中不存在且未声明的变量,程序调用会报错,运行时错误!
未定义的变量指的是程序中已声明但未赋值的变量;程序调用不会报错,返会undefined

    let a ;
    console.log(a);
    console.log(n);

分别输出:undefined, 报错 Uncaught ReferenceError: n is not defined

变量声明的“提前”

  (function() { 
    console.log(a);     //输出undefined  为什么没有报错呢?
    var a= "hello"; 
    console.log(a);     //输出的是hello
  })();

JavaScript解析器将当前作用域内声明的所有变量和函数都会放到作用域的开始处,但是,只有变量的声明被提前到作用域的开始处了,而赋值操作被保留在原处。

函数声明的“提前”

函数的“被提前”还要分两种情况,一种是函数声明,第二种是函数作为值赋值给变量。
第一种情况:函数声明:函数声明和整个函数的定义都被“提前”了。

    console.log(add(3,4));
    function add(a,b) {
        return a+b;
    }

第二种情况:函数作为值赋值给变量

    console.log(sum(3,4));
    var sum = function add(a,b) {
        return a+b;
    }

会报错:Uncaught TypeError: sum is not a function
为什么呢?sum是一个变量,变量的声明会提升,但是变量的赋值并不会被提升,sum是undefined,所以报错。
练习题1:

    printA();
    function printA() {
        console.log("aaa");
    }

    var printB = () => {
        console.log("bbb");
    }

    printB();

    console.log(d);

    printC();
    var printC = ()=>{
        console.log("ccc")
    }
    var d="124";

答案:
aaa
bbb
undefined
报错:Uncaught TypeError: printC is not a function
参考:www.cnblogs.com/hfxm/p/5550… 练习题2:

    var b = 3;
    (function () {
        b=5;
        var b=100;
        var a=c=5;
    })();
    console.log("b", b);
    console.log("c", c);

输出:
b  3
c  5
为什么呢?
对于b来说,因为立即执行函数()()中已经定义了b,所以里面的b被赋值的时候是内部变量;
对于c来说,var a=c=5; 等价于=> var a=c; c=5;这里c=5;相当于 window.c = 5; 所以c是全局变量。

没有块级作用域
再说一个问题 if 语句和 for 循环中的问题。在其他类C的语言中,由花括号封闭的代码块都有自己的作用域。在 JavaScript 中 if 语句或者 for 循环语句中的变量也存在变量声明的“提前”,请看下面的例子:

  function foo(shouldInit){
      if(shouldInit){
          var x = 10;
          for(var i=0;i<10;i++){
              if(i==9){
                  var y = 99;
              }
              console.log(i);
          }
      }
      console.log(x);
      console.log(y);
      console.log(i);
      console.log(j);
  }
  
  foo(false);

分别输出:undefined undefined undefined 报错 ReferenceError: j is not defined
可以看到变量 x y i 的声明都被提前了,虽然没有赋值。由于变量 j 没有声明直接使用是会报错的。

作用域

虽然理解了变量声明和函数声明(定义)的提前,并不能完全明明白白的解决一些题目。少了变量作用域的理解:

    var a = 10;
    (function () {
        console.log(a);
        a = 5;
        console.log(window.a);
    })();

    var b = 10;
    (function () {
        console.log(b);
        var b = 5;
        console.log(window.b);
    })();

还有一点:不使用var定义变量,直接赋值,该变量会被定义在全局上。使用var和不使用var的区别:

    var a = 'aa';
    console.log(delete window.a) // false
    b = 'b';
    console.log(delete window.b) // true

通过'var'声明的全局变量,其实际上是为'window'对象增加了一个不可配置的属性, 而不加'var'声明的全局变量,其实际上是为'window'对象增加了一个可以配置的属性。

DOM事件

DOM0级事件

DOM0级事件的特点:一是简单;二是兼容性好,具有跨浏览器的优势;三缺点是:只能定义一次,后面的会覆盖前面的定义。
使用方法如下:

<body>
    <div id="myDiv">
        <button id="myBtn">点我</button>
    </div>
    <script>
        let myDiv = document.getElementById('myDiv');
        myDiv.onclick = function () {
            console.log("div is clicked")
        };
        let myBtn = document.getElementById("myBtn");
        myBtn.onclick = function () {
            console.log("button is clicked")
        }
        myBtn.onclick = function () {
            console.log("我是覆盖的内容")
        }
    </script>
</body>

点击button,依次输出:"我是覆盖的内容" , "div is clicked"
从结果可以说明2点:
(1)DOM0级事件是处于冒泡阶段的,从 button => div => body => html
(2)DOM0级事件只能定义一次,后面的定义会覆盖掉之前的定义
(3)取消:myBtn.onclick = null

DOM2级事件

DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段。可以在这个阶段对事件做出相应。下图:《Javascript高级程序设计》13.1事件流截图 使用方法如下:

    <div id="myDiv">
        <button id="myBtn">点我</button>
    </div>
    <script>
        let myDiv = document.getElementById('myDiv');
        myDiv.addEventListener("click",()=>{console.log("div is clicked")}, true);
        let myBtn = document.getElementById("myBtn");
        myBtn.addEventListener("click",()=>{console.log("button is clicked")}, true);
        myBtn.addEventListener("click", ()=>{console.log("我是覆盖的内容")}, true);
    </script>

某个元素.addEventListener("click", ()=>{}, true)
click表示事件类型;
()=>{} 绑定的函数;
true:表示在捕获阶段触发的函数;false表示冒泡阶段触发。(默认第三个参数是 false,表示处于冒泡阶段)
(1)DOM2级事件可以定义多次,并且不会覆盖之前的定义;
(2)同时DOM2级事件可以区分事件捕获或者冒泡;
(3)DOM2级事件取消:
myBtn.addEventListener("click", handler, false);
myBtn.removeEventListener("click", handler, false);
注意:handler参数必须是同一个;第三个参数也必须保持一致,同为false,或者同为true

IE事件

由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。 使用方法如下:

    <div id="myDiv">
        <button id="myBtn">点我</button>
    </div>
    <script>
        var myDiv = document.getElementById('myDiv');
        myDiv.attachEvent("onclick",function () {
            console.log("div is clicked");
        });
        var myBtn = document.getElementById("myBtn");
        var handler = function(){
            console.log("button is clicked");
        }
        myBtn.attachEvent("onclick",handler);
        myBtn.attachEvent("onclick", function (e) {
            console.log("我是覆盖的内容");
            e.cancelBubble = true;
        });
        myBtn.detachEvent("onclick", handler);
    </script>

某个元素.attachEvent("onclick", function(){});
前面说了IE8及更早的版本只支持事件冒泡,所以没有第三个参数。还有一点就是阻止事件冒泡的写法也不一样了:e.cancelBubble = true;

兼容写法

<div id="myDiv">
    <button id="myBtn">点我</button>
</div>
<script>
    var EventUtil = {
        addHandle: 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] = null;
            }
        }
    };
    var myBtn = document.getElementById("myBtn");
    EventUtil.addHandle(myBtn, 'click', function (e) {
        console.log("button is clicked")
        window.event? window.event.cancelBubble = true : e.stopPropagation();
    });
    var myDiv = document.getElementById('myDiv');
    EventUtil.addHandle(myDiv, 'click', function () {
        console.log("div is clicked")
    });
</script>

事件委托

事件委托利用了事件冒泡或事件捕获,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。

<ul id="myLinks">
    <li id="goSomewhere">Go somewhere</li>
    <li id="doSomething">Do something</li>
    <li id="sayHi">Say hi</li>
</ul>
<script>
    var EventUtil = {
        addHandle: 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] = null;
            }
        },
        getEvent: function (event) {
            return event ? event : window.event;
        },
        getTarget: function (event) {
            return event.target || event.srcElement;
        },
        preventDefault: function (event) {
            if(event.preventDefault){
                event.preventDefault();
            }else{
                event.returnValue = false;
            }
        },
        stopPropagation: function (event) {
            if(event.stopPropagation){
                event.stopPropagation();
            }else{
                event.cancelBubble = true;
            }
        }

    };
    var list = document.getElementById("myLinks");
    EventUtil.addHandle(list, 'click', function (e) {
        event = EventUtil.getEvent(e);
        var target = EventUtil.getTarget(event);
        switch (target.id) {
            case "doSomething":
                document.title = "I changed the document's title";
                break;
            case "goSomewhere":
                location.href = "http://www.wrox.com";
                break;
            case "sayHi":
                alert("Hi");
                break;
        }
    })
</script>

Math.round()

console.log(Math.round(2.5), Math.round(-2.5), Math.round(-2.51));

Math.round() 函数返回一个数字四舍五入后最接近的整数。
注意,与其他很多语言中的round()函数不同,Math.round()并不总是舍入到远离0的方向(尤其是在负数的小数部分恰好等于0.5的情况下)。
上面代码答案是:3 -2 -3

arguments

  1. arguments对象是所有(非箭头)函数中都可用的局部变量;
  2. arguments对象不是数组,除了length属性和索引元素之外没有任何Array属性;
    function add(a, b){
      console.log(typeof arguments);
      console.log(arguments instanceof Array);
      console.log(Object.prototype.toString.call(arguments));
    }

object
false
[object Arguments]

3.arguments转数组的方法;

    function add(a, b){
      let args = Array.prototype.slice.call(arguments);
      let args2 = [].slice.call(arguments);

      //ES6
      const args3 = Array.from(arguments);
      const args4 = [...arguments];

    }

arguments.callee

仅作知识点补充:

    let factorial = function (n) {
        if(n==1){
            return 1;
        }else{
            return arguments.callee(n-1) * n   // =》 return factorial(n-1) * n;
        }
    }
    console.log(factorial(4));

我们在递归的时候是使用函数名的引用。函数名和这个函数紧紧耦合在一起。我们如果使用arguments.callee的时候,就会解决这个耦合性。 www.cnblogs.com/evilliu/p/1…

闭包

直接说吧,在一个函数A中创建了另一个函数B,B能够访问A中的变量,这就是闭包。
一般定义的函数在执行完毕后,函数内部的变量会被销毁。但是函数A中创建了另一个函数B,执行A过后,A中的变量并不会被销毁。A中的变量和函数B构成了闭包。
www.jianshu.com/p/21a16d44f…

内存泄漏

内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。(内存被占据着无法释放)

由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集例程(方法),因此闭包在IE9之前的版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。如下面的例子:

  function assignHandler(){
      var element = document.getElementById("someElement");
      element.onclick = function(){
          alert(element.id);
      };
  }
  assignHandler();

assignHandler函数执行后, element变量并不会被销毁;一方面是由于闭包的存在,闭包可以访问到定义内部函数的外层函数的变量;另一方面,内部函数调用了 element这个变量 elment.id 。解决方法:

  function assignHandler(){
      var element = document.getElementById("someElement");
      var id = element.id;
      element.onclick = function(){
          alert(id);
      };
      element = null;
  }
  assignHandler();

这样闭包的作用域链中就不会保存HTML元素了,解除对DOM对象的引用,顺利地减少其引用次数,确保正常回收其占用的内存。

闭包的用途:

柯里化, 可以理解为提取接收部分参数,延迟执行,不立即输出结果,而是返回一个接收剩余参数的函数。柯里化,是一个逐步接收参数的过程。
www.jianshu.com/p/2975c25e4…
juejin.im/post/684490…

    function add() {
        let args = Array.prototype.slice.call(arguments);
        let _add = function () {
          args.push(...arguments);
          return _add;
        };
        _add.toString = function () {
            return args.reduce((cur, next)=>cur+next);
          }
        return _add();
    }

模块化

  (function () {
    var a = 10;
    var b = 20;
    function add(num1, num2) {
        var num1 = !!num1 ? num1 : a;
        var num2 = !!num2 ? num2 : b;
        return num1 + num2;
    }
    window.add = add;
  })();

  console.log(add(0, 2));

垃圾回收

Javascript具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。垃圾收集器会按照固定的时间间隔,周期性地执行这一操作。

标记清除

Javascript中最常用的垃圾收集方式是标记清除(mark-and-sweep). 由标记阶段和清除阶段构成,标记阶段将所有的活动对象做上相应的标记,清除阶段把这些没有标记的对象,也就是非活动对象进行回收。在搜索对象并进行标记的时候使用了深度优先搜索,尽可能的从深度上搜索树形结构。

引用计数法

引用计数的含义是跟踪记录每个值被引用的次数。当有其他对象引用这个值时计数器+1,反之引用解除时减一。垃圾回收器就会释放哪些引用次数为0的值所占用的内存。

缺点:无法回收循环引用的存储对象。

function f(){
  var o1 = {};
  var o2 = {};
  o1.p = o2; // o1引用o2
  o2.p = o1; // o2引用o1
}
f();

IE9之前的问题

IE9之前,BOM和DOM中的对象是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现的。而COM对象的垃圾收集机制采用的就是引用计数策略。IE的Javascript引擎是使用标记清除策略来实现的。

So 只要在IE中涉及COM对象,就会存在引用计数循环引用的问题。

var element = document.getElementById("some_element");
var myObject = {};
myObject.element = element;
element.someObject = myObject;

这个例子在一个DOM元素(element)与一个原生Javascript对象(myObject)之间创建了循环引用。由于存在这个循环引用,即使将例子中的DOM从页面中移除,它也永远不会被收回。

为了解决上述问题,IE9把BOM和DOM对象都转换成了真正的Javascript对象。这样,就避免了两种垃圾收集算法并存导致的问题,也就消除了常见的内存泄漏现象。