JavaScript基础学习之一
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() // 不报错
脚本调用
-
尽量不要将JS函数绑定元素写在HTML中,以便于维护。
-
外部引用JS文件时加上属性
async
(“异步”属性)来解决这一问题,它告知浏览器在遇到<script>
元素时不要中断后续 HTML 内容的加载。例:
<script src="script.js" async></script>
这种情况下,脚本和HTML将一起加载。内部示例:
document.addEventListener("DOMContentLoaded", function(){ ... });
注:“外部”示例中
async
属性可以解决调用顺序问题,因此无需使用DOMContentLoaded
事件。而async
只能用于外部脚本,因此不适用于“内部”示例。 -
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()
– 返回删除的元素
语句块
-
if...else
-
switch(){case :...break;}
-
... ? ... : ...
三元运算符 -
for( ; ; )
continue breakfor ... 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
-
while(){} / do{}while();
-
function 函数名(参数名){return ;}
如没有返回值不需要加return语句。匿名函数
function(){}
匿名函数也称为函数表达式。函数表达式与函数声明有一些区别。函数声明会进行声明提升(declaration hoisting),而函数表达式不会。
函数作用域
所有函数的最外层被称为全局作用域。 在全局作用域内定义的值可以在任意地方访问。
函数内定义的变量和其他东西都在它们自己的单独的范围内(函数作用域), 意味着它们不能被函数外的代码访问。调用全局函数时,函数调用与访问变量在同一作用域内。