美文网首页
JS基础总结

JS基础总结

作者: 黑色的五叶草 | 来源:发表于2018-10-01 19:17 被阅读0次

    (以下内容,参考自阮一峰es5)

    1. console.log(对象)时,页面弹出的就是[object,Object] 它代表什么?
    alert会调用变量的toString方法,toString会生成[object classname]的结果,
    第一个object表示变量是对象,不是值类型,classname表示该对象的类名。
    [object Object]前面一个object表示他是对象,后面一个Object表示它是Object类的。
    
    1. 函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域
    2. 函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
    var p = 2;
    
    function f(p) {
      p = 3;
    }
    f(p);
    
    p // 2
    

    如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。

    var obj = { p: 1 };
    
    function f(o) {
      o.p = 2;
    }
    f(obj);
    
    obj.p // 2
    

    注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。(本质上是替换了地址)

    var obj = [1, 2, 3];
    
    function f(o) {
      o = [2, 3, 4];
    }
    f(obj);
    
    obj // [1, 2, 3]
    
    1. Object.keys
    • Object.keys方法的参数是一个对象,返回一个数组。该数组的成员都是该对象自身的(而不是继承的)所有属性名。
    var obj = {
      p1: 123,
      p2: 456
    };
    Object.keys(obj) // ["p1", "p2"]
    
    • Object.keys方法用于数组时,返回数组的所有键名。可以看到数组的键名就是整数0、1、2。
    var arr = ['a', 'b', 'c'];
    
    Object.keys(arr)
    // ["0", "1", "2"]
    
    • 区别的原因:由于数组成员的键名是固定的(默认总是0、1、2...),因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
    var arr = ['a', 'b', 'c'];
    
    arr['0'] // 'a'
    arr[0] // 'a'
    
    1. 对象有两种读取成员的方法:点结构(object.key)和方括号结构(object[key])。但是,对于数值的键名,不能使用点结构。
    var arr = [1, 2, 3];
    arr.0 // SyntaxError
    
    1. 数组遍历方法:
    • for
    • while
    • forEach
    var a = [1, 2, 3];
    
    // for循环
    for(var i = 0; i < a.length; i++) {
      console.log(a[i]);
    }
    
    // while循环
    var i = 0;
    while (i < a.length) {
      console.log(a[i]);
      i++;
    }
    
    // 反向遍历,从最后一位开始
    var l = a.length;
    while (l--) {
      console.log(a[l]);
    }
    
    var colors = ['red', 'green', 'blue'];
    colors.forEach(function (color) {
      console.log(color);
    });
    // red
    // green
    // blue
    
    • 不使用for...in,原因是for...in不仅会遍历数组所有的数字键,还会遍历非数字键。
    var a = [1, 2, 3];
    a.foo = true;
    
    for (var key in a) {
      console.log(key);
    }
    // 0
    // 1
    // 2
    // foo
    
    1. 数组的空位
    • 使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性。
    var a = [1, 2, 3];
    delete a[1];
    
    a[1] // undefined
    a.length // 3
    
    • 数组的某个位置是空位,与某个位置是undefined,是不一样的。如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。
    var a = [, , ,];
    
    a.forEach(function (x, i) {
      console.log(i + '. ' + x);
    })
    // 不产生任何输出
    
    for (var i in a) {
      console.log(i);
    }
    // 不产生任何输出
    
    Object.keys(a)
    // []
    
    • 如果某个位置是undefined,遍历的时候就不会被跳过。
    var a = [undefined, undefined, undefined];
    
    a.forEach(function (x, i) {
      console.log(i + '. ' + x);
    });
    // 0. undefined
    // 1. undefined
    // 2. undefined
    
    for (var i in a) {
      console.log(i);
    }
    // 0
    // 1
    // 2
    
    Object.keys(a)
    // ['0', '1', '2']
    
    1. 类数组对象:
    • “类似数组的对象”的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但是有一个问题,这种length属性不是动态值,不会随着成员的变化而变化。
    • 典型的“类似数组的对象”是函数的arguments对象,以及大多数DOM 元素集,还有字符串
    // arguments对象
    function args() { return arguments }
    var arrayLike = args('a', 'b');
    
    arrayLike[0] // 'a'
    arrayLike.length // 2
    arrayLike instanceof Array // false
    
    // DOM元素集
    var elts = document.getElementsByTagName('h3');
    elts.length // 3
    elts instanceof Array // false
    
    // 字符串
    'abc'[1] // 'b'
    'abc'.length // 3
    'abc' instanceof Array // false
    
    • 将类数组对象变成数组的方法:
      1. 使用数组的slice方法,变成真正的数组:Array.prototype.slice.call()
       // arguments对象
       function args() { return arguments }
       var arrayLike = args('a', 'b');
       var arr = Array.prototype.slice.call(arrayLike);
      
      1. 使用数组的forEach方法,通过call()把数组的方法放到对象上面:Array.prototype.forEach.call()
      // arguments对象
      function args() { return arguments }
      var arrayLike = args('a', 'b');
      
      function print(value, index) {
      console.log(index + ' : ' + value);
      }
      
      Array.prototype.forEach.call(arrayLike, print);
      
      • arrayLike代表一个类似数组的对象,本来是不可以使用数组的forEach()方法的,但是通过call(),可以把forEach()嫁接到arrayLike上面调用。

      • 注意,这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。

      var arr = Array.prototype.slice.call('abc');
      arr.forEach(function (chr) {
        console.log(chr);
      });
      // a
      // b
      // c
      
    1. 加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)。由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。
    '3' + 4 + 5 // "345"
     3 + 4 + '5' // "75"
    
    1. 对象的相加:必须先转成原始类型的值,然后再相加。
    var obj = { p: 1 };
    obj + 2 // "[object Object]2"
    

    操作顺序:
    1.首先,自动调用对象的valueOf方法,返回对象自身

     var obj = { p: 1 };
     obj.valueOf() // { p: 1 }
    

    2.再自动调用对象的toString方法,将其转为字符串。

    var obj = { p: 1 };
    obj.valueOf().toString() // "[object Object]"
    
    • 所以,可以通过自定义对象的valueOf()和toString()方法
    var obj = {
      valueOf: function () {
        return 1;
      },
      toString: function () {
        return 'hello';
      }
    };
    
    obj + 2 // 3
    

    由于valueOf方法直接返回一个原始类型的值,所以不再调用toString方法。

    • 这里有一个特例,如果运算子是一个Date对象的实例,那么会优先执行toString方法。
    1. 强制转换_Number()方法,参数是对象时转型过程:
      (1). 调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
      (2). 如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
      (3). 如果toString方法返回的是对象,就报错。

      var obj = {x: 1};
      Number(obj) // NaN
      
      // 等同于
      if (typeof obj.valueOf() === 'object') {
        Number(obj.toString());
      } else {
        Number(obj.valueOf());
      }
      

      上面代码中,Number函数将obj对象转为数值。背后发生了一连串的操作,首先调用obj.valueOf方法, 结果返回对象本身;于是,继续调用obj.toString方法,这时返回字符串[object Object],对这个字符串使用Number函数,得到NaN。

      默认情况下,对象的valueOf方法返回对象本身,所以一般总是会调用toString方法,而toString方法返回对象的类型字符串(比如[object Object])。所以,会有下面的结果。

      Number({}) // NaN
      

      如果toString方法返回的不是原始类型的值,结果就会报错。

      var obj = {
        valueOf: function () {
          return {};
        },
        toString: function () {
          return {};
        }
      };
      
      
      Number(obj)
      // TypeError: Cannot convert object to primitive value
      

      valueOf和toString方法,都是可以自定义的。

      Number({
        valueOf: function () {
          return 2;
        },
        toString: function () {
          return 3;
        }
      })
      // 2
      
    2. parseIntparseFloatNumber的区别:parseInt、parseFloat是对字符换转换;Number可以对任意类型转换

    3. Number()+运算符转换数值时,null转为数值时为0,而undefined转为数值时为NaN。

    4. Object.prototype.toString()

    • toString方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。
      var o1 = new Object();
      o1.toString() // "[object Object]"
      
      var o2 = {a:1};
      o2.toString() // "[object Object]"
      
    • 字符串[object Object]本身没有太大的用处,但是通过自定义toString方法,可以让对象在自动类型转换时,得到想要的字符串形式。
      var obj = new Object();
      
      obj.toString = function () {
        return 'hello';
      };
      
      obj + ' ' + 'world' // "hello world"
      
      上面代码表示,当对象用于字符串加法时,会自动调用toString方法。由于自定义了toString方法,所以返回字符串hello world
    • 数组字符串函数Date 对象都分别部署了自定义的toString方法,覆盖了Object.prototype.toString方法。
      [1, 2, 3].toString() // "1,2,3"
      
      '123'.toString() // "123"
      
      (function () {
        return 123;
      }).toString()
      // "function () {
      //   return 123;
      // }"
      
      (new Date()).toString()
      // "Tue May 10 2016 09:11:31 GMT+0800 (CST)"
      
    • toString() 的应用:判断数据类型
      Object.prototype.toString方法返回对象的类型字符串,因此可以用来判断一个值的类型。
      Object.prototype.toString.call(value)
      

      利用这个特性,可以写出一个比typeof运算符更准确的类型判断函数。

      var type = function (o){
      var s = Object.prototype.toString.call(o);
      return s.match(/\[object (.*?)\]/)[1].toLowerCase();
      };
      
      type({}); // "object"
      type([]); // "array"
      type(5); // "number"
      type(null); // "null"
      type(); // "undefined"
      type(/abcd/); // "regex"
      type(new Date()); // "date"
      
    1. Array.prototype.slice()方法:
      返回数组的深拷贝
      var arr = ['djf', {a:1,b:2,c:[4,5,6]}, 112];
      var copy = arr.slice();
      console.dir(copy);
      copy[0] = 0;
      console.dir(arr);
      
      slice方法的一个重要应用,是将类似数组的对象转为真正的数组。
      Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
      // ['a', 'b']
      
      Array.prototype.slice.call(document.querySelectorAll("div"));
      Array.prototype.slice.call(arguments);
      
    2. Array.prototype.concat()方法:
    • concat方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。
    • 如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是新数组拷贝的是对象的引用。
      var obj = { a: 1 };
      var oldArray = [obj];
      
      var newArray = oldArray.concat();
      
      obj.a = 2;
      newArray[0].a // 2
      
      上面代码中,原数组包含一个对象,concat方法生成的新数组包含这个对象的引用。所以,改变原对象以后,新数组跟着改变。
    1. String.prototype.concat()方法:
    • concat方法用于连接两个字符串,返回一个新字符串,不改变原字符串。
      var s1 = 'abc';
      var s2 = 'def';
      
      s1.concat(s2) // "abcdef"
      s1 // "abc"
      
    • 如果参数不是字符串,concat方法会将其先转为字符串,然后再连接。
      var one = 1;
      var two = 2;
      var three = '3';
      
      ''.concat(one, two, three) // "123"
      one + two + three // "33"
      
      上面代码中,concat方法将参数先转成字符串再连接,所以返回的是一个三个字符的字符串。作为对比,加号运算符在两个运算数都是数值时,不会转换类型,所以返回的是一个两个字符的字符串。
    1. String.prototype.slice()方法:
    • slice方法用于从原字符串取出子字符串并返回,不改变原字符串。它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置)。
      'JavaScript'.slice(0, 4) // "Java"
      
    • String.prototype.substring()
      substring方法用于从原字符串取出子字符串并返回,不改变原字符串,跟slice方法很相像。它的第一个参数表示子字符串的开始位置,第二个位置表示结束位置(返回结果不含该位置)。
      'JavaScript'.substring(0, 4) // "Java"
      
    • String.prototype.substr()
      substr方法用于从原字符串取出子字符串并返回,不改变原字符串。
      substr方法的第一个参数是子字符串的开始位置(从0开始计算),第二个参数是子字符串的长度
      'JavaScript'.substr(0, 4) // "Java"
      
    1. String.prototype.trim()方法:
      trim方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串。
       '  hello world  '.trim()
       // "hello world"
      
    2. 事件循环
    • JavaScript 运行时,除了一个正在运行的主线程,引擎还提供多个任务队列(task queue),里面是各种需要当前程序处理的异步任务。
    • 引擎检查异步任务队列的任务,执行完的任务会重新进入到主线程,这种机制叫:事件循环
    1. 定时器运行机制:
      setTimeoutsetInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。
    • 由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeoutsetInterval指定的任务,一定会按照预定时间执行。
      setTimeout(someTask, 100);
      veryLongTask();
      
      上面代码的setTimeout,指定100毫秒以后运行一个任务。但是,如果后面的veryLongTask函数(同步任务)运行时间非常长,过了100毫秒还无法结束,那么被推迟运行的someTask就只有等着,等到veryLongTask运行结束,才轮到它执行。
    • 再看一个setInterval的例子。
      setInterval(function () {
      console.log(2);
      }, 1000);
      
      sleep(3000);
      
      function sleep(ms) {
        var start = Date.now();
        while ((Date.now() - start) < ms) {
        }
      }
      
      上面代码中,setInterval要求每隔1000毫秒,就输出一个2。但是,紧接着的sleep语句需要3000毫秒才能完成,那么setInterval就必须推迟到3000毫秒之后才开始生效。注意,生效后setInterval不会产生累积效应,即不会一下子输出三个2,而是只会输出一个2。
    • setTimeout(f, 0)会在下一轮事件循环一开始就执行。
      setTimeout(function () {
        console.log(1);
      }, 0);
      console.log(2);
      // 2
      // 1
      
    1. Promise对象:
    1. 除了时间对象Date。其他对象求值都是先调用valueOf()方法,再调用toString()方法
    2. 页面重流和重绘性能优化
    • 读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
    • 缓存 DOM 信息。
    • 不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。
    • 使用documentFragment操作 DOM
    • 动画使用absolute定位或fixed定位,这样可以减少对其他元素的影响。
    • 只在必要时才显示隐藏元素。
    • 使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
    • 使用虚拟 DOM(virtual DOM)库。
    1. History对象:
    • 属性:
      1. History.length:当前窗口访问过的网址数量(包括当前网页)
      2. History.state:History 堆栈最上层的状态值
    • 方法:
      1. History.back()
      2. History.forward()
      3. History.go(): history.go(0)相当于刷新当前页面。
      4. History.pushState:pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有反应。
        window.history.pushState(state, title, url)
        state:一个与添加的记录相关联的状态对象,主要用于popstate事件。
        该事件触发时,该对象会传入回调函数。
        也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页              
        面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。
        title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可      
        以填空字符串。
        url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这    
        个网址。
        
        如果pushState的 URL 参数设置了一个新的锚点值(即hash),并不会触发hashchange事件。反过来,如果 URL 的锚点值变了,则会在 History 对象创建一条浏览记录。
        如果pushState()方法设置了一个跨域网址,则会报错。
      5. History.replaceState(): 方法用来修改 History 对象的当前记录
    • 事件popState: 每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
      注意:
      1. 仅仅调用pushState()方法或replaceState()方法,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用History.back()、History.forward()、History.go()方法时才会触发。

      2. 该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。

      3. 页面第一次加载的时候,浏览器不会触发popstate事件。

    相关文章

      网友评论

          本文标题:JS基础总结

          本文链接:https://www.haomeiwen.com/subject/jlinoftx.html