了解JavaScript

  • JavaScript 是一门跨平台、面向对象的脚本语言,而Java语言也是跨平台的、面向对象的语言,只不过Java是编译语言,是需要编译成字节码文件才能运行的;JavaScript是脚本语言,不需要编译,由浏览器直接解析并执行。
  • JavaScript 是用来控制网页行为的,它能使网页可交互;那么它可以做什么呢?如改变页面内容、修改指定元素的属性值、对表单进行校验等

JavaScript引入方式

JavaScript 引入方式就是 HTML 和 JavaScript 的结合方式。JavaScript引入方式有两种:

  • 内部脚本:将 JS代码定义在HTML页面中
  • 外部脚本:将 JS代码定义在外部 JS文件中,然后引入到 HTML页面中

内部脚本

在 HTML 中,JavaScript 代码必须位于 <script></script> 标签之间

参考代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    alert("hello js1");
</script>
</body>
</html>
Warning
  • 在 HTML 文档中可以在任意地方,放置任意数量的<script>标签但一般把脚本置于<body> 元素的底部,可改善显示速度
  • 因为浏览器在加载页面的时候会从上往下进行加载并解析。 我们应该让用户看到页面内容,然后再展示动态的效果。

外部脚本

第一步:定义外部 js 文件。如定义名为 demo.js的文件
demo.js 文件内容如下:

alert("hello js");

第二步:在页面中引入外部的js文件
在页面使用 script 标签中使用 src 属性指定 js 文件的 URL 路径。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="../js/demo.js"></script>
</body>
</html>
Warning
  • 外部脚本不能包含 <script> 标签。在js文件中直接写 js 代码即可,不要在 js文件 中写 script 标签
  • <script> 标签不能自闭合,在页面中引入外部js文件时,不能写成 <script src="../js/demo.js" />

基础语法

变量声明关键字

其实JS变量可以不使用关键字声明——(默认是类似的使用var但又有所不同)

var

  1. 通过 var 关键词声明的变量没有块作用域。在块 {} 内声明的变量可以从块之外进行访问。

    { 
      var x = 10; 
    }
    // 此处可以使用 x
    
    
  2. 通过 var 声明的变量会提升到顶端。您可以在声明变量之前就使用它

  3. 重新声明变量:

    var x = 10;
    // 此处 x 为 10
    { 
      var x = 6;
      // 此处 x 为 6
    }
    // 此处 x 为 6
    
    
  4. HTML 中的全局变量:

    使用 JavaScript 的情况下,全局作用域是 JavaScript 环境。

    在 HTML 中,全局作用域是 window 对象。

    通过 var 关键词定义的全局变量属于 window 对象:

    var carName = "porsche";
    // 此处的代码可使用 window.carName
    

let

  • 具有块级作用域
{ 
  let x = 10;
}
// 此处不可以使用 x
  • 解决var重新声明变量带来的问题

    var x = 10;
    // 此处 x 为 10
    { 
      let x = 6;
      // 此处 x 为 6
    }
    // 此处 x 为 10
    

const

关键字 const 有一定的误导性。不是真正的常数.

它没有定义常量值。它定义了对值的常量引用。

因此,我们不能更改常量原始值,

const PI = 3.141592653589793;
PI = 3.14;      // 会出错
PI = PI + 10;   // 也会出错
// ------------------------------------
const car = {type:"porsche", model:"911", color:"Black"};
car = {type:"Volvo", model:"XC60", color:"White"};    // ERROR
// ------------------------------------
const cars = ["Audi", "BMW", "porsche"];
cars = ["Honda", "Toyota", "Volvo"];    // ERROR

但我们可以更改常量对象的属性。

// 您可以创建 const 对象:
const car = {type:"porsche", model:"911", color:"Black"};

// 您可以更改属性:
car.color = "White";

// 您可以添加属性:
car.owner = "Bill";

//----------------------------------------
// 您可以创建常量数组:
const cars = ["Audi", "BMW", "porsche"];

// 您可以更改元素:
cars[0] = "Honda";

// 您可以添加元素:
cars.push("Volvo");

数据类型

所有 JavaScript 值,除了原始值,都是对象。原始值指的是没有属性或方法的值。

原始数据类型指的是拥有原始值的数据。

JavaScript 定义了 5 种原始数据类型:

  • string
  • number
  • boolean
  • null
  • undefined(未定义,不存在,log()为undefined)
Tip

typeof 运算符返回一个用来表示表达式的数据类型的字符串

顺带一提:input中的value始终是string

数据结构

  • 数组——[]
  • json——{}
  • 字典

其实以上,都属于Object【对象】

JavaScript 中的 JSON 序列化和反序列化是将 JavaScript 对象与 JSON 字符串相互转换的过程,通常用于数据的传输和存储。在这些过程中,涉及到 JSON 字符串的转译(转义)和反转译(反转义)。

  1. JSON序列化(转译)

    • JSON序列化是将 JavaScript 对象转换为 JSON 字符串的过程。这个过程会对 JavaScript 对象中的特殊字符进行转义,确保生成的 JSON 字符串是有效的。
    • 例如,当 JavaScript 对象中包含特殊字符(如双引号、斜杠、换行符等)时,这些字符会被转义为它们的转义序列,例如 \n 表示换行符,\" 表示双引号,\\ 表示斜杠等。
  2. JSON反序列化(反转译)

    • JSON反序列化是将 JSON 字符串转换为 JavaScript 对象的过程。在这个过程中,JSON字符串中的转义字符会被还原为它们原本的字符形式。
    • 例如,当 JSON 字符串中包含 \n\"\\ 等转义序列时,它们会被解析为换行符、双引号和斜杠等。

下面是一个简单的示例,演示了 JSON 序列化和反序列化的过程:

// 定义一个 JavaScript 对象
var obj = {
  name: "John",
  age: 30,
  city: "New York"
};

// 将 JavaScript 对象序列化为 JSON 字符串
var jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出 '{"name":"John","age":30,"city":"New York"}'

// 将 JSON 字符串反序列化为 JavaScript 对象
var newObj = JSON.parse(jsonString);
console.log(newObj); // 输出 { name: 'John', age: 30, city: 'New York' }

在这个示例中,JSON.stringify() 函数将 JavaScript 对象 obj 序列化为 JSON 字符串 jsonString,而 JSON.parse() 函数将 JSON 字符串 jsonString 反序列化为 JavaScript 对象 newObj。在序列化过程中,对象的属性被转义为 JSON 格式,而在反序列化过程中,转义字符被还原为原始的对象属性。

.forEach循环

for循环语法大差不差,主要留意增强for语法的特点
.forEach循环中,breakcontinuereturn这些关键字并不适用,因为.forEach内部实际上是一个匿名函数,而且.forEach不支持breakcontinue

两种跳出.forEach循环的方法:

  1. 改用普通for循环:由于for循环支持breakcontinue,可以通过将.forEach转换为for循环来实现循环的跳出。
  2. 使用try...catch结构抛出异常:在.forEach的回调函数中,可以在特定条件下抛出异常,然后在外部的catch块中处理跳出循环后的逻辑。

js不存在方法重载

js中函数声明会提前,如果有多个函数名相同的函数(不管参数个数),那么最后一个声明的函数将会有效,会覆盖前面相同函数名字的声明。 例如:

function f1(a, b){  
  
}  
  
function f1(a, b, c){  
}  

f1(a,b)将会调用最后一个f1函数。因为后面个f1覆盖了前面个f1的声明。

但是可以通过argument对象的length属性实现类似方法重载的效果
注意:argument对象每个函数调用时隐藏起来的参数对象,获取其所有参数,推荐将其转换成数组再获取
参考文档:Javascript中arguments对象的详解和使用方法 (yjbys.com)如图所示:

常用方法

参考文章:

强大的eval

JavaScript 中的 eval() 函数是一个强大而灵活的功能,它可以将字符串作为 JavaScript 代码来执行。虽然 eval() 具有强大的功能,但也存在一些潜在的安全风险和性能问题,因此在使用时需要谨慎考虑。

下面是 eval() 函数的一些特点和用法:

  1. 执行字符串中的 JavaScript 代码

    • eval() 函数接受一个字符串参数,将这个字符串作为 JavaScript 代码来执行,并返回执行结果。
  2. 动态执行代码

    • 由于 eval() 可以接受任意字符串作为参数,因此可以用来动态执行代码,包括动态生成函数、对象、表达式等。
  3. 访问局部变量和全局变量

    • eval() 在执行字符串中的代码时,可以访问包含它所在的函数作用域中的局部变量,以及全局作用域中的全局变量。
  4. 引入潜在的安全风险

    • 因为 eval() 可以执行任意字符串作为代码,所以可能会被用于执行恶意代码或者导致安全漏洞。
  5. 性能问题

    • 由于 eval() 需要在运行时解析和执行字符串中的代码,所以会比直接执行预先定义好的函数或代码块更慢,可能会影响性能。
  6. 用于动态生成代码

    • 在某些情况下,eval() 可以用于动态生成代码,例如动态创建函数、动态生成对象属性等。

虽然 eval() 具有强大的功能,但是由于潜在的安全风险和性能问题,通常建议尽量避免使用 eval(),特别是在处理用户输入或者动态生成的代码时。如果确实需要使用 eval(),应该谨慎验证和过滤输入,并尽量避免在生产环境中使用。

变量判断

1、使用相等运算符“==”和“===”:

	- ==(双等号)是比较两个值是否相等,它会进行类型转换后再比较。
	- ===(三等号)是比较两个值是否严格相等,不会进行类型转换。
2、利用数组的toString()方法:
	- 将数组转换为字符串,然后进行比较。这种方法适用于一维数组,但对于二维数组或包含特殊值(如nullundefined、对象、函数等)的数组可能会出错。

3、使用ES6中的Object.is()方法:
	-Object.is()用于比较两个值是否相等,它在处理+0-0NaN等特殊情况时与===有所不同。
4、使用JSON.stringify()将对象或数组转换为字符串:
	- 这种方法可以弥补===无法准确比较对象的局限,但同样有局限性,比如在遇到symbol值时会忽略。

小结:隐性类型转换可能会带来便利,但也可能导致不易发现的bug。因此,推荐在比较时使用严格比较运算符===。在比较引用类型时,===会比较变量的引用是否相同,而不是值本身。如果引用不同,即使值相同也会返回false

字符串处理

字符串转数字

Number、parseInt、parseFloat都会自动过滤字符串前导和后缀的空格

  • 转换函数:
    • parseInt('123')
    • parseFloat('89')
  • 强制类型转换
    • Number(value)——把给定的值转换成数字(可以是整数或浮点数)

利用字符串特性保留小数位数

  • 四舍五入:数值类型.toFixed(n)——返回结果是字符串,n是位数

    var num =2.446242342;
    num = num.toFixed(2);  // 输出结果为 2.45
    
  • 巧用正则匹配-当作字符串,使用正则匹配:

    Number(15.7784514000.toString().match(/^\d+(?:\.\d{0,2})?/))   
    // 输出结果为 15.77,不能用于整数如 10 必须写为10.0000
    

    问题很明显:使用正则匹配保留两位数的前提是有足够多的位数

截取

  • substr():第一个参数表示元素索引可以为负数, 第二个参数表示截取的字符串长度。
  • substring():两个参数都不接受负数,都表示元素索引,如果 第一个参数 比 第二个参数大,那么该方法在提取子串之前会先交换这两个参数。
  • slice(): 两个参数都接受负数,都表示元素索引, 如果 第一个参数 比 第二个参数大,返回空字符串。

判断是否是字符类型

typeof value == 'number'

返回的是数字类型的字符串表现形式,最终还是要通过字符串比较来判断字符串

格式输出

在JavaScript中,格式化输出通常指的是将数据以一种易于阅读的方式展示出来。这可以通过多种方式实现,包括使用模板字符串、console.log()函数的不同参数,以及一些库如util.format()(在Node.js中)等。以下是一些常见的格式化输出的例子:

  1. 使用模板字符串(Template Literals)

    • 模板字符串允许嵌入表达式
    const name = 'World';
    const greeting = `Hello, ${name}!`;
    console.log(greeting); // 输出:Hello, World!
    
    • 模板字符串允许你创建多行字符串,而不需要使用转义字符(如\n
    const multiLine = `This is a string
    that spans multiple
    lines without needing to use
    escape characters like \n`;
    console.log(multiLine);
    

2. **使用`console.log()`输出多个值**:
   - `console.log()`可以用来输出一个或多个值,它们会以空格分隔。

   ```javascript
   const a = 1;
   const b = 'text';
   console.log(a, b); // 输出:1 text
  1. 使用console.dir()列出对象的属性

    • console.dir()用于列出对象的所有可枚举属性。
    const obj = { a: 1, b: 2 };
    console.dir(obj); // 输出对象的所有属性
    
  2. 使用JSON.stringify()将对象转换为字符串

    • JSON.stringify()可以将对象转换为JSON格式的字符串,这对于调试复杂对象非常有用。
    const obj = { a: 1, b: { c: 3 } };
    console.log(JSON.stringify(obj, null, 2)); // 输出格式化的JSON字符串
    
  3. 使用util.format()(Node.js)

    • 在Node.js中,util.format()提供了一种格式化字符串的方法,类似于C语言的printf
    const util = require('util');
    const formatted = util.format('Hello %s! The value of pi is approximately %d.', 'World', 3.14159);
    console.log(formatted); // 输出:Hello World! The value of pi is approximately 3.
    

删除字符

Warning

在JavaScript中,字符串是不可变的,这意味着一旦创建了字符串,就不能直接修改它的内容。字符串的方法如 substring() 返回的是一个新的字符串,而不是对原始字符串进行修改。

  • str.subString():
    str.substring(4); 括号里只有一位数时,取的是str最后几位的字符串
    var str="aabbccdd";
    console.info(str.substring(4));  //得到ccdd,需要用变量接收
    
  • string.replace(regexp, newSubstr|function),只要newSubstr为空字符串,那么就能达到删除字符的效果,当然正则更多用来匹配替换而不仅仅是删除字符

如果 newSubstr 是一个函数,则会为每个匹配的部分调用该函数,并将其返回值作为替换字符串。例如:

var str = "Hello World";
var newStr = str.replace(/(\w+)\s(\w+)/, function(match, p1, p2) {
  return p2 + ', ' + p1;
});
console.log(newStr); // 输出 "World, Hello"

在这个示例中,正则表达式 /\w+\s\w+/ 匹配了 "Hello World" 中的 "Hello" 和 "World",然后通过一个函数来交换它们的位置,并返回新的字符串。

附录(零碎)

将RGB颜色值转换为十进制

                if (danmu[i].v2_color) {

                    color = (danmu[i].v2_color.color_left.r << 16) + (danmu[i].v2_color.color_left.g << 8) + (danmu[i].v2_color.color_left.b);

                }

js输出

JavaScript 能够以不同方式“显示”数据:

  • 使用 window.alert() 弹出警告框
  • 使用 document.write() 写入 HTML 输出
  • 使用 innerHTML 修改 HTML 元素
    • 先通过方法获取html元素对象
    • 更改 HTML 元素的 innerHTML 属性是在 HTML 中显示数据的常用方法。
  • 使用 console.log() 写入浏览器控制台

函数

函数声明

function functionName(parameters) {
   要执行的代码
}

函数表达式(匿名函数)

var x = function (a, b) {return a * b};
var z = x(4, 3);

上面的函数实际上是一个匿名函数(没有名称的函数)。

存放在变量中的函数不需要函数名。他们总是使用变量名调用。

上面的函数使用分号结尾,因为它是可执行语句的一部分。

自调用函数

函数表达式可以作为“自调用”。

自调用表达式是自动被调用(开始)的,在不进行调用的情况下。

  • 函数表达式会自动执行,假如表达式后面跟着 ()
  • 您需要在函数周围添加括号,以指示它是一个函数表达式:
(function () {
    var x = "Hello!!";      // 我会调用我自己
})();

箭头函数(Lambda表达式)

// ES5
var x = function(x, y) {
  return x * y;
}

// ES6
const x = (x, y) => x * y;

箭头函数未被提升。它们必须在使用前进行定义。

使用 const 比使用 var 更安全,因为函数表达式始终是常量值。

箭头函数的this来自上下文,它们不适合定义对象方法。
const obj = {
 method: () => {
   console.log(this); // 这里的this指向的是箭头函数创建时的上下文,而不是obj
 }
};

// 正确的做法是使用传统的function关键字定义方法
const obj = {
 method: function() {
   console.log(this); // 这里的this指向obj
 }
};

函数是对象

JavaScript 函数都有属性方法

  • arguments.length 会返回函数被调用时收到的参数数目:

    function myFunction(a, b) {
        return arguments.length;
    }
    
  • toString() 方法以字符串返回函数:

    function myFunction(a, b) {
        return a * b;
    }
    
    var txt = myFunction.toString();
    

JavaScript call() 方法

call() 方法是预定义的 JavaScript 方法。

它可以用来调用所有者对象作为参数的方法。

通过 call(),您能够使用属于另一个对象的属性作为参数值的方法。而且call() 方法可接受参数

var person = {
  fullName: function(city, country) {
    return this.firstName + " " + this.lastName + "," + city + "," + country;
  }
}
var person1 = {
  firstName:"Bill",
  lastName: "Gates"
}
person.fullName.call(person1, "Seattle", "USA");

上述代码中如果只是调用person.fullName()那么this指代的就是person的实例对象,获取调用对象本身的属性值,而使用了call()并传递person1对象作为参数,那么this指代的就是person1的实例对象

JavaScript apply()方法

类似call(),区别在于

call() 方法分别接受参数。

apply() 方法接受数组形式的参数。

如果要使用数组而不是参数列表,则 apply() 方法非常方便。

var person = {
  fullName: function(city, country) {
    return this.firstName + " " + this.lastName + "," + city + "," + country;
  }
}
var person1 = {
  firstName:"Bill",
  lastName: "Gates"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);

嵌套函数和闭包

嵌套函数

所有函数都有权访问全局作用域。

事实上,在 JavaScript 中,所有函数都有权访问它们“上面”的作用域。

在本例中,内部函数 plus() 可以访问父函数中的 counter 计数器变量:

function add() {
    var counter = 0;
    function plus() {counter += 1;}
    plus();     
    return counter; 
}

闭包

变量 add 的赋值是自调用函数的返回值。

这个自调用函数只运行一次。它设置计数器为零(0),并返回函数表达式。

这样 add 成为了函数。最“精彩的”部分是它能够访问父作用域中的计数器。

这被称为 JavaScript 闭包。它使函数拥有“私有”变量成为可能。

计数器被这个匿名函数的作用域保护,并且只能使用 add 函数来修改。

闭包指的是有权访问父作用域的函数,即使在父函数关闭之后。

var add = (function () {
    var counter = 0;
    return function () {return counter += 1;}
})();

add();
add();
add();

// 计数器目前是 3 

回调函数和异步

回调和异步往往同时出现

什么是回调函数

"I will call back later!"

回调 (callback) 是作为参数传递给另一个函数的函数

这种技术允许函数调用另一个函数

回调函数可以在另一个函数完成后运行(等我干完这件事再回头调用你

什么是异步

"I will finish later!"

与其他函数并行运行的函数称为异步(asynchronous)

一个很好的例子是 JavaScript setTimeout()——>每一次超时执行一次设定好的回调函数

实例:等待文件

如果您创建函数来加载外部资源(如脚本或文件),则在内容完全加载之前无法使用这些内容。

这是使用回调的最佳时机。

此例加载一个 HTML 文件 (mycar.html),并在文件完全加载后在网页中显示该 HTML 文件:

function myDisplayer(some) {
  document.getElementById("demo").innerHTML = some;
}

function getFile(myCallback) {
  let req = new XMLHttpRequest();
  req.open('GET', "mycar.html");
  req.onload = function() {
    if (req.status == 200) {
      myCallback(this.responseText);
    } else {
      myCallback("Error: " + req.status);
    }
  }
  req.send();
}

getFile(myDisplayer);

在上面的示例中,myDisplayer 用作回调。函数(函数名)作为参数传递给 getFile()

——>执行完getFile()后就会去回调myDisplayer<——

类和对象

  1. 对象(Object)

    • JavaScript 是一种基于对象的语言,几乎所有的东西都是对象,包括基本数据类型(如字符串、数字、布尔值)、函数和数组等。
    • 对象是由属性(properties)和方法(methods)组成的集合,每个属性都是一个键值对,其中键(属性名)是字符串,值可以是任意类型的数据。
  2. 类(Class)

    • 在 ES6(ECMAScript 2015)之后,JavaScript 引入了类的概念,使得 JavaScript 具有了更加传统面向对象编程语言的特性。
    • 类是对象的蓝图,用于创建具有相似属性和行为的对象。
    • 类可以包含属性和方法,可以通过类来创建对象的实例。
    • 类可以使用构造函数来初始化对象的状态,并且可以使用 extends 关键字来实现类的继承。

类的定义和创建

类是对象的模板,通过类来new出对象,类有类属性、类方法,对象有对象属性、对象方法
——>而类中还始终添加 constructor() 方法,默认空参构造<——

class Car {
  constructor(name, year) {
    this.name = name;
    this.year = year;
  }
  age(x) {
    return x - this.year;
  }
}

let date = new Date();
let year = date.getFullYear();

let myCar = new Car("Ford", 2014);
document.getElementById("demo").innerHTML=
"My car is " + myCar.age(year) + " years old.";

注意事项:

类的实例方法

Hoisting

类声明不会被提升。

这意味着您必须先声明类,然后才能使用它

对于其他声明,如函数,在声明之前尝试使用它时不会出错,因为 JavaScript 声明的默认行为是提升(将声明移到顶部)。

类的继承

如需创建类继承,请使用 extends 关键字。super() 方法引用父类。

使用类继承创建的类继承了另一个类的所有方法:

class Car {
  constructor(brand) {
    this.carname = brand;
  }
  present() {
    return 'I have a ' + this.carname;
  }
}

class Model extends Car {
  constructor(brand, mod) {
    super(brand);
    this.model = mod;
  }
  show() {
    return this.present() + ', it is a ' + this.model;
  }
}

let myCar = new Model("Ford", "Mustang");
document.getElementById("demo").innerHTML = myCar.show();

getter/setter 方法

  • 类中添加 getter 和 setter,请使用 getset 关键字。

  • 即使 getter 是一个方法,当你想要获取属性值时也不要使用括号。

  • getter/setter 方法的名称不能与属性名称相同——>可以在属性名称前使用下划线字符 _ 将 getter/setter 与实际属性分开

getter与setter:

class Car {
  constructor(brand) {
    this._carname = brand;
  }
  get carname() {
    return this._carname;
  }
  set carname(x) {
    this._carname = x;
  }
}

let myCar = new Car("Ford");
myCar.carname = "Volvo";
document.getElementById("demo").innerHTML = myCar.carname;

static 类方法

static 类方法是在类本身上定义的。

您不能在对象上调用 static 方法,只能在对象类上调用。

如果要在 static 方法中使用 实例类对象,可以将其作为参数发送:

class Car {
  constructor(name) {
    this.name = name;
  }
  static hello(x) {
    return "Hello " + x.name;
  }
}
let myCar = new Car("Ford");
document.getElementById("demo").innerHTML = Car.hello(myCar);

对象

那如何不通过类这一模板而直接创建一个对象呢,如下:

var myObject = {
    firstName:"Bill",
    lastName: "Gates",
    fullName: function () {
        return this.firstName + " " + this.lastName;
    }
}

函数调用

  • 函数形式调用

    function myFunction(a, b) {
        return a * b;
    }
    myFunction(10, 2);           // 将返回 20
    
  • 作为方法调用函数: 在 JavaScript 中,您可以把函数定义为对象方法。
    下面的例子创建了一个对象(myObject),带有两个属性(firstName 和 lastName),以及一个方法(fullName):

    var myObject = {
        firstName:"Bill",
        lastName: "Gates",
        fullName: function () {
            return this.firstName + " " + this.lastName;
        }
    }
    myObject.fullName();         // 将返回 "Bill Gates"