let和var之间的区别

作用域不同

let仅在块级作用域内有效,而var全局有效。

例:

var a = []
for(var i=0; i<10; i++){
    a[i] = function(){
        console.log(i);
    }
}
a[6]()

如果是var,上述输出10,如果是let则输出6.

JS中的for循环体比较特殊,每次执行都是一个全新的独立的块作用域,用let声明的变量传入到 for循环体的作用域后,不会发生改变,不受外界的影响。

例2 let配合for循环的独特应用:

for (var i = 0; i <10; i++) {  
  setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
    console.log(i);        // 执行此代码时,同步代码for循环已经执行完成
  }, 0);
}
// 输出结果
10   共10个
// 这里面的知识点: JS的事件循环机制,setTimeout的机制等

// i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
for (let i = 0; i < 10; i++) { 
  setTimeout(function() {
    console.log(i);    //  i 是循环体内局部作用域,不受外界影响。
  }, 0);
}
// 输出结果:
0  1  2  3  4  5  6  7  8 9

补充:const定义块级作用域的变量,类似let,不能被重新声明,也无法重新赋值。

变量提升

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。

由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用,这个行为叫做“hoisting”。“hoisting”就像是把所有的变量声明移动到函数或者全局代码的开头位置

let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

例1:

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

例2:

var x = y, y = 'A';
console.log(x + y); // undefinedA

相当于把x,y的声明提到前面,而赋值还是按原来的顺序。

例3:

var x = 0;

function f(){
  var x = y = 1; // x在函数内部声明,y不是!
}
f();

console.log(x, y); // 0, 1
// x 是全局变量。
// y 是隐式声明的全局变量。

例4:

var x = 0;  // x是全局变量,并且赋值为0。

console.log(typeof z); // undefined,因为z还不存在。

function a() { // 当a被调用时,
  var y = 2;   // y被声明成函数a作用域的变量,然后赋值成2。

  console.log(x, y);   // 0 2

  function b() {       // 当b被调用时,
    x = 3;  // 全局变量x被赋值为3,不生成全局变量。
    y = 4;  // 已存在的外部函数的y变量被赋值为4,不生成新的全局变量。
    z = 5;  // 创建新的全局变量z,并且给z赋值为5。
  }         // (在严格模式下(strict mode)抛出ReferenceError)

  b();     // 调用b时创建了全局变量z。
  console.log(x, y, z);  // 3 4 5
}

a();                   // 调用a时同时调用了b。
console.log(x, z);     // 3 5
console.log(typeof y); // undefined,因为y是a函数的本地(local)变量。

暂时性死区(temporal dead zone,简称 TDZ)

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

当某区块中存在let定义变量,则在此区域中,该变量定义之前都不能被使用(也不能使用Typeof),即使区块外存在var同名变量。

例:

var tmp = 123;
if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError
    
  typeof tmp; //  ReferenceError
  typeof undeclared_variable; // undefined

  let tmp = tmp; // 报错
  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 345;
  console.log(tmp); // 345
}

暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

相同作用域下的重复声明

var变量可以重复声明,其值为最后一次修改的值。let变量不能重复声明。

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

脚本调用

  1. 尽量不要将JS函数绑定元素写在HTML中,以便于维护。

  2. 外部引用JS文件时加上属性async(“异步”属性)来解决这一问题,它告知浏览器在遇到 <script> 元素时不要中断后续 HTML 内容的加载。

    例:<script src="script.js" async></script> 这种情况下,脚本和HTML将一起加载。

    内部示例:

    document.addEventListener("DOMContentLoaded", function(){
        ...
    });
    

    :“外部”示例中 async 属性可以解决调用顺序问题,因此无需使用 DOMContentLoaded 事件。而 async 只能用于外部脚本,因此不适用于“内部”示例。

  3. defer控制脚本的运行顺序,添加 defer 属性的脚本将按照在页面中出现的顺序加载。

    当有ABC3个脚本需要运行,而BC依赖于A时,可以使用defer让BC运行次序在A之后。

    例:

    <script defer src="js/vendor/jquery.js"></script>
    <script defer src="js/script2.js"></script>
    <script defer src="js/script3.js"></script>
    

数据类型

Number(不管是整数还是浮点数都是同一种数据类型)\String\Boolean(小写)\Array(中括号)

Boolean

任何不是 false, undefined, null, 0, NaN 的值,或一个空字符串('')在作为条件语句进行测试时实际返回true,因此您可以简单地使用变量名称来测试它是否为真,甚至是否存在(即它不是未定义的)

Object

//定义
let dog = {name:'Spot', breed:'Dalmatian', age:8};
//访问
dog.name 

判断某变量是什么数据类型可用typeof

Note: 使用==!=来判断相等和不相等是有效的,但它们与===/!==不同,前者测试是否相同, 但是数据类型*可能不同,而后者的严格版本测试值和数据类型是否相同。

对象

对象 {name : value}

一个对象由许多的成员组成,每一个成员都拥有name和value。每一个名字/值(name/value)对被逗号分隔开,并且名字和值之间由冒号(:)隔开。

点表示法:对象名.属性名或方法名

括号表示法:对象名['属性名或方法名']

var person = {
  name : ['Bob', 'Smith'],
  age : 32,
  gender : 'male',
  interests : ['music', 'skiing'],
  bio : function() {
    alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
  },
  greeting: function() {
    alert('Hi! I\'m ' + this.name[0] + '.');
  }
};

//点表示法
person.name[0]
person.age
person.interests[1]
person.bio()
person.greeting()
//括号表示法
person['age']

当在成员函数中引用属性时可以通过 this.属性名 来得到属性值。

构造函数

通常以大写字母开头以区分普通函数。

function Person(name) {
  this.name = name;
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

调用

var person1 = new Person('Bob');

也可以使用Object()创建:

//先创建空对象再赋值
var person1 = new Object();
person1.name = 'Chris';
person1['age'] = 38;
person1.greeting = function() {
  alert('Hi! I\'m ' + this.name + '.');
}
//将参数传给Object
var person1 = new Object({
  name : 'Chris',
  age : 38,
  greeting : function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
});

基于现有对象创建新对象 Object.create()

var person2 = Object.create(person1);

对象原型和继承的部分先暂时跳过,等需要用到时再做补充。

字符串和数组之间的转换

以逗号分隔的字符串为例:

//字符串转数组
//1 - split
let myData = 'Manchester,London,Liverpool,Birmingham,Leeds,Carlisle';
let myArray = myData.split(',');
console.log(myArray);
//2 - toString
let dogNames = ["Rocket","Flash","Bella","Slugger"];
console.log(dogNames.toString()); //Rocket,Flash,Bella,Slugger

//数组转字符串 - join
let myNewString = myArray.join(',');
console.log(myNewString);

从结尾操作:

数组添加元素 push() - 返回数组的新长度

数组删除元素 pop() - 返回删除的元素

从开始操作:

数组添加元素 unshift() - 返回数组的新长度

数组删除元素 shift() - 返回删除的元素

语句块

  1. if...else

  2. switch(){case :...break;}

  3. ... ? ... : ... 三元运算符

  4. for( ; ; ) continue break

    for ... in 迭代数组元素及自定义属性

    for ... of 循环可迭代对象(包括Array、Map、Set、arguments 等等)

    let arr = [3, 5, 7];
    arr.foo = "hello";
    
    for (let i in arr) {
       console.log(i); // 输出 "0", "1", "2", "foo"
    }
    
    for (let i of arr) {
       console.log(i); // 输出 "3", "5", "7"
    }
    

    foreach 语法:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

    forEach() 方法按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。

    将for循环转换成foreach:

    const items = ['item1', 'item2', 'item3'];
    const copy = [];
    
    // before
    for (let i=0; i<items.length; i++) {
      copy.push(items[i]);
    }
    
    // after
    items.forEach(function(item){
      copy.push(item);
    });
    
    

    如果数组在迭代时被修改了,则其他元素会被跳过。比如删除了一个元素,则后续元素向前移,跳过一个元素。

    var words = ['one', 'two', 'three', 'four'];
    words.forEach(function(word) {
      console.log(word);
      if (word === 'two') {
        words.shift();
      }
    });
    // one
    // two
    // four
    //跳过了three
    
  5. while(){} / do{}while();

  6. function 函数名(参数名){return ;} 如没有返回值不需要加return语句。

    匿名函数 function(){}

    匿名函数也称为函数表达式。函数表达式与函数声明有一些区别。函数声明会进行声明提升(declaration hoisting),而函数表达式不会。

    函数作用域

    所有函数的最外层被称为全局作用域。 在全局作用域内定义的值可以在任意地方访问。

    函数内定义的变量和其他东西都在它们自己的单独的范围内(函数作用域), 意味着它们不能被函数外的代码访问。调用全局函数时,函数调用与访问变量在同一作用域内。

 posted on 2021-03-11 22:25  _STITCH  阅读(75)  评论(0编辑  收藏  举报