需要为HTML页面上添加一些动态效果, Brendan Eich这哥们在两周之内设计出了JavaScript语言
几个公司联合ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准
$ node -v
v14.17.1
JavaScript的Array可以包含任意数据类型,并通过索引来访问每个元素
var arr1 = new Array(1, 2, 3); // 创建了数组[1, 2, 3]
var arr2 = [1, 2, 3.14, 'Hello', null, true];
越界不报错
arr1[0] // 1
arr1[3] // undefined
// 如果通过索引赋值时,索引超过了范围,统一可以赋值
arr1[3] = 3
arr1[3] // 3
var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的长度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; // []
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
arr.reverse();
arr; // ['C', 'B', 'A']
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
// 如果不给slice()传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个Array
var aCopy = arr.slice();
aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; // false
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成
obj1 = new Object()
obj2 = {}
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性
未定义的属性不报错
obj1.a = 1
obj1.a // 1
obj1.b // undefined
obj1.b = 2
obj1.b // 2
// 删除b属性
delete obj1.b
delete obj1.b // 删除一个不存在的school属性也不会报错
使用hasOwnProperty, 判断对象是否有该属性
obj1.hasOwnProperty('b') // false
null表示一个空的值,而undefined表示值未定义。事实证明,这并没有什么卵用
var a = {a: 1}
a.b // undefined
a.b = null
a.b // null
大于和小于没啥特别的, 要特别注意相等运算符==。JavaScript在设计时,有两种比较运算符:
false == 0; // true
false === 0; // false
var:变量提升(无论声明在何处,都会被提至其所在作用于的顶部)
var age = 20
function f1() {console.log(age)}
f1() // 20
let:无变量提升(未到let声明时,是无法访问该变量的)
{ let a1 = 20 }
a1 // a1 is not defined
const:无变量提升,声明一个基本类型的时候为常量,不可修改;声明对象可以修改
const c1 = 20
c1 = 30 // Assignment to constant variable
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部
function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}
// JavaScript引擎看到的代码相当于
function foo() {
var y; // 提升变量y的申明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'Bob';
}
所以js里面 变量都定义在顶部, 并且大量使用let来声明变量
js里面还有这种你看不懂的骚操作
// 数组属性是index, 解开可以直接和变量对应
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
// 对象解开后是属性, 可以直接导出你需要的属性, 用的地方很多
var {name, age, passport} = person;
这就叫解构赋值
JavaScript的字符串就是用''或""括起来的字符表示
str1 = 'str'
str2 = "str"
使用转义符: \
'I\'m \"OK\"!';
ml = `这是一个
多行
字符串`;
// "这是一个\n多行\n字符串"
格式: 使用``表示的字符串 可以使用${var_name} 来实现变量替换
var name = '小明'
var age = 20
console.log(`你好, ${name}, 你今年${age}岁了!`)// 你好, 小明, 你今年20岁了!
直接使用+号
一种是程序写的逻辑不对,导致代码执行异常
var s = null
s.length
// VM1760:1 Uncaught TypeError: Cannot read property 'length' of null
// at <anonymous>:1:3
如果在一个函数内部发生了错误,它自身没有捕获,错误就会被抛到外层调用函数,如果外层函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被JavaScript引擎捕获,代码终止执行
我们可以判断s的合法性, 在保证安全的情况下,使用
if (s !== null) {s.length}
也可以捕获异常, 阻断其往上传传递
try { s.length } catch (e) {console.log('has error, '+ e)}
// VM2371:1 has error, TypeError: Cannot read property 'length' of null
完整的try ... catch ... finally:
try {
...
} catch (e) {
...
} finally {
...
}
常见实用案例: loading
javaScript有一个标准的Error对象表示错误
err = new Error('异常来')
err
// <!-- Error: 异常来
// at <anonymous>:1:7 -->
err instanceof Error
// true
程序也可以主动抛出一个错误,让执行流程直接跳转到catch块。抛出错误使用throw语句
throw new Error('抛出异常')
// VM3447:1 Uncaught Error: 抛出异常
// at <anonymous>:1:7
// (anonymous) @ VM3447:1
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
上述abs()函数的定义如下:
比如我们定义了一个对象
var person = {name: '小明', age: 23}
那么我们如何给这个对象添加方法喃?
var person = {name: '小明', age: 23}
person.greet = function() {
console.log(`hello, my name is ${this.name}`)
}
person.greet()
绑定到对象上的函数称为方法,和普通函数也没啥区别,但是它在内部使用了一个this关键字
在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量
注意这里的this, 如果你没有绑带在对象上, this 指的是 浏览器的window对象
fn = function() {
console.log(this)
}
fn()
// <ref *1> Object [global] {
// global: [Circular *1],
// clearInterval: [Function: clearInterval],
// clearTimeout: [Function: clearTimeout],
// setInterval: [Function: setInterval],
// setTimeout: [Function: setTimeout] {
// [Symbol(nodejs.util.promisify.custom)]: [Getter]
// },
// queueMicrotask: [Function: queueMicrotask],
// clearImmediate: [Function: clearImmediate],
// setImmediate: [Function: setImmediate] {
// [Symbol(nodejs.util.promisify.custom)]: [Getter]
// },
// fn: [Function: fn]
// }
var person = {name: '小明', age: 23}
person.greetfn = function() {
return function() {
console.log(`hello, my name is ${this.name}`)
}
}
person.greetfn()() // hello, my name is undefined
此时我们可以通过一个变量+ 闭包, 把当前this传递过去, 确保this正常传递
var person = {name: '小明', age: 23}
person.greetfn = function() {
var that = this // 这个很多,别看不懂
return function() {
console.log(`hello, my name is ${that.name}`)
}
}
person.greetfn()() // hello, my name is 小明
在js中你会看到很多这样的语法:
fn = x => x * x
console.log(fn(10))
这就是js特色的箭头函数
x => x * x
// 等价于下面这个函数
function (x) {
return x * x;
}
一个完整的箭头函数语法:
(params ...) => { ... }
我们看看下面列子
axios
.get('http://localhost:8050/hosts', {params: this.query})
.then(response => {
console.log(response)
this.tableData = response.data.data.items
this.total = response.data.data.total
console.log(this.tableData)
})
.catch(function (error) { // 请求失败处理
console.log(error);
});
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定
var person = {name: '小明', age: 23}
person.greetfn = function() {
return () => {
// this 继承自上层的this
console.log(`hello, my name is ${this.name}`)
}
}
person.greetfn()()
所有在js中 到处都是箭头函数
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性
alert("hello")
// 等价于
window.alert("hello")
由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象
var a = 10
a // 10
window.a // 10
甚至我们可以覆盖掉浏览器的内置方法:
alert = () => {console.log("覆盖alert方法")}
() => {console.log("覆盖alert方法")}
alert() // 覆盖alert方法
是不是很骚, 这要是做大项目 就是在玩火, 那如果避免这种问题喃? 使用命名空间
所以我们需要将功能分开, 做成一个一个的模块, 就和Go的pkg一样, 避免一个模块写大后,出现相互覆盖
在js中, 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变
下面是一个 JS 文件,里面使用export命令输出变量
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
为了方便我们也可以写到一行
// profile.js
export {firstName, lastName, year};
我们通过import来导入其他模块中定义的变量, 比如
import { firstName, lastName, year } from './profile.js';
如何导入的变量和本命令空间有冲突, 我们可以使用 import as语法来为变量 进行重命名
import { lastName as surname } from './profile.js';
如果你一不小心 忘记了as, 可能会导致变量覆盖, 这由回到了全局变量的问题, 所以模块导出的时候最好设置模块命名空间
我们可以将我们的所有方法绑定到一个变量上,然后暴露出去,避免导入时覆盖问题, 许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
export MYAPP
其他文件中
import { MYAPP } from './export';
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载, 比如
// 我们必须知道export文件里面 使用export导出了哪些变量, 才知道import时 需要导入哪些
import { MYAPP } from './export';
如果我们想要直接导入模块,再看该模块下有哪些变量可用,就像golang的pkg一样,我们该怎么做, 这个时候就要使用到export default了
export default命令,为模块指定默认输出
export default MYAPP
与export命令的区别:其他模块加载该模块时,import命令可以为该匿名函数指定任意名字,
import myPkg from './export';
myPkg.foo(); // 'foo'
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令
ES版本不同,有2种导入语法:
import/export的语法我们讲解了,至于CommonJs的语法这里简单说下:
一个模块想要对外暴露变量(函数也是变量),可以用module.exports = variable;,一个模块要引用其他模块暴露的变量,用var ref = require('module_name');就拿到了引用模块的变量
这里的机制其实就是为每个文件准备一个module对象, 把它装进去
var module = {
id: 'hello',
exports: {}
};
语法格式:
if (condition) {
...
} else if (condition) {
...
} else {
...
}
注意条件需要加上括号, 其他和Go语言的if一样:
var age = 20;
if (age >= 6) {
console.log('teenager');
} else if (age >= 18) {
console.log('adult');
} else {
console.log('kid');
}
语法格式:
for (初始条件; 判断条件; 修改变量) {
...
}
注意条件需要加上括号:
var x = 0;
var i;
for (i=1; i<=10000; i++) {
x = x + i;
}
x; // 50005000
for循环的一个变体是for ... in循环,它可以把一个对象的所有属性依次循环出来
遍历对象: 遍历出来的属性是元素的key
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}
遍历数组: 一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
for in 有啥问题? 为啥不推荐使用, 我们看下面一个例子
当我们手动给Array对象添加了额外的属性后,for ... in循环将带来意想不到的意外效果
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
console.log(x); // '0', '1', '2', 'name'
}
为什么? 这和for in的遍历机制相关: 遍历对象的属性名称
那如何解决这个问题喃? 答案是 for of
for ... of循环则完全修复了这些问题,它只循环集合本身的元素
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}
但是我们用for of 能遍历对象吗?
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key of o) {
console.log(key);
}
// VM2749:6 Uncaught TypeError: o is not iterable
// at <anonymous>:6:17
变通的方法是: 我们可以通过Object提供的方法获取key数组,然后遍历
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key of Object.keys(o)) {
console.log(key); // 'name', 'age', 'city'
}
forEach()方法是ES5.1标准引入的, 他也是遍历元素的一种常用手段, 也是能作用于可跌倒对象上, 和for of一样
arr.forEach(function(item) {console.log(item )})
当然这还有一种简洁写法
arr.forEach((item) => {console.log(item)})
如果后端返回的数据不满足我们展示的需求, 需要修改,比如vendor想要友好显示,我们可以直接修改数据
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。Javascript通过回调函数实现异步, js的一大特色
function callback() {
console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');
// before setTimeout()
// after setTimeout()
// 等待一秒后
// Done
由此可见并不会真正阻塞1秒, 而是在1秒后调用该函数, 这就是javascript的编程范式: 基于回调的异步
我们来看一个函数, resolve是成功后的回调函数, reject是失败后的回调函数
function testResultCallbackFunc(resolve, reject) {
var timeOut = Math.random() * 2;
console.log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
console.log('call resolve()...');
resolve('200 OK');
}
else {
console.log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}
然后我们把处理成功和失败的函数 作为回调传递给该函数:
function testResultCallback() {
success = (message) => {console.log(`success ${message}`)}
failed = (error) => {console.log(`failed ${error}`)}
testResultCallbackFunc(success, failed)
}
// set timeout to: 0.059809346310547795 seconds.
// call resolve()...
// success 200 OK
可以看出,testResultCallbackFunc()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果
Js把这种编程方式抽象成了一种对象: Promise
interface PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used to resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
下面我们将回调改为Promise对象:
var p1 = new Promise(testResultCallbackFunc)
p1.then((resp) => {
console.log(resp)
}).catch((err) => {
console.log(err)
})
// set timeout to: 0.628561731809246 seconds.
// call resolve()...
// 200 OK
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
从回调函数,到Promise对象,再到Generator函数(不讲, 这是协程方案的一种过度形态),JavaScript异步编程解决方案历程可谓辛酸,终于到了Async/await。很多人认为它是异步操作的最终解决方案
这些需要提到 async函数, async函数由内置执行器进行执行, 这和go func() 有异曲同工之妙
那我们如果声明一个异步函数, 其实很简单 在你函数前面加上一个 async关键字就可以了
async function testWithAsync() {
var p1 = new Promise(testResultCallbackFunc)
try {
var resp = await p1
console.log(resp)
} catch (err) {
console.log(err)
}
}
这里testWithAsync就是一个异步函数, 他执行的时候 是交给js的携程执行器处理的, 而 await关键字 就是 告诉执行器 当p1执行完成后 主动通知我下(协程的一种实现), 其实就是一个 event pool模型(简称epool模型)
我们修改下之前的demo, 使用async 来实现
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。