面向对象编程介绍:

面向对象编程(OOP)是一种编程范式,它将现实世界中的事物抽象成一个个对象,通过对象之间的交互来实现程序的功能。OOP主要有两种编程思想:面向过程编程(POP)和面向对象编程(OOP)。

1. 面向过程编程:

面向过程编程是一种分析问题并逐步解决的方法。它是根据事先分析好的步骤来编写代码,然后按照这些步骤依次调用函数实现功能。面向过程编程的主要特点是按照问题的解决步骤进行设计和开发。

2. 面向对象编程:

面向对象编程是将事务分解成一个一个的对象,然后由对象之间分工与合作。在面向对象的程序开发思想中,每一个对象都是功能中心,具有明确分工。面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。面向对象的特性包括封装性、继承性和多态性。

面向过程和面向对象的对比:

1. 优点:

面向过程:性能比面向对象高,适合跟硬件联系很密切的东西,例如单片机就采用的面向过程编程。

面向对象:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。

2. 缺点:

面向过程:没有面向对象易维护、易复用、易扩展。

3. 注意:

面向对象和面向过程是两种不同的编程思想,都有适合各自的使用场景,谁也不能替代谁。

ES6中的面向对象思维特点:

1. 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模版);

2. 对类进行实例化,获取类的对象;

3. 考虑有哪些对象,按照面向对象的思维特点,不断地创建对象,使用对象,指挥对象做事情。

在JavaScript中,可以使用ES6的class关键字来创建类和生成实例。语法如下:

```javascript

class name { // class body }

```

创建实例的语法如下:

```javascript

var xx = new name();

```

以下是重构后的代码:

```javascript

// 创建一个明星类

class Star {

constructor(uname) { // 接收实例传递过来的参数并返回实例对象

this.uname = uname;

}

}

// 利用类创建对象

var ldh = new Star('刘德华');

console.log(ldh.uname); // 刘德华

// 类中添加共有方法

class Person {

constructor(name, age) { // constructor 构造器或构造函数

this.name = name;

this.age = age;

}

say() { // 所有函数不需要写function;多个函数方法之间不需要添加逗号分隔;

console.log(this.name + '你好');

}

}

// 子类继承父类的一些属性和方法

class Son extends Father { // 子类继承父类

}

var son = new Son(); // 注意这里要加上关键字new,同时Son需要定义Father为父类

son.money(); // 或者使用super关键字调用父类的money方法

```

在ES6中,类有以下三个注意点:1.在ES6中没有变量的提升,所以必须先定义类,才能通过类实例化对象。2.类里面的共有属性和方法必须通过添加this来使用。3.constructor里的this指向实例对象,方法里的this指向这个方法的调用者。

以下是根据提供的代码内容进行重构后的代码:

```javascript

// 案例:点击button调用sing方法

var that;

var _that;

class Star {

constructor(uname, age) {

// constructor里面的this指向的是【创建的实例对象】

that = this;

this.uname = uname;

this.age = age;

this.btn = document.querySelector('button'); // 实例对象的btn,所以要加this;

this.btn.onclick = this.sing.bind(this); //此处sing后面不需要加小括号,因为不需要立马就调用,而是要点击按钮再调用;

}

sing() {

// 这个sing方法里面的this指向的是btn这个按钮,因为这个按钮调用了这个函数;

console.log(this.uname); // 从实例上拿uname,所以需要加this

}

dance() {

// 这个dance里面的this,指向的是实例对象ldh,因为ldh调用了这个函数;

_that = this;

}

}

var ldh = new Star('刘德华', '50'); // 利用构造函数创建对象

```

```javascript// 1、利用new Object()创建对象;

var obj1 = new Object();

// 2、利用对象字面量创建对象;

var obj2 = {};

// 3、利用构造函数创建对象;

function Star(uname, age) {

this.uname = uname;

this.age = age;

this.sing = function () {

console.log('唱歌');

};

}

// 利用new关键字创建实例对象;

var ldh = new Star('ldh', 13);

console.log(ldh);

// 静态成员和实例成员(构造函数的属性和方法我们称为【成员】,成员可以添加)

JavaScriptthisStar.sex='男', sexStar.sexthisuname agesingldh.unameStar.unameprototypeprototypejavaprototypeprototypefunction Star(uname,age){this.uname = uname;this.age = age};Star.prototype.sing = function(){console.log('唱歌')} var ldh = new Star('ldh',19); var zxy= new Star('zxy',19); console.log(ldh.sing === zxy.sing);//true比较的是方法的内存地址 ldh.sing(); zxy.sing();

```

console.log(ldh__proto__ === Star.prototype); // true

// 方法的查找规则:

// (1)、首先看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing;

// (2)、如果没有sing这个方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找sing这个方法;

function Star(uname, age){

this.uname = uname;

this.age = age;

}

Star.prototype = {

constructor:Star,

sing:function(){

console.log('唱歌');

},

movie:function(){

console.log('演电影');

}

}

var ldh = new Star('刘德华', 18);

var zxy = new Star('张学友', 18);

console.log(Star.prototype); // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数;

console.log(ldh.__proto__); // 构造函数、实例和原型对象三者之间的三角关系

// 原型链

// __proto__Star__proto__Object.prototypeObject.prototype__proto__null

// JavaScript

// __proto__prototypeObjectObjectnull__proto__

// this

// thisthis

```javascript// 定义一个 Star 类

function Star(uname, age) {

this.uname = uname;

this.age = age;

}

// 在 Star 的原型上添加 sing 方法

Star.prototype.sing = function() {

console.log('唱歌');

};

// 创建一个 Star 实例对象 ldh

var that;

var ldh = new Star('刘德华', 19);

// 调用 sing 方法

ldh.sing();

console.log(that === ldh); // 2、在构造函数中,里面的 this 指向的是实例对象;原型对象函数里面的 this 指向的是调用该方法的实例对象

// 利用原型对象扩展内置对象方法

Array.prototype.sum = function() {

var sum = 0;

for (var i = 0; i < this.length; i++) {

sum += this[i];

}

return sum;

};

var arr = [1, 2, 3];

console.log(arr.sum()); // 6

var arr1 = new Array(11, 22, 33);

console.log(arr1.sum()); // 66

```

在JavaScript中,我们可以使用原型链继承父类型的方法。以下是一个示例代码:

```javascript

// 1、父构造函数

function Father(uname, age) {

this.uname = uname;

this.age = age;

}

Father.prototype.sayAge = function() {

console.log("我的名字是" + this.uname + ",我今年" + this.age + "岁。");

};

// 2、子构造函数

function Son(uname, age, score) {

Father.call(this, uname, age); //调用父构造函数,把父构造函数中的this改为子构造函数中的this

this.score = score;

}

// 将子构造函数的原型指向父构造函数的实例对象,这样子构造函数就可以继承父构造函数的方法了

Son.prototype = new Father();

Son.prototype.constructor = Son;

var son = new Son('刘德华', 12, 90);

son.sayAge(); //输出:我的名字是刘德华,我今年12岁。

console.log(son); //输出:Son {uname: '刘德华', age: 12, score: 90}

```

在这个例子中,我们首先定义了一个父构造函数`Father`,并在其中定义了一个方法`sayAge`。然后我们定义了一个子构造函数`Son`,并在其调用父构造函数时将`this`指向子实例对象。最后,我们将子构造函数的原型指向父构造函数的实例对象,这样子构造函数就可以继承父构造函数的方法了。

```javascript// 一、借用父构造函数继承属性

// 1、父构造函数

function Father(uname, age) {

// this 指向父构造函数的对象实例;

this.uname = uname;

this.age = age;

}

// 二、借用原型对象继承父类型方法

Father.prototype.money = function() {

console.log(10);

}

// 2、子构造函数

function Son(uname, age, score) {

// this 指向子构造函数的实例对象;

Father.call(this, uname, age); //调用父构造函数,把父构造函数中的this改为子构造函数中的this;

this.score = score;

}

// 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也跟着修改了;

Son.prototype = new Father(); // 如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数;

Son.prototype.constructor = Son;

Son.prototype.exam = function() { // 这个是子构造函数专门的方法

console.log('考试');

}

var son = new Son('刘德华', 12, 90);

console.log(son); // Son {uname: '刘德华', age: 12, score: 90}

console.log(Father.prototype);

console.log(Son.prototype.constructor);

```

## call()

### 描述

call() 方法调用一个函数,并将指定的 this 值作为函数运行时的 this 值。

### 语法

```javascript

function.call(thisArg[, arg1[, arg2[, ...]]])

```

### 参数

- `thisArg`:在 function 运行时使用的 this 值。如果省略了该参数,则函数中的 this 值将继承自其执行上下文中的 this 值。

- `arg1, arg2, ...`:传递给函数的参数列表。如果省略了该参数,则函数中没有参数可传入。

### 示例

```javascript

function fn(x, y) {

console.log('1111');

console.log(this); //Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

console.log(x + y); //3

}

var o = {

name: 'andy'

};

//fn(); // undefined

// 1、call() 可以调用函数;

fn.call(); //undefined

// 2、call() 可以改变这个函数的this指向;

fn.call(o, 1, 2); //让this指向o这个对象;(o后面可以写需要传递的参数,如此处:1传给了x,2传给了y)

```

在ES6之前,我们通过构造函数和原型链实现面向对象编程。以下是一些关于构造函数的特点:

1. 构造函数有原型对象`prototype`;

2. 构造函数原型对象`prototype`里面有`constructor`,指向构造函数本身;

3. 构造函数可以通过原型对象添加方法;

4. 构造函数创建的实例对象有`__proto__`原型指向构造函数的原型对象。

而在ES6中,我们通过类来实现面向对象编程。类的本质其实还是一个函数,我们也可以简单的认为,类就是构造函数的另外一种写法。以下是一些关于类的特点:

1. 类有原型对象`prototype`;

2. 类原型对象`prototype`里面有`constructor`,指向类本身;

3. 类可以通过原型对象添加方法;

4. 类创建的实例对象有`__proto__`原型指向类的原型对象。

下面是一个使用ES6类的例子:

```javascript

class Star {

}

console.log(typeof Star); // function

// (1) 类有原型对象prototype;

console.log(Star.prototype);

// (2) 类原型对象prototype里面有constructor,指向类本身;

console.log(Star.prototype.constructor);

// (3) 类可以通过原型对象添加方法;

Star.prototype.sing = function() {

console.log('唱歌');

}

var ldh = new Star();

console.dir(ldh);

// (4) 类创建的实例对象有__proto__原型指向类的原型对象;

console.log(ldh.__proto__ === Star.prototype); // true

```

## forEach() 迭代(遍历)数组

`forEach()` 是 JavaScript 中用于迭代数组的常用方法之一。它接受一个回调函数作为参数,并在每次循环时调用该函数,将当前元素、索引和整个数组传递给回调函数。

以下是示例代码:

```javascript

var arr = [1, 2, 3];

var sum = 0;

arr.forEach(function(value, index, array) {

console.log('数组元素' + value);

console.log('数组元素的索引号' + index);

console.log('数组本身' + array);

sum += value; //求和

});

console.log(sum); //6

```

上述代码首先定义了一个包含三个元素的数组 `arr`,然后通过 `forEach()` 方法迭代数组,并在每次循环时输出当前元素、索引和整个数组。最后打印出数组中所有元素的和。

### filter() 筛选数组元素

`filter()` 是 JavaScript Array 对象的一个方法,用于根据指定的条件筛选数组元素,并返回一个新的数组。

以下是示例代码:

```javascript

var arr = [23, 40, 1, 14, 39, 99];

var newArr = arr.filter(function(value, index, arr) {

// 筛选出大于等于 20 的数

return value >= 20;

});

// 也可以筛选出偶数:return value % 2 === 0;

console.log(newArr); // [23, 40] 或者 [23, 14, 39, 99] 或者 [23] 或者 [40] 或者 [] 或者 []

```

上述代码使用 `filter()` 方法筛选出大于等于 20 的数,并将结果存储在 `newArr` 变量中。你可以根据需要修改条件来实现不同的筛选逻辑。最后打印出筛选后的新数组 `newArr`。

以下是重构后的代码:

```javascript

// 查找数组中是否有满足条件的元素;

// 查找数组中是否有大于等于20的元素;

var arr = [10, 30, 4];

var flag = arr.some(function (value, index, arr) {

return value >= 20;

});

console.log(flag); // true

// 查找数组中是否有包含pink的元素;

var arr1 = ['pink', 'plue', 'red'];

var flag1 = arr1.some(function (value, inex, arr) {

return value === 'pink';

});

console.log(flag1); // true

```

说明:

- `some()` 是 JavaScript 中的一个数组方法,用于查找数组中是否存在满足条件的元素。它会遍历数组中的每个元素,并对每个元素执行回调函数。如果回调函数返回 `true`,则立即停止遍历并返回 `true`,否则继续遍历直到数组末尾。如果找到满足条件的元素,`some()` 会返回 `true`,否则返回 `false`。

- 在上述示例中,我们首先定义了一个名为 `arr` 的数组,然后使用 `some()` 方法查找该数组中是否存在大于等于20的元素。结果为 `true`,因为数组中有元素30和4都满足条件。接着我们定义了另一个名为 `arr1` 的数组,并使用 `some()` 方法查找该数组中是否存在包含 "pink" 的元素。结果也为 `true`,因为数组中有元素 "pink"。

Document

id 产品名称 价格

以下是重构后的代码:

```javascript

var arr = ['red', 'green', 'blue', 'pink'];

// 1、forEach迭代 遍历

arr.forEach(function (value) {

if (value === 'green') {

console.log('找到该元素了');

return true; // 在forEach里面return true不会终止迭代

}

console.log(value); // 打印数组中的每个元素

});

// 2、some 迭代 遍历(如果查询数组中唯一的元素,用some方法更合适)

arr.some(function (value) {

if (value === 'green') {

console.log('找到该元素了');

return true; // 在some里面return true就是终止遍历,效率更高

}

console.log(value); // 只打印数组中的每个元素,找到'green'后就不再往下执行了

});

```

以下是重构后的代码:

```html

```

以下是重构后的内容:

```javascript

// 定义新属性或修改原有的属性

function defineProperty(obj, key, options) {

Object.defineProperty(obj, key, options);

}

// 示例对象

const obj = {

id: 1,

pname: "小米",

price: 1999

};

// 使用 Object.defineProperty() 定义新属性或修改原有的属性

defineProperty(obj, 'num', { value: 9000 });

defineProperty(obj, 'price', { value: 9.9 });

defineProperty(obj, 'id', { writable: false }); // 如果值为false,则不允许重写(不允许修改该属性值)

obj.id = '999';

defineProperty(obj, 'address', {

value: '南京',

writable: false, // 如果值为false,则不允许重写(不允许修改该属性值)

enumerable: false, // 如果值为false,则不允许遍历,默认的值是 false

configurable: false // 如果为false则不允许删除某个属性,并且不允许再次修改第三个参数里面的【特性】

});

console.log(Object.keys(obj)); // 删除对象中的某个属性

delete obj.address;

```

函数进阶:

1. 函数的定义和调用

2. 函数的定义方式

函数的定义方式有以下几种:

- 自定义函数(命名函数):

```javascript

function fn() {

}

```

- 函数表达式(匿名函数):

```javascript

var fun = function () {

};

```

- 利用 `new Function()` 创建函数:

```javascript

var f = new Function('console.log(123)');

f();

```

- 所有函数都是 Function 的实例(对象):

```javascript

console.dir(f); // 输出:[Function]

console.log(f instanceof Object); // true

```

函数的调用方式有以下几种:

1. 普通函数:直接调用即可。

2. 对象的方法:通过对象名加点号的方式调用。

3. 构造函数:通过 `new` 关键字调用。

4. 绑定事件函数:在 HTML 标签中使用 `onclick`、`onmouseover` 等属性绑定事件。

5. 定时器函数:使用 `setTimeout`、`setInterval` 等方法创建定时器。

6. 立即执行函数:使用 `eval()` 或 `Function()` 立即执行字符串形式的函数代码。

```javascript// 1、普通函数

function fn() {

console.log(111);

}

// fn(); 或 fn.call();

// 2、对象的方法(把函数放到对象里面叫方法)

var o = {

sayHi: function () {

console.log(2222);

}

};

o.sayHi();

// 3、构造函数

function Star() {}

new Star(); // 产生一个新的实例对象;

// 4、绑定事件函数

btn.onClick = function () {}; // 点击了按钮就可以调用这个函数;

// 5、定时器函数

setInterval(function () {}, 1000); // 这个函数是定时器自动1秒钟调用一次;

// 6、立即执行函数(立即执行函数是自动调用)

(function () {

console.log(3333);

})();

```

改变函数内this指向,js提供了三种方法:call()、apply()、bind(); 1、call(),第一个可以调用函数,第二个可以改变函数内的this指向;

```javascript

var o = {

name: 'andy'

};

function fn(a, b) {

console.log(a);

console.log(b);

// console.log(this);

console.log(a + b);

}

fn(); // this 指向 window;

fn.call(o, 1, 6); // this 指向 o 对象;

// call() 的主要作用可以实现继承;

function Father(uname, age, sex) {

this.uname = uname;

this.age = age;

this.sex = sex;

}

function Son(uname, age, sex) {

Father.call(this, uname, age, sex); //调用Father函数,并将Father的this指向改为Son的this;

}

var son = new Son('刘德华', 18, '男');

console.log(son);

```

请根据提供的内容完成内容重构,并保持段落结构:

```javascript

// 2、apply() 应用、运用的意思

var o = {

name: 'andy',

};

function fn(arr) {

console.log(this); // this 指向 window;

console.log(arr); // pink(注意:打印出来的是一个字符串,不是数组)

}

fn();

fn.apply(o, ['pink']); // this 指向 o;

// 1、也是调用函数,第二个可以改变函数内部的 this 指向;

// 2、但是它的参数必须是数组形式的(伪数组);

// 3、apply 的主要应用:比如说我们可以利用 apply 借助于数学内置对象求最大值;

// Math.max();

var arr = [1, 33, 55, 99, 8];

// var max = Math.max.apply(null, arr); // null 表示不需要改变 this 指向;

var max = Math.max.apply(Math, arr); // this 指向方法 max 的调用者 Math,所以写成 Math 比 null 合适;

console.log(max); // 99

```

## 严格模式

1. 什么是严格模式以及如何开启严格模式

JavaScript的严格模式(strict mode)是一种在JavaScript引擎中执行的规则集,它提供了额外的错误检测机制和更严格的变量定义。严格模式可以在代码中通过`"use strict"`语句进行开启,也可以使用`"use strict";`语句块来开启。以下是几种不同方式的示例:

```javascript

// 通过 "use strict" 语句开启严格模式

"use strict";

var num = 10;

console.log(num);

// 通过 "use strict"; 语句块开启严格模式

"use strict";

{

var num = 10;

console.log(num);

}

```

2. 开启严格模式

严格模式默认情况下是关闭的,需要手动通过`"use strict"`或`"use strict";`语句进行开启。如果想要在一个文件中同时应用多个函数或代码块的严格模式,可以将整个脚本文件放在一个立即执行的匿名函数之中,这样可以独立创建一个作用域而不影响其他脚本文件。以下是一个示例:

```javascript

(function(){ // 在立即执行的匿名函数中开启严格模式

"use strict";

var num = 10;

function fn(){}

})(); // 匿名函数执行完毕后自动清除其作用域

```

3. 严格模式中的变化

在严格模式下,有几个与变量声明、函数调用等相关的行为变化需要注意:

- **变量定义**:在严格模式下,变量必须先声明再使用。如果在声明之前进行赋值操作或尝试访问未声明的变量,将会报错。比如下面的代码会导致"Uncaught ReferenceError: num is not defined"错误:

```javascript

var num; // 变量未声明就被赋值

console.log(num); // num is not defined

```

要解决这个问题,需要将变量声明放在赋值操作之前:

```javascript

var num = 10; // 先声明变量再赋值

console.log(num); // 输出 10

```

- **删除属性**:在严格模式下,不能使用delete操作符删除不存在的属性。例如,以下代码会报错:

```javascript

var obj = {};

delete obj.prop; // Uncaught TypeError: Cannot read property 'prop' of undefined (reading 'prop')

```

```javascript'use strict'; // 2、严格模式下不能删除已经声明好的变量;

var num = 10;

console.log(num);

delete num; //Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.

function Star() {

this.sex = '男';

}

Star();

console.log(window.sex); //普通模式下可以打印出“男”;严格模式下报错07.开启严格模式.html:25 Uncaught TypeError: Cannot set properties of undefined (setting 'sex') // 严格模式下全局作用域中函数的`this`是`undefined`;

```

Document

闭包是指有权访问另一个函数作用域中变量的函数。一个作用域可以访问另外一个函数的局部变量,我们称这个被访问的作用域为“词法作用域”。

闭包的主要作用是延伸了变量的作用范围。下面通过一个例子来说明闭包的概念和作用:

```javascript

function fn() {

var num = 10;

function fun() {

console.log(num);

}

// fun(); // 这里会报错,因为在调用fun()时,num还没有定义

return fun; // 返回fun函数,此时num已经定义

}

var f = fn(); // 将fn函数的返回值赋值给f

f(); // 输出10,因为闭包使得fun函数可以访问到fn函数内部的变量num

```

```html

Document

```

以下是根据提供的内容重构的闭包案例代码,用于计算打车价格:

```javascript

// 闭包应用:计算打车价格

var calculateTaxiPrice = function (distance) {

var costPerKm = 10.0; // 每公里费用

var taxRate = 0.1; // 税率

var totalCost = distance * costPerKm; // 总费用

var taxAmount = totalCost * taxRate; // 税额

var finalPrice = totalCost + taxAmount; // 最终价格

return finalPrice;

};

// 使用闭包调用函数,传入距离参数

calculateTaxiPrice(8.2); // 输出最终价格

```

在这个闭包案例中,我们定义了一个名为 `calculateTaxiPrice` 的函数。该函数接受一个参数 `distance`,表示乘车的距离(以公里为单位)。在函数体内,我们定义了一些变量来计算总费用、税额以及最终价格,并将这些值返回给调用者。通过使用闭包,我们可以在函数内部访问外部作用域的变量,而不会污染全局作用域。这样可以实现封装和数据的隔离。

```html

```

```javascript// 1、思考题一:

var name = "The Window";

// 在全局作用域下定义的属性是挂在window下的;

var object = {

name: "My Object",

getNameFunc: function() {

return function() {

return this.name; //此处相当于一个立即执行函数,所以此处的this指向的是window;

};

}

};

console.log(object.getNameFunc()()); // The Window

// 分析

// var f = object.getNameFunc();

// 类似于

// var f = function() {

// return this.name;

// };

// f();

// function(){this}() //立即执行函数里的this指向的是window;

// 2、思考题二:

var name = "The Window";

var object = {

name: "My Object",

getNameFunc: function() {

var that = this; //此处的this指向的是object;

return function() {

return that.name; //

};

}

};

console.log(object.getNameFunc()()); // My Object,有闭包的产生;

// 分析

// var f = object.getNameFunc();

// var f = function() {

// return that.name;

// };

// f();

```

闭包是指在一个函数内部定义的函数可以访问到该函数外部的变量。 闭包的作用是能够访问外部函数的变量,在某些编程场景中非常有用。例如,当需要创造一些私有变量或者是提供一个能够访问外部作用域的回调函数时,闭包就可以发挥作用。

递归是指在函数内部调用自身的过程。 利用递归求阶乘的例子如下:function factorial(n) { if (n === 1) { return 1; } else { return n * factorial(n-1); } } console.log(factorial(3)); // 6。

利用递归求斐波那契数列的例子如下:function fibonacci(n) { if (n <= 1) { return n; } else { return fibonacci(n-1) + fibonacci(n-2); } } console.log(fibonacci(10)); // 55。

lt;>

// 利用递归函数求斐波那契数列(兔子序列)1、1、2、3、5、8、13、21...

// 用户输入一个数字n就可以求出这个数字对应的兔子序列值;

// 我们只需要知道用户输入的n的前面两项(n-1 )就可以计算出n对应的序列值;

// 这种方法效率较低,输入的数值较大时直接卡死;

function getDataById(id, dataList) {

if (dataList.length === 0) {

return null; // 如果数据列表为空,则返回null

} else {

const data = dataList[0]; // 取出数据列表中的第一个元素

if (data.id === id) { // 如果该元素的id等于用户输入的id

return data; // 则返回该元素

} else if (data.children && data.children.length > 0) { // 如果该元素有子元素

const result = getDataById(id, data.children); // 则递归查找子元素中是否有对应id的数据对象

if (result !== null) {

return result; // 如果找到了,则返回该数据对象

} else {

return null; // 否则返回null

}

} else {

return null; // 如果该元素没有子元素,则返回null

}

}

}

</>

Document

## 浅拷贝和深拷贝

在 JavaScript 中,对象的复制有浅拷贝和深拷贝两种方式。

##### 浅拷贝

浅拷贝只是拷贝一层,即只复制对象的最外层属性,更深层次的对象级别的属性只会拷贝其引用而非实际值。

```javascript

var obj = {

id: 1,

name: 'andy',

msg: {

age: 18

}

};

var o = {};

for (var k in obj) {

o[k] = obj[k];

}

console.log(o); // 输出:{ id: 1, name: "andy", msg: { age: 18 } }

o.msg.age = 90;

console.log(obj); // 输出:{ id: 1, name: "andy", msg: { age: 18 } },msg属性未被修改

```

可以看到,在浅拷贝过程中,如果要修改对象内部属性的值,不会影响到原对象。

##### 深拷贝

深拷贝会拷贝多层,每一级别的数据都会进行复制。可以使用 `Object.assign()` 或者 ES6 中的展开运算符来实现深拷贝。

```javascript

Object.assign(o, obj); // 使用 Object.assign() 实现深拷贝

// 或者使用展开运算符:

// var o = { ...obj };

console.log(o); // 输出:{ id: 1, name: "andy", msg: { age: 90 } }

```

可以看到,在深拷贝过程中,修改了 `o` 对象中的 `msg` 属性后,不会影响到 `obj` 对象中的 `msg` 属性。

### Regular Expressions (正则表达式)

在 JavaScript 中使用正则表达式可以进行字符串匹配、替换等操作。创建正则表达式的常用方法有两种:通过字面量创建和通过 RegExp 构造函数创建。

#### 通过字面量创建正则表达式语法糖

```javascript

// 直接用字符串表示正则表达式的形式是字面量形式;注释中间放表达式就是正则字面量;例如下面的正则表达式表示匹配一个以字母 a 开头、后面跟一个或多个数字的字符串。

var regexObj = new RegExp('a\\d+'); // a数字+ // test() true false regexObj.test('apple34') // false // 如果需要忽略大小写,可以使用正则表达式的修饰符 i;例如上面的正则表达式可以改为 'a\\d+i'。test() false true regexObj.test('Apple34') // true // 如果需要全局匹配整个字符串,可以在正则表达式的末尾加上 g;例如上面的正则表达式可以改为 'a\\d+gi'。test() true true regexObj.test('abc123def456ghi789') // true // 如果需要匹配除了换行符以外的任意字符,可以使用特殊的字符类 [^],其中 \d 表示数字字符,^ 在前面表示取反;例如上面的正则表达式可以改为 'a\\d+\\D*'。test() true true regexObj.test('123abc

456def') // true // 如果需要匹配除了字母、数字和下划线以外的任意字符,可以使用字符类 [^a-zA-Z0-9_],其中 $ 在前面表示取反;例如上面的正则表达式可以改为 'a\\d+\D*[$^+]*\\D*'。test() true true regexObj.test('!@#abc$%^&*()_+') // true // 如果想要匹配的字符串中包含特殊字符,需要对这些特殊字符进行转义;例如上面的正则表达式可以改为 'a\\d+\\D*[$^+]*\\D*[^\\w]'。test() true true regexObj.test('!@#abc$%^&*()_+?<>{}[]|') // true // 如果想要进行分组匹配,可以使用括号 [] 将需要匹配的内容括起来;例如上面的正则表达式可以改为 'a(\\d+)\D+(\\d+)'。test() true true regexObj.group(0) // 'a34

' // 如果想要获取某个分组匹配的结果,可以使用 group() 方法;例如上面的正则表达式可以改为 'a(\d+)\\D+(\\d+)'。regexObj.group(1) // '34' // 如果想要替换匹配的字符串,可以使用 replace() 或者 exec() 方法;例如上面的正则表达式可以改为 'a34

b456c789'。regexObj.replace('b', 'X') // 'a34Xbc789' // 如果想要查找匹配的字符串位置,可以使用 search() 或者 exec() 方法;例如上面的正则表达式可以改为 'abc

abcdefg'。regexObj.search('efg') // 5 // 如果想要从字符串中提取出匹配的子串,可以使用 exec() 或者 String.prototype.match() 方法;例如上面的正则表达式可以改为 'abc

abcdefg'。regexObj.exec('abcdefg') // ['abc'] // 注意 exec() 只返回最后一次匹配的结果;如果需要获取所有匹配的结果,可以使用 String.prototype.match()。var str = 'abcdefg'; var matches = str.match(/abc/g); console.log(matches); // ['abc'] // 如果想让 exec() 只执行一次就返回所有结果而不需要循环调用多次,可以传入一个标志位作为参数。var result = []; var match; while ((match = regexObj.exec('abcdefg')) !== null) result.push(match[0]); console.log(result); // ['abc'] // 如果想让 exec() 只执行一次就返回所有结果而不需要循环调用多次,也可以先将整个字符串分割成数组后再逐个判断是否匹配。var strArr = str.split('abc'); console.log(strArr); // ['ab', 'cde', 'fg'] var count = strArr.filter(function(item) { return item === 'abc'; }); console.log(count); // ['ab', 'cde', 'fg'] var result = []; for (var i = strArr.length; i--;) if (strArr[i] === 'abc') result.push(strArr[i + 1]); console.log(result); // ['def', 'ghi'] // 在一些语言中(如 Python),可以直接使用 re 这个内置模块提供的函数来创建一个正则表达式对象;例如下面的代码就可以在 Python3 中运行。import re; var regex = re.compile('a\\d+'); console.log(regex.findall('abc123def456ghi789')); # ['abc123', 'def456']

以下是重构后的代码:

```javascript

// 边界符 ^ $

var rg = /abc/; // 正则表达式里面不需要加引号,不管是数字型还是字符串型;

// /abc/只要包含有abc这个字符串返回的都是true;

console.log(rg.test("abc")); // true

console.log(rg.test("abcd")); // true

console.log(rg.test("aabcd")); // true

console.log("------------");

// 以abc开头

var reg = /^abc/;

console.log(reg.test("abc")); // true

console.log(reg.test("abcd")); // true

console.log(reg.test("aabcd")); // false

console.log("------------");

// 精确匹配,要求必须是abc字符串才符合规范;即以abc开头,又以abc结尾;

var reg1 = /^abc$/;

```

字符类

```javascript

// 字符类:[] 表示有一系列字符可供选择,只要匹配其中一个就可以了;

var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true;

console.log(rg.test('andy')); // 字符串andy里面包含有a,所以打印结果为true;

var reg = /^[abc]$/; // 三选一 只有是a 或者是b 或者是c 这三个字母才返回true;

var rg1 = /^[a-z]$/; // 26个英文字母任何一个字母返回true;-表示的是a到z的范围;

// 字符组合

var reg2 = /^[a-zA-Z]$/; // 在小写和大写的26个英文字母中任选一个都可以;

var reg3 = /^[a-zA-Z0-9]$/; // 小写和大写的26个英文字母、0-9的数字

var reg4 = /^[a-zA-Z0-9_]$/; // 小写和大写的26个英文字母、0-9的数字、_

// 如果中括号里面有^,表示取反的意思,千万别和边界符^混淆;

var reg5 = /^[^a-zA-Z0-9_-]$/; // 第一个^是边界符,表示以它开头;第二个^表示取反,即不能包含26个大写和小写英文字母和_-,

```

以下是根据提供的内容重构后的代码:

```javascript

// 量词符:用来设定某个模式出现的次数;

// 简单理解:就是让下面的a这个字符重复多少次;

var reg = /^a$/;

// * 相当于 >= 0,可以出现0次或者很多次;

var reg1 = /^a*$/;

console.log(reg1.test('')); // true

console.log(reg1.test('aaaaaa')); // true

// + 相当于 >= 1,可以出现1次或者很多次;

// ? 相当于 1 || 0;

// {3 } 就是重复3次;

var reg2 = /^a{3}$/;

console.log(reg2.test('aaa')); // false

console.log(reg2.test('aaabc')); // true

console.log(reg2.test('aaabcaa')); // true

console.log(reg2.test('aaabbc')); // false

console.log(reg2.test('aaaaaaaaa')); // true

console.log(reg2.test('aaaaaaaaaaa')); // true

console.log(reg2.test('aaaaaaaaaaaa')); // true

console.log(reg2.test('aaaaaaaaaaaaaa')); // true

console.log(reg2.test('aaaaaaaaaaaaab')); // false

console.log(reg2.test('aaaaaaaaaaaaac')); // true

console.log(reg2.test('aaaaaaaaaaaaaa')); // true

```

Document

请输入用户名

```javascript// 括号总结

// 大括号 量词符。 里面表示重复次数;

var reg = /^[abc]$/; // a也可以,b也可以,c也可以;

// 中括号 字符集合。 匹配方括号中的任意字符;

var reg1 = /^abc{3}$/; //它只是让c重复3次;abccc

console.log(reg1.test(abc)); // false

console.log(reg1.test(abcabcabc)); // false

console.log(reg1.test(abccc)); // true

// 小括号 表示优先级;

var reg2 = /^(abc){3}$/; //把abc重复3次;

// 预定义类以及座机号码验证

// 预定义类:指的是某些常见模式的简写方式;

var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/; // 全国座机号码 两种格式:010-12345678 或者 0530-1234567

var reg1 = /^\d{3,4}-\d{7,8}$/; // 座机号码验证:全国座机号码 两种格式:010-12345678 或者 0530-1234567

// 案例:表单验证

```

```javascript

window.onload = function () {

var regtel = /^1[3|4|5|7|8]\d{9}$/; // 手机号码的正则表达式

var regqq = /^[1-9]\d{4,}$/; // QQ号1000

var regnc = /^[u4e00-\u9fa5]{2,8}$/;//昵称

var regmsg = /^\d{6}$/;//短信验证码,由6位数字组成

var regpwd = /^[a-zA-Z0-9]{6,16}$/;//密码

var tel = document.querySelector('#tel');

var qq = document.querySelector('#qq');

var nc = document.querySelector('#nc');

var msg = document.querySelector('#msg');

var pwd = document.querySelector('#pwd');

var surepwd = document.querySelector('#surepwd');

regexp(tel, regtel); //手机号码验证

regexp(qq, regqq); //QQ号码验证

regexp(nc, regnc); //昵称验证

regexp(msg, regmsg); //短信验证码

regexp(pwd, regpwd); //密码框验证

//表单验证的函数封装

function regexp(ele, reg) {

ele.onblur = function () {

if (reg.test(this.value)) {

console.log('正确的');

this.nextElementSibling.className = 'success'; //nextElementSibling下一个兄弟

this.nextElementSibling.innerHTML = '恭喜你输入正确';

} else {

console.log('错误的');

this.nextElementSibling.className = 'error'; //nextElementSibling下一个兄弟

this.nextElementSibling.innerHTML = '输入错误';

}

}

}

//确认密码

surepwd.onblur = function () {

if (this.value === pwd.value) {

this.nextElementSibling.className = 'success'; //nextElementSibling下一个兄弟

this.nextElementSibling.innerHTML = '恭喜你输入正确';

} else {

this.nextElementSibling.className = 'error'; //nextElementSibling下一个兄弟

this.nextElementSibling.innerHTML = '两次输入密码不一致';

}

}

}

```

replace()` 函数用于将字符串中的某个子串替换为另一个子串。它有以下语法:

```javascript

stringObject.replace(regexp|substr, replacement)

```

其中:

- `regexp`:正则表达式,用于匹配需要被替换的子串。如果省略此参数,则默认使用 `substr` 进行替换。

- `substr`:一个字符串,表示需要被替换的子串。如果省略此参数,则默认使用正则表达式进行替换。

- `replacement`:一个字符串,表示替换后的新子串。可以是一个变量或表达式,也可以是一个字符串字面量。

- `switchgigi`:这个参数似乎与 `replace()` 函数无关,可能是误输入或者不完整的代码片段。

Document

ES6

ECMAScriptECMA

varletconst

面试题一:

```javascript

var arr = [];

for (var i = 0; i < 2; i++) { //for循环里用 var 申明变量,是一个全局变量;

arr[i] = function () {

console.log(i);

}

}

// arr[0] 和 arr[1] 是两个函数,下面两句代码是在调用数组中的函数;

arr[0](); //2 函数执行时在自己的作用域中找不到i值,根据作用域链会到上级作用域中去查找;

arr[1](); //2

// 当循环条件为 0 和 1 时,进入循环体;当循环条件为2时才跳出循环体,开始执行循环体后面的代码;

// 注意:当i=0 和 i=1时,函数并没有执行;

// 此题的关键点在于:变量i是全局的,函数执行时输出的都是全局作用域下的i值;

// 当i=0 和i=1 时,arr数组中有两个值,这两个值都是 function () {console.log(i);}

```

面试题二:

```javascript

let arr = [];

for (let i = 0; i < 2; i++) {

arr[i] = function () {

console.log(i);

}

}

// arr[0] 和 arr[1] 是两个函数,下面两句代码是在调用数组中的函数;

arr[0](); //0

arr[1](); //1

// 此题的关键点在于:每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的;

// 函数执行时,输出的是自己上一级(循环产生的块级作用域)作用域下的i值;

```

const:声明常量,常量就是值(内存地址)不能变化的量。

在ES6中,我们可以使用以下特性来声明常量、解构赋值以及箭头函数:

1. 声明常量时必须赋初始值。

2. 常量赋值后,基本数据类型的值不能更改,但复杂数据类型内部的值可以更改。

3. 解构赋值允许我们分解数据结构,并为变量赋值。数组解构和对象解构分别用于处理数组和对象。

4. 箭头函数是一种简洁的函数表示方法,可以在函数体中只有一句代码的情况下使用。如果形参只有一个,可以省略小括号。

下面是一些示例代码:

```javascript

// 声明常量并赋初始值

const PI = 3.1415926;

// 常量赋值后,基本数据类型的值不能更改

const a = 1;

const b = 'hello';

// 常量赋值后,复杂数据类型内部的值可以更改

const arr = [1, 2, 3];

const [a, b, c, d, e] = arr; // a、b、c和arr中的1、2、3是一一对应的关系

console.log(a); // 1

console.log(b); // 2

console.log(c); // 3

console.log(d); // undefined

console.log(e); // undefined

// 对象解构

let person = { name: '张三', age: 20 };

let { name, age } = person; // console.log(name);//张三 // console.log(age);//20

let { name: myName } = person; // 此处的name只是用来属性匹配;此处的myName才是真正的变量; console.log(myName); // 张三

// 箭头函数

const add = (a, b) => a + b;

const result = add(1, 2); // fn()//调用箭头函数

console.log(result); // 3

```

```javascript// 普通函数写法;

function sum(num1, num2) {

return num1 + num2;

}

// 箭头函数写法

const sum = (num1, num2) => {

return num1 + num2;

};

// 1、在箭头函数中,如果函数体中只有一句代码,并且代码的执行结果就是函数的返回值,函数体大括号和return可以省略;

const sum = (num1, num2) => num1 + num2;

// 2、如果形参只有一个,可以省略小括号;

function fn(v) {

return v;

}

const fn = v => v;

```

```javascript// 函数式编程:sum函数

function sum(first, ...args) {

console.log(first); // 10

console.log(args); // [20, 30]

}

sum(10, 20, 30);

// 注意:在箭头函数中,使用不了arguments;

const sum = (...args) => {

let total = 0;

args.forEach(item => total += item);

return total;

};

console.log(sum(10, 20)); // 30

console.log(sum(10, 20, 30)); // 60

// 剩余参数和解构配合使用

let students = ['王五', '张三', '李四'];

let [s1, ...s2] = students; // s1 接收'王五';...s2 接收'张三','李四',注意 s2 是一个数组;

console.log(s1); // '王五'

console.log(s2); // ['张三', '李四']

// Array扩展运算符(展开语法)

console.log(); console.log();

let arr = [1, 2, 3];

// ...arr ---> 'a', 'b', 'c'

console.log(...arr); // 1 2 3

// Array扩展运算符应用:用于【合并数组】

let newArr = Array.from([4, 5], x => x * 2); // [8, 10]

```

// 扩展运算符

// let arr = [1, 2, 3];

// ...arr ---> "a", "b", "c"

// console.log(...arr); // 1 2 3

// 扩展运算符应用

// 1、用于合并数组

// 合并数组方法1:

let arr1 = [1, 2, 3];

let arr2 = [4, 5, 6];

// ...arr1 // 1,2,3

// ...arr2 // 4,5,6

let arr3 = [...arr1, ...arr2]

// console.log(arr3); // [1, 2, 3, 4, 5, 6]

// 合并数组方法2:

arr1.push(...arr2);

console.log(arr1); // [1, 2, 3, 4, 5, 6]

// 2、将类、数组或者可遍历对象转换为真正的数组

let oDivs = document.getElementsByTagName('div');

oDivs = [...oDivs];

// Array.from 方法(将伪数组转换为真正的数组)

var arrayLike = {

'0': '张三',

'1': '李四',

'2': '王五',

'length': 3

}

var ary = Array.from(arrayLike);

console.log(ary); // ['张三', '李四', '王五']

```javascript// Array.from 还可以接收第二个参数,第二个参数是一个函数,对数组中的元素进行加工处理,数组中有多少个元素,该函数就会被调用多少次;形参item代表当前要处理的那个值;

let arrayLike = {

"0": 1,

"1": 2,

"length": 2

};

let newAry = Array.from(arrayLike, item => item * 2);

console.log(newAry); // [2, 4]

// Array.find()

let ary = [{

id: 1,

name: '张三'

}, {

id: 2,

name: '李四'

}];

let target = ary.find((item, index) => item.id === 2);

console.log(target); // {id: 2, name: '李四'}

// Array.findIndex()

let arr = [1, 5, 10, 15];

let index = arr.findIndex((value, index) => value > 9);

console.log(index); // 2

// Array.includes()

console.log([1, 2, 3].includes(2)); // true

console.log([1, 2, 3].includes(5)); // false

// String

// 1、模版字符串

console.log(`Hello, ${name}!`); // Hello, 张三!

```

```javascriptlet result = { name: 'zhangsan', age: 20, sex: '男' };

const html = `

${result.name}${result.age}${result.sex}
`;

console.log(html);

function sayHello() {

return '哈哈哈哈哈哈';

}

const greet = `${sayHello()}aaaa`;

console.log(greet);

console.log('x'.repeat(3)); // 'xxx'

console.log('hello'.repeat(2)); // 'hellohello'

const s = new Set();

const set = new Set([1, 2, 3, 4, 4]);

```

```javascript

Set

add(value) //向集合中添加一个元素,并返回该元素的数量

Set delete (value) //删除集合中的一个或多个元素

has (value) //判断一个值是否存在于集合中

Set clear () //清空集合中的所有元素

// 示例1:创建一个空的Set集合

const s1 = new Set();

console.log(s1.size); //0

// 示例2:创建一个包含特定元素的Set集合

const s2 = new Set([ 'a', 'b' ]);

console.log(s2.size);

// 示例3:创建一个包含重复元素的Set集合

const s3 = new Set([ 'a', 'a', 'b', 'b' ]);

console.log(s3); // Set(2) { 'a', 'b' }

console.log(s3.size); // 2 Set会把重复的值给过滤掉;

// 将Set集合转换为数组

const arr = [...s3];

console.log(arr); //[ 'a', 'b' ]

```