JavaScript - 基础知识学习笔记

262 阅读39分钟

修改日志

修改日期修改内容
2024.04.11JavaScript概述、变量、数字与运算符、数组,字符串和类型转换
2024.04.15流程控制,函数,对象,Web API
2024.04.16函数进阶,值类型和引用类型,异常处理,OOP

1. JavaScript 概述

JavaScript,通常缩写为 JS,是一种高级的,解释执行的脚本编程语言。JavaScript 是一门基于原型、函数先行的语言,是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。它提供语法来操控文本、数组、日期以及正则表达式等,不支持 I/O,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。

虽然 JavaScript 与 Java 这门语言不管是在名字上,或是在语法上都有很多相似性,但这两门编程语言从设计之初就有很大的不同,JavaScript 的语言设计主要受到了 Self(一种基于原型的编程语言)和 Scheme(一门函数式编程语言)的影响。在语法结构上它又与 C 语言有很多相似,例如 if 条件语句、while 循环、switch 语句、do-while 循环等。

在客户端,JavaScript 在传统意义上被实现为一种解释语言,但在最近,它已经可以被即时编译(JIT)执行。随着最新的 HTML5 和 CSS3 语言标准的推行,它还可用于游戏、桌面和移动应用程序的开发和在服务器端网络环境运行,如 Node.js。

JavaScript 的组成

  • ECMAScript:JavaScript 的语法标准。
  • DOM:JavaScript 操作网页上的元素的 API。
  • BOM:JavaScript 操作浏览器的部分功能的 API。

JavaScript 的特点

  • 可以使用任何文本编辑工具编写,然后使用浏览器就可以执行程序。
  • 是一种解释型脚本语言:代码不进行预编译,从上往下逐行执行,不需要进行严格的变量声明。
  • 主要用来向 HTML 页面添加交互行为。

脚本语言和编译语言

脚本语言 Scripting Language

脚本语言(Scripting Language)是一种编程语言,用于编写用于控制应用程序或软件的脚本。脚本通常以文本文件的形式存在,并由解释器逐行解释执行。脚本语言通常用于自动化任务、批处理处理、网页开发等领域。常见的脚本语言包括 JavaScript、Python、Ruby、Perl、Shell 脚本等。

编译语言 Compiled Language

编译语言(Compiled Language)也是一种编程语言,它的源代码在运行前需要经过编译器的编译过程,将源代码转换成机器语言的可执行文件。当然,也有一些编译语言可以通过解释器来执行,但通常它们都会经历一个编译的阶段,以生成中间代码或者直接生成可执行代码。常见的编译语言包括 C、C++、Java、Go 等。

脚本语言和编译语言的主要区别在于执行方式和编译过程。脚本语言的执行通常更灵活,可以直接在解释器或运行时环境中逐行执行脚本代码,而编译语言需要先将代码编译成可执行文件,然后才能执行。

补充说明

  1. JavaScript 代码是放在 <script>……</script> 标签里,而包含 JavaScript 代码的 script 标签,我们可以放在 <body>……</body> 标签里,也可以放在 <head>……</head> 标签里
  2. 编写html时,使用外部引入的方式执行js代码

2. JavaScript 变量类型与注释

变量的命名规则如下:

  • 变量名必须以字母、下划线 “_”、美元符号 “$” 开头,不能以数字开头。
  • 变量可以包含字母、数字、下划线和美元符号。
  • 不能使用 JavaScript 中的关键字做为变量名。
  • 变量名不能有空格。
  • 变量名对大小写敏感,比如:name 和 Name 就是两个完全不同的变量。

变量类型

  • Number:你可以在变量中存储数字,不论这些数字是 10(整数),或者是 3.1415926(浮点数)。
  • String:存储字符(比如 "shiyanlou")的变量,字符串可以是引号中的任意文本,你可以使用单引号或双引号,也可以在字符串中使用引号,只要不匹配包围字符串的引号即可。
  • Boolean:布尔类型的值有两种:true 和 false。通常被用于在适当的代码之后,测试条件是否成立。
  • Array:数组是一个单个对象,其中包含很多值,方括号括起来,并用逗号分隔。
  • Object:对象类型。
x = 50;
var x1 = 10;
var x2 = 3.14;
var carname = "shiyanlou";
var myNameArray = ["Tom", "Bob", "Jim"];
var myNumberArray = [10, 15, 20];
var student = {name: "Tom", age: 10};

JavaScript的数据类型是动态类型,直接全部用var声明即可,浏览器会自动识别数据类型。

注释

单行注释:用来描述下面一个或多行代码的作用。单行注释快捷键:Ctrl + /
多行注释:用来注释多条代码。多行注释快捷键:Ctrl + Shift + /

3. JavaScript 数字与运算符

数字类型

  • 整数。例如:1, 2, 100, -10。
  • 浮点数:就是小数。例如:0.2, 3.1415926。
  • 双精度:是一种特定类型的浮点数,它们具有比标准浮点数更高的精度。

注:我们这里作为了解就可以了。因为和其他语言不同的是,在 JavaScript 中,不管什么数字类型的全部用 var 声明就可以了,而且 JavaScript 中只有一个数字类型:Number。

算术运算符

image.png

注:num++ 和 ++num ,前者是先赋值再自增,后者是先自增再赋值。同样的 num-- 和 --num 也是一样的效果。

操作运算符

image.png

比较运算符

image.png
注:
满足返回true,不满足返回false;
== 是相等操作符,比较值是否相等,如果相等输出为 true,否则为 false;
=== 是全等操作符,比较值和类型是否都相等,如果都相等输出为 true,否则为 false。

逻辑运算符

image.png

运算符优先级

image.png

4. JavaScript 数组语法

创建数组

// 创建数组同时赋值 
var myarray = new Array(1, 2, 3, 4, 5); 
// 直接输入一个数组(称“字面量数组”)
var myarray = [1, 2, 3, 4, 5]; 

读取数组元素

var color = ["red", "green", "blue", "yellow"]; 
color[0]; // returns "red" 
color[1]; // returns "green" 
color[2]; // returns "blue" 
color[3]; // returns "yellow" 
color[4]; // returns undefined

多维数组

var student = [ ["张三", "男", "18"], ["李四", "女", "20"], ]; 
student[0][2]; // returns "18"

数组操作

// 修改数组
var color = ["red", "green", "blue", "yellow"]; 
color[0] = "black"; 
color; // returns ["black", "green", "blue", "yellow"]

//获取数组长度
color.length;

//数组和字符串之间的转换
"1:2:3:4".split(":"); // returns ["1", "2", "3", "4"] 
"|a|b|c".split("|"); // returns ["", "a", "b", "c"]

["1", "2", "3", "4"].join(":"); // returns "1:2:3:4" 
["", "a", "b", "c"].join("|"); // returns "|a|b|c"

//添加和删除数组项
var arr = ["1", "2", "3", "4"]; 
arr.push("5", "6"); 
arr; // returns ["1", "2", "3", "4", "5", "6"]

var arr = ["1", "2", "3", "4"]; 
arr.pop(); // returns 4 
arr; // returns ["1", "2", "3"] 

var arr1 = []; 
arr1.pop(); // returns undefined 
arr1; // returns []

注:unshift() 和 shift() 从功能上与 push() 和 pop() 完全相同,只是它们分别作用于数组的开始,而不是结尾。(Q:压栈?弹栈?数组的存储原理用的是栈结构?)

Null 和 Undefined

null 和 undefined 都表示无,但有区别。null 和 undefined 的值都不等于 0,它们的值相等,但是类型不相等。

5. JavaScript 字符串

常用的转义字符

image.png

字符串操作

//连接字符串
var one = "Hello,jack.";
var two = "I'm rose";
result = one + two;

//字符串转换
var myNum = 123;
var myString = myNum.toString();
typeof myString;

var myString = '123';
var myNum = Number(myString);
typeof myNum;

//获取字符串长度
var myString = "hello world";
myString.length;
myString[0];

//在字符串中查找子字符串并提取它
str.indexof(searchValue, fromIndex);
"Blue Sky".indexOf("Blue"); // returns 0 
"Blue Sky".indexOf("Ble"); // returns -1 
"Blue Sky".indexOf("Sky", 0); // returns 5 
"Blue Sky".indexOf("Sky", -1); // returns 5 
"Blue Sky".indexOf("Sky", 5); // returns 5 
"Blue Sky".indexOf("Sky", 9); // returns -1 
"Blue Sky".indexOf("", 0); // returns 0 
"Blue Sky".indexOf("", 5); // returns 5 
"Blue Sky".indexOf("", 9); // returns 8

注:(第20行)

  1. str指的是我们需要查的较长的字符串
  2. searchValue表示指定的较小字符串
  3. fromIndex表示调用该方法的字符串中开始查找的位置,可以是任意值也可以不写
  4. fromIndex < 0fromIndex = 0 是一样的效果,表示从头开始查找整个字符串
  5. 如果 fromIndex >= str.length,则该方法的返回值为 -1。这里有个特殊的情况:就是如果被查找的字符串(searchValue)是一个空字符串,那么当 fromIndex <= 0 时返回 0,0 < fromIndex <= str.length 时返回 fromIndexfromIndex > str.length 时返回 str.length
  6. 返回值指的是指定值第一次出现的索引,如果没有找到返回 -1
  7. indexOf() 方法区分大小写
//提取子字符串
"Blue Sky".slice(0, 3); // returns "Blu"
"Blue Sky".slice(2); // returns "ue Sky"

//转换大小写
var string = "I like study"; 
string.toLowerCase(); // returns "i like study" 
string.toUpperCase(); // returns "I LIKE STUDY"

//替换字符串某部分
//只替换第一个匹配的字符串
var string = "I like study"; 
string.replace("study", "sleep"); // returns "I like sleep"
//全局替换
var string = "I like study study"; string.replace(/study/g, "sleep");

6. JavaScript 类型转换

转换成字符串类型

//1. toString()方法
var num = 8;
var numString = num.toString();
numString;

var result = true;
var resultString = result.toString();
resultString;

var num = 16;
console.log(num.toString());
console.log(num.toString(10));
console.log(num.toString(2));
console.log(num.toString(8));
console.log(num.toString(16));


//2. String()函数
var num1 = 8;
var num2 = String(num1);
num2;

var result = true;
var result1 = String(result);
result1;

//null和undefined没有toString方法,所以用String()
String(null);
String(undefined);


//3. 使用拼接字符串
var num1 = 16;
var num2 = num1 + "";
num2;

转换成数值类型

//1. Number()方法
var num1 = Number(true);
num1; // true 返回 1,false 返回 0

var num2 = Number(undefined);
num2; // 返回 NaN (Not a number)

var num3 = Number(null);
num3; // 返回 0

var num4 = Number("syl");
num4; // 返回 NaN

var num5 = Number("   ");
num5; // 如果是空字符串返回 0

var num6 = Number(123);
num6; // 返回123,如果是数字型的字符,返回数字

var num7 = Number("123abc");
num7; // 返回 NaN


//2. parseInt()方法
/*
    parseInt()可以传递两个参数:
    第一个参数是要转换的字符串,
    第二个参数是要转换的进制。
*/
var num1 = parseInt("12.3abc");
num1; // 返回 12,如果第一个字符是数字会解析直到遇到非数字结束,只取整,不是约等于

var num2 = parseInt("abc123");
num2; // 返回 NaN,如果第一个字符不是数字或者符号就返回 NaN

var num3 = parseInt("");
num3; // 空字符串返回 NaN,但是 Number("")返回 0

var num4 = parseInt("520");
num4; // 返回 520

var num5 = parseInt("0xA");
num5; // 返回 10


//3. parseFloat()方法:把字符串转换成浮点数
/*
    parseFloat 不支持第二个参数,只能解析 10 进制数。
    如果解析的内容里只有整数,解析成整数。
*/
parseFloat("10"); // returns 10
parseFloat("10.000"); // returns 10
parseFloat("10.01"); // returns 10.01
parseFloat("10"); // returns 10
parseFloat("10 hours"); // returns 10
parseFloat("aaa 10"); // returns NaN


//4. 执行减 0 操作,将字符串转换为数值
var n = "10";
var m = n - 0;
m; // returns 10

var n = "abc";
var m = n - 0;
m; // returns NaN
typeof m; // returns "number"

转换成布尔类型

//1. Boolean()方法
Boolean(123); // returns true
Boolean("abc"); // returns true
Boolean(null); // returns false
Boolean(undefined); // returns false
Boolean(NaN); // returns false
Boolean(0); // returns false
Boolean(""); // returns false
Boolean(false); // returns false
Boolean("false"); // returns true
Boolean(-1); // returns true

//2. 流程控制语句会把后面的值隐式转换为boolean类型
var message;
if (message) {
  console.log("你好啊");
} else {
  console.log("我很好");
}

7. JavaScript 流程控制

if...else 语句

if (条件) { 
    // 当条件为 true 时执行的语句 
} 
else { 
    // 当条件为 false 时执行的语句 
}

if(条件 1){ 
    // 当条件 1 为 true 时执行的代码 
} else if(条件 2){ 
    // 当条件 2 为 true 时执行的代码 
} 
else{ 
    // 当条件 1 和 条件 2 都不为 true 时执行的代码 
}

switch... case 语句

switch(k){ 
    case 1: 执行代码块 1 ; 
        break; 
    case 2: 执行代码块 2 ; 
        break; 
    default: 
        默认执行(k 值没有在 case 中找到匹配时); 
}

三元运算符

条件表达式?结果 1:结果 2

for 循环

for (初始化; 条件; 增量) { 
    循环代码; 
    continue; //跳过迭代
    break; //跳出循环
}

while 循环

while (条件) { 
    // 需要执行的代码; 
}

do-while 循环

do { 
    // 需要执行的代码; 
} while (条件);

8. JavaScript 函数

函数声明创建函数

JavaScript 解析器首先会把当前作用域的函数声明提前到整个作用域的最前面,故以下代码能够正常执行。

f(2, 3); 
function f(a, b) { 
    console.log(a + b); 
}

函数表达式创建函数

函数表达式如同定义其它基本类型的变量一样,只在执行到某一句时也会对其进行解析,故以下代码不能够正常执行。

// 报错:f is not a function 
f(2, 3); 
var f = function (a, b) { 
    console.log(a + b); 
};

函数的参数

  • 形参:function f(a, b){return a + b;} // a, b 是形参,占位用,函数定义时形参无值。
  • 实参:当我们调用上面的函数时比如 f(2, 3);其中 2 和 3 就是实参,会传递给 a 和 b,最后函数中执行的语句就变成了:return 2 + 3;

注:在 JavaScript 中,实参个数和形参个数可以不相等。

函数无重载

function f(a, b) {
    return a + b;
}
var result = f(5, 6);
result; // returns NaN

函数返回值

var result = function f(a, b){
    a + b;
}
console.log(result(2, 3)); // 返回:undefined

var result = function f(a, b){
    return a + b;
}
console.log(result(2, 3)); // 返回:5

匿名函数

匿名函数就是没有命名的函数,一般用在绑定事件的时候。示例为:

var myButton = document.querySelector("button"); 
myButton.onclick = function () {
    alert("hello"); 
};

自调用函数

(function () { 
    alert("hello"); 
    })();

函数进阶

call()、apply()、bind()

先看一个例子:

function foods() {}
foods.prototype = {
  price: "¥15",
  say: function () {
    console.log("My price is " + this.price);
  },
};

var apple = new foods();
apple.say(); // My price is ¥15
var orange = new foods();
orange.say(); // My price is ¥15

上述例子调用 say() 方法,最后打印的结果都是一样的,但是如果我们想打印橘子的价钱是 10 元呢?又不想重新定义 say() 方法。
JavaScript 为我们专门提供了 call()apply()bind() 三个函数方法帮我们更优雅的处理函数内部 this 指向问题。

fun.call(thisArg, arg1, arg2, ...)

call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。

  • thisArg 指的是在 fun 函数中指定的 this 的值。如果指定了 null 或者 undefined 则内部 this 指向 window,同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。是一个可选项。
  • arg1, arg2, ...指定的参数列表。也是可选项。
  • 使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
  • call() 允许为不同的对象分配和调用属于一个对象的函数/方法。
  • call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call() 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
// 1. 使用call()方法调用函数并且指定上下文的this
function foods() {}
foods.prototype = {
  price: "¥15",
  say: function () {
    console.log("My price is " + this.price);
  },
};

var apple = new foods();
orange = {
  price: "¥10",
};
apple.say.call(orange); // My price is ¥10


// 2. 可以通过调用父构造函数的call()方法来实现继承
function Father(name, age) {
    this.name = name;
    this.age = age;
}
  
function Son(name, age) {
    Father.call(this, name, age);
    this.hobby = "study";
}
  
var S1 = new Son("zhangsan", 18);
S1; // Son {name: "zhangsan", age: 18, hobby: "study"}

fun.apply(thisArg, [argsArray]);

apply() 方法与 call() 方法类似,唯一的区别是 call() 方法接受的是参数,apply() 方法接受的是数组。

// 1. 使用apply()方法将数组添加到另一个数组
var array = ["a", "b", "c"];
var nums = [1, 2, 3];
array.push.apply(array, nums);
array; // ["a", "b", "c", 1, 2, 3]

注:concat() 方法连接数组,不会改变原数组,而是创建一个新数组。而使用 push() 是接受可变数量的参数的方式来添加元素。使用 apply() 则可以连接两个数组。

// 2. 使用apply()方法和内置函数
var numbers = [7, 10, 2, 1, 11, 9];
var max = Math.max.apply(null, numbers);
max; // 11

fun.bind(thisArg[, arg1[, arg2[, ...]]])

bind() 方法创建一个新的函数(称为绑定函数),在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

// 例1:
var bin = function () {
    console.log(this.x);
  };
var foo = {
    x: 10,
};
  
bin(); // undefined
var func = bin.bind(foo); // 创建一个新函数把 'this' 绑定到 foo 对象
func(); // 10


// 例2:
this.num = 6;
var test = {
  num: 66,
  getNum: function () {
    return this.num;
  },
};

test.getNum(); // 返回 66

var newTest = test.getNum;
newTest(); // 返回 6, 在这种情况下,"this"指向全局作用域

// 创建一个新函数,将"this"绑定到 test 对象
var bindgetNum = newTest.bind(test);
bindgetNum(); // 返回 66

递归

// 一定要写临界条件,不然程序无法结束并且会报错
function foo(n) {
    if (n == 0) {
      return 0;
    } // 临界条件
    else {
      return n + foo(n - 1);
    }
}
var a = foo(10);
a; // 55

作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。简单来说,作用域的值就是作用范围,也就是说一个变量或函数在什么地方可以使用,在什么地方不能使用。

块级作用域

在 JavaScript 中是没有块级作用域的。比如:

{
    var num = 123;
    {
      console.log(num);
    }
}
console.log(num);

上面的例子并不会报错,而是打印两次 123,但是在其他编程语言中(C#、C、JAVA)会报错,这是因为在 JavaScript 中是没有块级作用域。也就是说,使用 {} 标记出来的代码块中声明的变量 num,是可以被 {} 外面访问到的。

函数作用域

JavaScript 的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的,不涉及赋值。

function test() {
    var num = 123;
    console.log(num);
    if (2 == 3) {
      var k = 5;
      for (var i = 0; i < 10; i++) {}
      console.log(i);
    }
    console.log(k); // 即使没赋值,也不会报错,而是显示 undefined
}
test();

全局作用域

全局作用域也就是说什么地方都能够访问到。比如我们不用 var 关键字,直接声明变量的话,那这个变量就是全局变量,它的作用域就是全局作用域。使用 window 全局对象来声明,全局对象的属性也是全局变量。另外在所有的函数外部用 var 声明的变量也是全局变量,这是因为内层作用域可以访问外层作用域。

  • 内层作用域可以访问外层作用域,反之不行。
  • 整个代码结构中只有函数可以限定作用域。
  • 如果当前作用规则中有名字了,就不考虑外面的同名变量。
  • 作用域规则首先使用提升规则分析。

变量名提升

JavaScript 是解释型的语言,但是它并不是真的在运行的时候完完全全的逐句的往下解析执行。
JavaScript 引擎在对 JavaScript 代码进行解释执行之前,会对 JavaScript 代码进行预解析,在预解析阶段,会将以关键字 var 和 function 开头的语句块提前进行处理。当变量和函数的声明处在作用域比较靠后的位置的时候,变量和函数的声明会被提升到作用域的开头。

  • 变量名提升指的是声明的提升,不包含赋值。
  • 函数同名时,两个函数都会被提升。
  • 函数声明与变量声明同名时,只会提升函数声明。

闭包

闭包是指函数可以使用函数之外定义的变量。

// 简单实例
var num = 3;
function foo() {
  console.log(num);
}
foo(); //打印 3

// 复杂实例
// 报错:num2 is not defined
function f1() {
    var num1 = 6;
    function f2() {
      var num2 = 7;
    }
    console.log(num1 + num2);
}
f1();
// 将num2放入返回值
function f1() {
    var num1 = 6;
    function f2() {
      var num2 = 7;
      return num2;
    }
    console.log(num1 + f2());
}
f1();

arguments 对象

function add() {
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
      sum += arguments[i];
    }
    return sum;
}
add(); // 0
add(1); // 1
add(1, 2); // 3
add(1, 2, 3); // 6

Function 对象

用 Function() 对象创建函数的语法如下:

var function_name = new Function(arg1, arg2, ..., argN, function_body)

注:每个参数都必须是字符串,function_body 是函数主体,也就是要执行的代码。

// function 对象的三种示例代码
var add = new Function("a", "b", "console.log(a+b);");
add(2, 5); // 打印 7

var add = new Function("a", "b", "console.log(a+b);");
var doAdd = add;
doAdd(2, 5); // 打印 7
add(2, 5); // 打印 7

function addF(foo, b, c) {
    foo(b, c);
}
var add = new Function("a", "b", "console.log(a+b);");
addF(add, 2, 5); // 打印 7

// function 对象的length属性
var add = new Function("a", "b", "console.log(a+b);");
console.log(add.length); // 打印 2

// function 对象的方法
var add = new Function("a", "b", "console.log(a+b);");
add.valueOf();
add.toString();

9. JavaScript 对象

对象基础概念

在 JavaScript 中所有事物都是对象:字符串,数组,日期等等。我们可以自己创建对象,将相关的函数和变量封装打包成便捷的数据容器。

JavaScript 是一门基于对象的语言。基于对象和面向对象之间的区别在于,基于对象的语言可能具有一些面向对象编程的特性,但不是全面的实现,而面向对象编程则是一种完整的编程范式,强调对象的概念和对象之间的交互。

属性与方法

属性是与对象相关的值,也可以理解为特征。方法是能够在对象上执行的动作,也可以理解为行为。

举个例子:一辆汽车就是现实生活中的对象,它的名字,它的颜色,它的价格等特征就是汽车对象的属性。它能够启动,驾驶,加速,刹车等行为就是汽车对象的方法。

JSON - JavaScript Object Notation

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种由道格拉斯·克罗克福特构想和设计、轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。

JSON 数据格式与语言无关,脱胎于 JavaScript,但当前很多编程语言都支持 JSON 格式数据的生成和解析。JSON 的官方 MIME 类型是 application/json,文件扩展名是 .json。

  • JSON 是一种纯数据格式,它只包含属性,没有方法。
  • JSON 的属性必须通过双引号引起来。
  • JSON 要求两头有 {} 来使其合法。
  • 可以把 JavaScript 对象原原本本的写入 JSON 数据,比如:字符串,数字,数组,布尔还有其它的字面值对象。

以下给出两个示例:

{
    "name": "zhangsan",
    "age": 18,
    "gender": "male"
}

{
    "students": [
      { "firstName": "san", "lastName": "zhang" },
      { "firstName": "si", "lastName": "li" },
      { "firstName": "wu", "lastName": "wang" }
    ]
}

常用内置对象及操作

Array 对象

  1. Array对象的常用属性:length,获取数组对象的长度
  2. Array对象的常用方法:(见下述代码块)
// Array 对象
var a = [1, 2, 3];
var b = [4, 5, 6];
var c = ["one", "two", "three"];

// concat():连接两个或多个数组,并返回结果
console.log(a.concat(b, c));

// pop():删除并返回数组最后一个元素
delete_element_pop = a.pop();
console.log(delete_element_pop);
// shift():删除并返回数组的第一个元素
delete_element_shift = a.pop();
console.log(delete_element_shift);

// push():向数组末尾添加一个或更多元素,并返回新的长度
new_length_push = a.push(3);
console.log(new_length_push);
// unshift():向数组开头添加一个或更多元素,并返回新的长度
new_length_unshift = a.unshift(3);
console.log(new_length_unshift);

// reverse():颠倒数组的顺序
a.reverse();
console.log(a);

var a = [1, 2, 3, 4, 5, 6];
// slice(start, end):从某个已有的数组返回选定的元素
/*
    start: 必须有,规定从何处开始选取
    end:可选,规定从何处结束选取,若没有则选取所有
*/
cut_a = a.slice(2, 5);
console.log(cut_a);
console.log(a);

// splice(start, deleteCount, options):删除或替换当前数组的某些项目
/*
    start: 必须有,规定从何处开始选取
    deleteCount: 必须有,规定要删除的项目数量,如果设置为0则不会删除项目
    options: 可选,规定要替换的新项目
*/
cut_b = a.splice(2, 2, "abc");
console.log(cut_b);
console.log(a);

// sort(sortby):sortby可选,规定排序顺序,必须是函数,若没有参数,
//        将会按照字符编码顺序进行排序。
//        若想按照其他标准进行排序,则需要提供比较函数。
// 比较函数示例:
function sortNum1(a, b){
    return a - b; // 从小到大排序
}
function sortNum2(a,b){
    return b - a; // 从大到小排序
}

// toString():把数组转换为字符串,并返回结果
string_a = toString(a);
console.log(typeof(a)); // 输出为:object
console.log(typeof(string_a)); // 输出为:string

String 对象

  1. String 对象的常用属性:length,获取字符串的长度。
  2. String 对象的常用方法:(见下述代码块)
// String 对象
var str = "Hello world!";

// charAt(index):获取指定位置处字符
// index在0 ~ string.length之间,则返回对应字符,否则返回空字符串
console.log(str.charAt(2));

// charCodeAt(index):获取指定位置处字符的Unicode编码
console.log(str.charCodeAt(2));

// concat():连接字符串,等效于“+”,“+”更常用,与数组中的concat()类似
// slice():提取字符串片段
// indexOf():检索字符串
// toString():返回字符串
// toLowerCase():把字符串转换为小写
// toUpperCase():把字符串转换为大写
// replace():替换字符串中的某部分
// split():把字符串分割为字符串数组

Date 对象

  • Date():返回当日的日期和时间(输出的是中国标准时间)。
  • getDate():从 Date 对象返回一个月中的某一天 (1 ~ 31)。
  • getDay():从 Date 对象返回一周中的某一天 (0 ~ 6)。
  • getMonth():从 Date 对象返回月份 (0 ~ 11)。
  • getFullYear():从 Date 对象以四位数字返回年份。
  • getHours():返回 Date 对象的小时 (0 ~ 23)。
  • getMinutes():返回 Date 对象的分钟 (0 ~ 59)。
  • getSeconds():返回 Date 对象的秒数 (0 ~ 59)。
  • getMilliseconds():返回 Date 对象的毫秒(0 ~ 999)。

Math 对象

  1. Math 对象的常用属性:
  • E :返回常数 e (2.718281828...)。
  • LN2 :返回 2 的自然对数 (ln 2)。
  • LN10 :返回 10 的自然对数 (ln 10)。
  • LOG2E :返回以 2 为底的 e 的对数 (log2e)。
  • LOG10E :返回以 10 为底的 e 的对数 (log10e)。
  • PI :返回 π(3.1415926535...)。
  • SQRT1_2 :返回 1/2 的平方根。
  • SQRT2 :返回 2 的平方根。
  1. Math 对象的常用方法:
  • abs(x) :返回 x 的绝对值。
  • round(x) :返回 x 四舍五入后的值。
  • sqrt(x) :返回 x 的平方根。
  • ceil(x) :返回大于等于 x 的最小整数。
  • floor(x) :返回小于等于 x 的最大整数。
  • sin(x) :返回 x 的正弦。
  • cos(x) :返回 x 的余弦。
  • tan(x) :返回 x 的正切。
  • acos(x) :返回 x 的反余弦值(余弦值等于 x 的角度),用弧度表示。
  • asin(x) :返回 x 的反正弦值。
  • atan(x) :返回 x 的反正切值。
  • exp(x) :返回 e 的 x 次幂 (e^x)。
  • pow(n, m) :返回 n 的 m 次幂 (nm)。
  • log(x) :返回 x 的自然对数 (ln x)。
  • max(a, b) :返回 a, b 中较大的数。
  • min(a, b) :返回 a, b 中较小的数。
  • random() :返回大于 0 小于 1 的一个随机数。

创建对象

// 1. 通过对象字面量来创建
var student = {
    name: "zhangsan",
    age: 18,
    gender: "male",
    sayHi: function(){
        console.log("Hi, my name is " + this.name);
    },
};

student.name;
student.age;
student.gender;
student.sayHi;

// 2. 通过new Object()创建对象
var student = new Object();
(student.name = "zhangsan"),
    (student.age = 18),
    (student.gender = "male"),
    (student.sayHi = function(){
        console.log("Hi, my name is " + this.name);
    });

// 3. 通过工厂函数创建对象
function createStudent(name, age, gender){
    var student = new Object();
    student.name = name;
    student.age = age;
    student.gender = gender;
    student.sayHi = function(){
        console.log("Hi, my name is " + this.name);
    }
}
var s1 = createStudent("zhangsan", 18, "male");

// 4. 自定义构造函数
function Student(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayHi = function(){
        console.log("Hi, my name is " + this.name);
    }
}
var s1 = createStudent("zhangsan", 18, "male");

new 关键字

构造函数,是一种特殊的函数。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与 new 运算符一起使用在创建对象的语句中。这里有需要特别注意的几点:

  • 构造函数用于创建一类对象,首字母要大写。
  • 内部使用 this 关键字给对象添加成员。
  • 使用 new 关键字调用对象构造函数。

this 详解

在 JavaScript 中,我们经常会使用到 this 关键字,那么 this 到底指向什么呢?这里有一个口诀:谁调用 this,它就是谁。大家也可以从前面的例子中细细体会一下。

  • 函数在定义的时候 this 是不确定的,只有在调用的时候才可以确定。
  • 一般函数直接执行,内部 this 指向全局 window。
  • 函数作为一个对象的方法,被该对象所调用,那么 this 指向的是该对象。
  • 构造函数中的 this,始终是 new 的当前对象。

访问对象

遍历对象的属性

通过 for...in 语句用于遍历数组或者对象的属性,对数组或者对象的属性进行循环操作。比如:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>

  <body>
    <script>
      var student = {
        name: "zhangsan",
        age: 18,
        gender: "male",
      };
      for (var key in student) {
        console.log(key);
        console.log(student[key]);
      }
    </script>
  </body>
</html>

删除对象的属性

使用 delete 删除对象的属性。比如在控制台中输入以下代码:

var student = {
    name: "zhangsan",
    age: 18,
    gender: "male",
};
student.name; // zhangsan
delete student.name;
student.name; // undefined

内置对象使用实例

去掉数组中的重复元素

var array = ["x", "c", "a", "b", "c", "b", "c"];
function clearArray(){
    var o = {};
    for (var i = 0; i < array.length; i++){
        var item = array[i];
        if(o[item]){
            o[item]++;
        } else {
            o[item] = 1;
        }
    }
    var tmpArray = [];
    for(var key in o){
        if(o[key == 1]){
            tmpArray.push(key);
        } else {
            if (tmpArray.indexOf(key) == -1){
                tmpArray.push(key);
            }
        }
    }
    return tmpArray;
}
console.log(clearArray(array));

10. Web API

浏览器是一个封装的较为完善的软件,它给我们提供了操作浏览器功能和页面元素的接口。

API - Application Programming Interface

API(Application Programming Interface,应用程序编程接口):"计算机操作系统"(Operating system)或"程序库"提供给应用程序调用使用的代码。其主要目的是让应用程序开发人员得以调用一组例程功能,而无须考虑其底层的源代码为何、或理解其内部工作机制的细节。API 本身是抽象的,它仅定义了一个接口,而不涉及应用程序在实际实现过程中的具体操作。

Web API

Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API(BOM 和 DOM)。

BOM - Browser Object Model

浏览器对象模型(Browser Object Model (BOM))指的是由 Web 浏览器暴露的所有对象组成的表示模型。BOM 与 DOM(Document Object Model,文档对象模型)不同,其既没有标准的实现,也没有严格的定义,所以浏览器厂商可以自由地实现 BOM。

作为显示文档的窗口,浏览器程序将其视为对象的分层集合。当浏览器分析文档时,它将创建一个对象的集合,以定义文档,并详细说明它应如何显示。浏览器创建的对象称为文档对象,它是浏览器使用的更大的对象集合的一部分。此浏览器对象集合统称为浏览器对象模型或 BOM。

BOM 层次结构的顶层是窗口对象,它包含有关显示文档的窗口的信息。某些窗口对象本身就是描述文档和相关信息的对象。

常用操作方法

window 是浏览器的顶级对象,当调用 window 下的属性和方法时,可以省略 window。

对话框
  • alert():显示带有一段消息和一个确认按钮的警告框。
  • prompt():显示可提示用户输入的对话框。
  • confirm():显示带有一段消息以及确认按钮和取消按钮的对话框。
页面加载事件
// 页面加载事件
window.onload = function(){
    // 当页面加载完成执行
    // 当页面完全加载所有内容(包括图像、脚本文件、CSS文件等)执行
};
window.onunload = function(){
    // 当用户退出页面时执行  
};
浏览器尺寸
// 浏览器尺寸
var width = window.innerWidth;
document.documentElement.clientWidth;
document.body.clientWidth;

var height = window.innerHeight;
document.documentElement.clientHeight;
document.body.clientHeight;

上述代码可以获取所有浏览器的宽高(不包括工具栏/滚动条)

定时器
// 定时器
// 创建一个定时器,2000ms后执行,返回定时器的标示
var timerId = setTimeout(function (){
    console.log("Hello");
}, 2000);

// 取消定时器的执行
clearTimeout(timerId);

DOM - Document Object Model

文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标志语言的标准编程接口。DOM 定义了访问 HTML 和 XML 文档的标准。我们这里主要学习 HTML DOM。DOM 可以把 HTML 看做是文档树,通过 DOM 提供的 API 可以对树上的节点进行操作。下面我们来看一下 W3C 上的 DOM 树:

image.png

DOM HTML

改变HTML输出流

在 JavaScript 中,使用 document.write() 可用于直接向 HTML 输出流写内容。比如:

document.write("新设置的内容<p>标签也可以生成</p>");
改变HTML中的内容

使用 innerHTML 属性改变 HTML 内容。比如修改 p 标签中的内容:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>
  <body>
    <p id="p1">Hello World!</p>
    <script>
      document.getElementById("p1").innerHTML = "Hello 实验楼";
    </script>
  </body>
</html>
改变HTML属性
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>
  <body>
    <img id="image" src="https://www.baidu.com/img/bd_logo1.png" />
    <script>
      document.getElementById("image").src =
        "https://static.shiyanlou.com/img/shiyanlou_logo.svg";
    </script>
  </body>
</html>
改变HTML元素样式
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>
  <body>
    <p id="syl" style="color: red;">实验楼</p>
    <script>
      document.getElementById("syl").style.color = "green";
    </script>
  </body>
</html>

注:在上述例子中,p 标签中实验楼的颜色本来为红色,但是通过 DOM 方法,最后将其改变成了绿色。运行上述代码,最终的效果是显示一个颜色为绿色的实验楼文本。

DOM 节点

根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:整个文档就是一个文档节点,而每一个 HTML 标签都是一个元素节点。HTML 标签中的文本则是文本节点,HTML 标签的属性是属性节点,一切都是节点。

image.png

获取节点
// 1. 通过ID找到HTML元素
document.getElementById("demo");

// 2. 通过标签名找到HTML元素
// 选取第1个input标签
document.getElementsByTagName("input")[0].value = "Hello"; 
// 选取第2个input标签
document.getElementsByTagName("input")[1].value = "JavaScript";

// 3. 通过类名来找到HTML元素
// 返回包含class = "name"的所有元素的一个列表
document.getElementsByClassName("name");
DOM节点之间的关系
父节点 兄弟节点 子节点所有子节点
parentNodenextSiblingfirstChildchildNodes
nextElementSiblingfirstElementChildchildren
previousSiblinglastChild
previousElementSiblinglastElementChild
// 示例代码
<html>
  <head>
    <title>DOM 节点演示</title>
  </head>
  <body>
    <h1>我是h1标签</h1>
    <p>我是p标签</p>
  </body>
</html>

上面例子中:

  • <html>节点没有父节点,它是根节点。
  • <head> 和 <body> 的父节点是 <html> 节点。
  • 文本节点 我是 p 标签 的父节点是 <p> 节点。
  • <html> 节点有两个子节点:<head> 和 <body>
  • <h1> 节点和 <p> 节点是兄弟节点,同时也是 <body> 的子节点。

需要注意以下几点:

  • childNodes:它是标准属性,它返回指定元素的子元素集合,包括 HTML 节点,所有属性,文本节点。
  • children:非标准属性,它返回指定元素的子元素集合。但它只返回 HTML 节点,甚至不返回文本节点。
  • nextSibling 和 previousSibling 获取的是节点,获取元素对应的属性是 nextElementSibling 和 previousElementSibling
  • nextElementSibling 和 previousElementSibling 有兼容性问题,IE9 以后才支持。
DOM 节点操作
  1. 创建节点
  • 创建元素节点:使用 createElement() 方法。比如:
var par = document.createElement("p");
  • 创建属性节点:使用 createAttribute() 方法。
  • 创建文本节点:使用 createTextNode() 方法。
  1. 插入子节点
  • appendChild () 方法向节点添加最后一个子节点。
  • insertBefore (插入的新的子节点,指定的子节点) 方法在指定的子节点前面插入新的子节点。如果第二个参数没写或者为 null,则默认插入到后面。
  1. 删除节点:使用 removeChild() 方法。写法为:
父节点.removeChild(子节点);
node.parentNode.removeChild(node); // 如果不知道父节点是什么,可以这样写
  1. 替换子节点:使用 replaceChild() 方法。语法为:
node.replaceChild(newnode, oldnode);
  1. 设置节点的属性:
  • 获取:getAttribute(name)
  • 设置:setAttribute(name, value)
  • 删除:removeAttribute(name)
DOM 事件
事件的定义

在什么时候执行什么事。

事件三要素

事件由:事件源 + 事件类型 + 事件处理程序组成。

  • 事件源:触发事件的元素。
  • 事件类型:事件的触发方式(比如鼠标点击或键盘点击)。
  • 事件处理程序:事件触发后要执行的代码(函数形式,匿名函数)。
常用的事件
事件名说明
onclick鼠标单击
ondblclick鼠标双击
onkeyup按下并释放键盘上的一个键时触发
onchange文本内容或下拉菜单中的选项发生改变
onfocus获得焦点,表示文本框等获得鼠标光标。
onblur失去焦点,表示文本框等失去鼠标光标。
onmouseover鼠标悬停,即鼠标停留在图片等的上方
onmouseout鼠标移出,即离开图片等所在的区域
onload网页文档加载事件
onunload关闭网页时
onsubmit表单提交事件
onreset重置表单时
常用示例
  1. 鼠标单击事件
<p onclick="this.innerHTML = '我爱学习,身体好好!'">请点击该文本</p>
  1. 鼠标双击事件
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>
  <body>
    <h1 ondblclick="changetext(this)">请点击该文本</h1>
    <script>
      function changetext(id) {
        id.innerHTML = "我爱学习,身体棒棒!";
      }
    </script>
  </body>
</html>
  1. 鼠标移出悬停
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>
  <body>
    <div
      onmouseover="mOver(this)"
      onmouseout="mOut(this)"
      style="background-color:deepskyblue;width:200px;height:100px;"
    >
      把鼠标移到上面
    </div>
    <script>
      function mOver(obj) {
        obj.innerHTML = "你把鼠标移到了上面 ";
      }

      function mOut(obj) {
        obj.innerHTML = "你把鼠标移开了";
      }
    </script>
  </body>
</html>
  1. 导航栏样式切换

Snipaste_2024-04-16_11-50-26.png

Snipaste_2024-04-16_11-50-39.png

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8 /"
        <title>Title</title>
        <style>
            #list li {
                list-style-type: none;
                width: 100px;
                height: 50px;
                line-height: 50px;
                background-color: beige;
                text-align: center;
                float: left;
            }

            #list li.current {
                background-color: red;
            } 

            #list li a {
                text-decoration: none;
            }
        </style> 
    </head>

    <body>
        <div id="menu">
            <ul id="list">
                <li class="current">
                    <a href="javascript:void(0)">首页</a>
                </li>
                <li>
                    <a href="javascript:void(0)">HTML</a>
                </li>
                <li>
                    <a href="javascript:void(0)">CSS</a>
                </li>
                <li>
                    <a href="javascript:void(0)">JavaScript</a>
                </li>
                <li>
                    <a href="javascript:void(0)">关于</a>
                </li>
                <li>
                    <a href="javascript:void(0)">帮助</a>
                </li>
            </ul>
        </div>

        <script>
            // 获取所有的 li 标签
            var liObjs = document.getElementById("list").getElementsByTagName("li");
            // 循环遍历,找到每个 li 中的 a,注册点击事件
            for (var i = 0; i < liObjs.length; i++) {
                // 每个 li 中的 a
                var aObj = liObjs[i].firstElementChild;

                aObj.onclick = function () {
                    // 把这个 a 所在的 li 的所有的兄弟元素的类样式全部移除
                    for (var j = 0; j < liObjs.length; j++) {
                        liObjs[j].removeAttribute("class");
                    }
                    // 当前点击的 a 的父级元素 li(点击的这个a所在的父级元素 li),设置背景颜色
                    this.parentNode.className = "current";
                };
            }
        </script>
    </body>
</html>

11. JavaScript 值类型和引用类型

值类型

概述

值类型又叫基本数据类型,在 JavaScript 中值类型有以下五种:

  • 数值类型
  • 布尔类型
  • undefined
  • null
  • 字符串

值类型存储在栈(stack)中,它们的值直接存储在变量访问的位置。比如:

image.png

特征

  • 值类型的值是不可变的,不可变是指值类型指向的空间不可变。
  • 按值传递的变量之间互不影响。
  • 值类型赋值,直接将值赋值一份。
  • 当参数为值类型的时候,函数内和函数外的两个变量完全不同,仅仅只是存的值一样而已,修改时互不影响。

引用类型

概述

引用类型又叫复合数据类型,在 JavaScript 中引用类型有以下三种:

  • 对象
  • 数组
  • 函数

引用类型存储在堆中,也就是说存储在变量处的值是一个指针,指向存储对象的内存处。

image.png

特征

  • 引用类型赋值,是将地址复制一份。
  • 当参数为引用类型的时候,函数内和函数外的两个变量不同,但是共同指向同一个对象,在函数内修改对象数据时会影响外部。

12. JavaScript 异常处理

调试工具的使用

在编写 JavaScript 代码时,如果遇见错误,首先在浏览器中运行我们的代码,然后 F12,查看错误信息。

  • 首先查看控制台信息,在控制台中会有报错提示,一般看这个就能知道问题是什么了
  • 在 Sources 中能够清楚的看到哪一行的代码出问题了,会有很明显地提醒

设置断点逐步执行

  • 首先运行代码,点击 F12 进行调试界面,点击 Sources,设置断点的方法很简单,直接左键点击代码旁边的数字行数
  • 将变量添加到 Watch 窗口,实时查看它的值的变化
  • 准备工作做好之后我们还需要点击一下刷新
  • 然后就可以通过点击运行按钮,逐行运行我们设置断点时的代码,并在 Watch 中查看变量值的变化

异常捕获

try-catch 语句

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>

  <body>
    <input type="button" value="点击一下" onclick="message()" />
    <script>
      var txt = "";

      function message() {
        try {
          alertt("我爱学习,身体好好"); // 故意写错 alert
        } catch (err) {
          txt = "There was an error on this page.\n\n";
          txt += "Error description: " + err.message + "\n\n";
          txt += "Click OK to continue.\n\n";
          alert(txt);
        }
      }
    </script>
  </body>
</html>
  • 语句 try 和 catch 是成对出现的。
  • 如果在 try 中出现了错误,try 里面出现错误的语句后面的代码都不再执行,直接跳转到 catch 中,catch 处理错误信息,然后再执行后面的代码。
  • 如果 try 中没有出现错误,则不会执行 catch 中的代码,执行完 try 中的代码后直接执行后面的代码。
  • 通过 try-catch 语句进行异常捕获之后,代码将会继续执行,而不会中断。

throw 语句

通过 throw 语句,我们可以创建自定义错误。throw 语句常常和 try catch 语句一起使用。

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>

  <body>
    <p>请输入 0 到 100 之间的数字:</p>
    <input id="demo" type="text" />
    <button type="button" onclick="myFunction()">测试输入值</button>
    <p id="throwText"></p>
    <script>
      function myFunction() {
        try {
          var x = document.getElementById("demo").value;
          if (x == "") throw "您输入的值为空";
          if (isNaN(x)) throw "您输入的不是数字";
          if (x > 100) throw "您输入的值太大";
          if (x < 0) throw "您输入的值太小";
        } catch (err) {
          var y = document.getElementById("throwText");
          y.innerHTML = "错误提示:" + err + "。";
        }
      }
    </script>
  </body>
</html>

13. JavaScript 面向对象编程

概述

什么是对象

ECMA-262 把对象(object)定义为“属性的无序集合,每个属性存放一个原始值、对象或函数”。我们可以从以下两个层次来理解对象到底是什么:

  1. 对象是单个事物的抽象。比如一支笔,一本书,一辆车都可以是一个对象。
  2. 对象是一个容器,封装了属性和方法。比如:一辆车。它的颜色,大小,重量等是它的属性,而启动,加速,减速,刹车等是它的方法。

OOP - Object Oriented Programming

面向对象程序设计(英语:Object Oriented Programming,缩写:OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。

示例代码

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
    <style type="text/css">
      div,
      p {
        width: 200px;
        height: 100px;
      }
    </style>
  </head>

  <body>
    <div>你好吗?</div>
    <p>我很好</p>
    <div>测试一下嘛</div>
    <p>好的啊</p>
    <script>
      var test = {
        getEle: {
          tag: function (tagName) {
            return document.getElementsByTagName(tagName);
          },
          id: function (idName) {
            return document.getElementById(idName);
          },
          class: function (className) {
            return document.getElementsByClassName(className);
          },
        },
        setCss: {
          setStyle: function (arr) {
            for (var i = 0; i < arr.length; i++) {
              arr[i].style.backgroundColor = "red";
            }
          },

          updateCss: function () {},
          deleteCss: function () {},
          // ...
        },
      };

      var divs = test.getEle.tag("div");
      test.setCss.setStyle(divs);
      var ps = test.getEle.tag("p");
      test.setCss.setStyle(ps);
    </script>
  </body>
</html>

属性:Prototype 原型

前面创建JavaScript对象的方法中,都存在不同的问题,构造函数创建对象的方法相较于其他三种很好的解决了代码冗余的问题。
但是构造函数仍然存在问题:当每次new创建新的对象时,内部的属性都会被重新创建,造成内存浪费。
若单独将实现相同的功能的函数定义在外部,又会使全局变量过多,可能会引起命名冲突,代码结果混乱,维护困难。

在 JavaScript 中,每一个函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。最优的写法如下:

function Student(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
  }
  
  Student.prototype.sayHi = function () {
    console.log("hi");
  };
  
  var s1 = new Student("zhangsan", 18, "male");
  s1.sayHi(); // 打印 hi
  var s2 = new Student("lisi", 18, "male");
  s2.sayHi(); // 打印 hi
  console.log(s1.sayHi == s2.sayHi); // 结果为 true

构造函数、实例、原型三者之间的关系

每一个函数都有一个 prototype 属性,指向另一个对象。让我们用代码验证一下,在编辑器中输入以下代码:

<script type="text/javascript">
  function F() {}
  console.log(F.prototype);
</script>

上述代码在浏览器中打印结果为 Object,验证了我们所说的 prototype 属性,指向另一个对象。

构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。在控制台中运行下面的代码:

function F() {}
console.log(F.prototype.constructor === F); // 结果为 ture

通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto____proto__ 属性最早是火狐浏览器引入的,用以通过实例对象来访问原型,这个属性在早期是非标准的属性。在控制台中运行下面的代码:

function F() {}
var a = new F();
console.log(a.__proto__ === F.prototype); // 结果为 true

实例对象可以直接访问原型对象成员,所有实例都直接或间接继承了原型对象的成员。

总结:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针 constructor,而实例都包含一个指向原型对象的内部指针__proto__

原型链

所有的对象都有原型,而原型也是对象,也就是说原型也有原型,那么如此下去,也就组成了我们的原型链。

属性搜索原则

属性搜索原则,也就是属性的查找顺序,在访问对象的成员的时候,会遵循以下原则:

  • 首先从对象实例本身开始找,如果找到了这个属性或者方法,则返回。
  • 如果对象实例本身没有找到,就从它的原型中去找,如果找到了,则返回。
  • 如果对象实例的原型中也没找到,则从它的原型的原型中去找,如果找到了,则返回。
  • 一直按着原型链查找下去,找到就返回,如果在原型链的末端还没有找到的话,那么如果查找的是属性则返回 undefined,如果查找的是方法则返回 xxx is not a function

更简单的原型语法

在前面的例子中,我们是使用 xxx.prototype. 然后加上属性名或者方法名来写原型,但是每添加一个属性或者方法就写一次显得有点麻烦,因此我们可以用一个包含所有属性和方法的对象字面量来重写整个原型对象,同时保证原型constructor成员指向正确:

function Student(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
  }
  Student.prototype = {
    constructor: Student, // 手动将 constructor 指向正确的构造函数
    hobby: "study",
    sayHi: function () {
      console.log("hi");
    },
  };
  
  var s1 = new Student("wangwu", 18, "male");
  console.log(Student.prototype.constructor === Student); // 结果为 true

原型链继承

原型链继承的主要思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法,比如实例化对象继承定义的类。

function Student(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}
Student.prototype.sayHi = function () {
    console.log("hi");
};
  
var s1 = new Student("zhangsan", 18, "male");
s1.sayHi(); // 打印 hi
var s2 = new Student("lisi", 18, "male");
s2.sayHi(); // 打印 hi

Object.prototype 常用成员介绍

image.png