JS高级知识总结

文章目录

  • 1. this指向问题
  • 2. 对象进阶
    • 2.1 对象的定义和使用
    • 2.2 对象访问器
      • 2.2.1 Getter
      • 2.2.2 Setter
    • 2.3 对象构造器
    • 2.4 对象原型
      • 2.4.1 prototype属性
      • 2.4.2 \_\_proto\_\_ 属性
      • 2.4.3 constructor属性
      • 2.4.4 原型链
    • 2.5 Object对象
      • 2.5.1 管理对象
      • 2.5.2 保护对象
  • 3. 函数进阶
    • 3.1 函数的定义和使用
    • 3.2 Function对象
      • 3.2.1 call
      • 3.2.2 apply
      • 3.2.3 bind
    • 3.3 高阶函数
    • 3.4 闭包
  • 4. 异常
  • 5. JSON
    • 5.1 JSON语法
    • 5.2 JSON解析
    • 5.3 JSON字符串化
  • 6. 拷贝
    • 6.1 浅拷贝
    • 6.2 深拷贝
  • 7. ES6

1. this指向问题

this指向:this的指向在函数定义是无法确定的,只有在函数执行时才能确定this到底指向谁,一般情况下this的指向就是调用它的对象。

一般分为如下三种情况:

  • 全局作用域或普通函数中,this指向全局对象window
  • 方法调用中谁调用this指向谁
  • 构造函数中this指向构造函数的实例

1.全局作用域或普通函数中,this指向全局对象window

全局作用域中的函数调用,是window对象调用的,只不过调用的时候,一般会省略掉window.,也就是fn()其实就是window.fn

console.log(this); // Window {window: Window, …}
function fn() {
  console.log(this); // Window {window: Window, …}
}

fn(); // 等同于window.fn()
// 等同于window.setTimeout()
setTimeout(function () {
  console.log(this); // Window {window: Window, …}
}, 1000);

2.方法调用中谁调用this指向谁

obj = {
  fn: function () {
    console.log(this); // {fn: ƒ}
  }
};

obj.fn(); // obj对象调用了fn方法
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>点击一下</button>
    <script>
      var btn = document.querySelector("button");

      // btn调用了匿名函数
      btn.addEventListener("click", function () {
        console.log(this); // <button>点击一下</button>
      });
    </script>
  </body>
</html>

3.构造函数中this指向构造函数的实例

创建对象时,会开辟一个新空间,this会指向这个对象

function Person() {
  console.log(this); // Person {}
}

var person = new Person(); // this指向的是person实例对象

2. 对象进阶

2.1 对象的定义和使用

1.使用字面量创建对象

// 创建对象
var obj = {
  name: "Bill",
  age: 18,
  sex: "男",
  sayHi: function () {
    console.log("hi");
  }
};

// 获取对象的属性
console.log(obj.age);
console.log(obj["age"]);

// 调用对象的方法
obj.sayHi();

2.使用new关键字创建对象

// 创建空对象
var obj = new Object(); 

// 设置对象的属性和方法
obj.name = "Bill";
obj.age = 18;
obj.sex = "男";
obj.sayHi = function () {
  console.log("hi");
};

// 获取对象的属性
console.log(obj.age);
console.log(obj["age"]);

// 调用对象的方法
obj.sayHi();

注:不建议使用new关键字创建对象的方式

2.2 对象访问器

GetterSetter的作用:

  • 提供了更简洁的语法
  • 允许属性和方法的语法相同
  • 可以确保更好的数据质量

注:gettersetter 的方法名不能与属性名相同

2.2.1 Getter

Getter:使用 get关键词,来获取对象的属性值。

var person = {
  name: "Bill",
  age: 18,
  get uname() {
    return this.name;
  },
  get uage() {
    return this.age;
  }
};

console.log(person.uname); // Bill
console.log(person.uage); // 18

2.2.2 Setter

Setter:使用 set关键词,来设置对象的属性值。

var person = {
  name: "Bill",
  age: 18,
  set uname(value) {
    this.name = value;
  },
  set uage(value) {
    this.age = value;
  }
};

person.uname = "Jackson";
person.uage = 20;
console.log(person.name, person.age); // Jackson 20

2.3 对象构造器

对象构造器:就是构造函数,通过 new 关键词调用构造函数,可以创建相同类型的对象。

构造函数的作用:使用2.1节中的创建对象的方式,只能创建单一对象,而通过构造函数,可以创建许多相同类型的对象。

1.使用构造函数,创建多个相同类型的对象

// 1.定义构造函数
function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
  this.sayHi = function () {
    console.log("hi");
  };
}

// 2.调用构造函数来创建对象
var teacher = new Person("Jackson", 40, "male");
var student = new Person("Bill", 18, "male");

// 3.获取对象的属性
console.log(teacher.name); // Jackson
console.log(student.name); // Bill

// 4.调用对象的方法
teacher.sayHi(); // hi
student.sayHi(); // hi

分析:使用构造函数创建了两个对象实例 teacherstudent,两个对象实例拥有着相同的属性和方法

2.为对象添加属性和方法

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
  this.sayHi = function () {
    console.log("hi");
  };
}

var student = new Person("Bill", 18, "male");

// 为对象添加属性
student.nationality = "English";

// 为对象添加方法
student.sayHello = function () {
  console.log("hello");
};

console.log(student);

2.4 对象原型

构造函数和原型对象:在声明了一个构造函数后,构造函数就会拥有一个prototype属性,该属性向的就是这个构造函数的原型对象。

原型对象的作用:构造函数的方法会存在浪费内存的问题,而构造函数通过原型分配的方法是所有对象所共享的,也就是可以对同一块内存进行复用,避免了浪费内存。

与原型相关的几个属性:

  • prototype
  • __proto__
  • constructor

2.4.1 prototype属性

prototype:每一个构造函数都有一个prototype属性,指向另一个对象。这个prototype就是一个对象(原型对象),这个对象的所有属性和方法,都会被构造函数所拥有。

我们可以把不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。

总结:
1.原型是一个对象
2.原型对象的作用是实现了方法的共享

1.构造函数每 new一个对象实例,都会开辟新的内存空间,当把方法定义在构造函数内部的时候,就会造成内存浪费。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHi = function () {
    console.log("hi");
  };
}

var teacher = new Person("Jackson", 40);
var student = new Person("Bill", 18);
console.log(teacher.sayHi === student.sayHi); // false
      

解析:在创建teacher 和 student 两个对象实例的时候,两个实例开辟了不同的内存空间(包含了name、age属性和sayHi 方法),因此这两个实例方法指向的地址是不同的,对比后得到的结果是false

2.将构造函数内部的方法,放到构造函数的原型对象上,只为该方法开辟一次内存空间,可以复用该方法,从而避免了内存浪费。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 将构造函数的方法定义到原型对象上
Person.prototype.sayHi = function () {
  console.log("hi");
};

var teacher = new Person("Jackson", 40);
var student = new Person("Bill", 18);
console.log(teacher.sayHi === student.sayHi); // true

解析:由于把 sayHi 方法定义到prototype对象上,那么每new一次对象实例时,该实例的方法都指向prototype对象的sayHi 方法上(同一块内存空间),因此这两个实例方法指向的地址是相同的,对比后得到的结果是true

2.4.2 __proto__ 属性

__proto__:每个对象都有一个 __proto__属性,它指向构造函数的prototype原型对象,之所以对象可以使用prototype对象的属性和方法,就是因为 __proto__的存在。

1. __proto__指向构造函数的prototype原型对象

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function () {
  console.log("hi");
};

var student = new Person("Bill", 18);
console.log(student.__proto__);

运行结果如下:可以看到__proto__拥有sayHi方法,而这个方法是定义在prototype对象上的,也就是说__proto__指向了prototype原型对象

注:此处的[[Prototype]]可以粗略的理解为就是__proto__

在这里插入图片描述

2.__proto__指向了prototype,因此这两者其实是等价的

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function () {
  console.log("hi");
};

var student = new Person("Bill", 18);
console.log(student.__proto__ === Person.prototype); // true
      

2.4.3 constructor属性

constructor__proto__prototype里面都有一个constructor属性,constructor称为构造函数,因为它指回构造函数本身。

constructor用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

1.constructor指向原型对象引用的构造函数

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function () {
  console.log("hi");
};

var student = new Person("Bill", 18);
console.log(Person.prototype.constructor);
console.log(student.__proto__.constructor);
      

运行结果如下:

在这里插入图片描述

2.可以利用 constructor属性,将原型对象指回原来的构造函数

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = {
  // 使用constructor属性,将原型对象指回Person构造函数
  constructor: Person,
  sayHi: function () {
    console.log("Hi");
  },
  sayHello: function () {
    console.log("Hello");
  }
};

var student= new Person("Bill", 18);
student.sayHi();
student.sayHello();

注:如果我们给原型对象赋值的是一个对象的情况下,原型对象就失去了constructor属性,这时候就需要手动将constructor属性添加回来,并指回原来的构造函数。

2.4.4 原型链

原型链:JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

  • 先捋清楚构造函数、实例和原型对象三者之间的关系,再理解原型链
  • 原型链是通过__proto__层层向上查找原型对象prototype,直到查找到顶层原型对象Object.prototype
  • 每一层原型链中,存在着构造函数、实例和原型对象三者之间的关系

通过如下例子,分析构造函数、实例和原型对象三者之间的关系,以及原型链:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function () {
  console.log("hi");
};

var student = new Person("Bill", 18);
console.log(student.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

分析:
1.student.__proto__ 指向 Person.prototype
2.Person.prototype.__proto__ 指向 Object.prototype
3.Object.prototype 是顶层原型对象,因此 Object.prototype.__proto__ 指向 null

1.构造函数、实例和原型对象三者之间的关系

在这里插入图片描述

2.原型链

在这里插入图片描述

2.5 Object对象

2.5.1 管理对象

管理对象的方法:

方法描述
create()以现有对象为原型创建新对象
defineProperty()添加或更改对象的单个属性
defineProperties()添加或更改对象属性的多个属性
getOwnPropertyDescriptor()获取对象单个属性的描述符
getOwnPropertyDescriptors()获取对象所有属性的描述符
getOwnPropertyNames()以数组返回所有属性
getPrototypeOf()获取对象的原型
keys()以数组返回可枚举属性
  1. create方法,以现有对象为原型创建新对象
var person = {
  name: "Bill",
  age: 18,
  sayHi: function () {
    console.log("Hi");
  }
};

// 以person对象为原型,创建一个新对象student
var student = Object.create(person);
student.age = 28;

console.log(student.name); // Bill
console.log(student.age); // 28
student.sayHi(); // Hi

分析:student.namestudent.sayHi() 可以使用,是因为person对象是student对象的原型,student对象继承了person对象的属性和方法,而student.age为28是因为student对象覆盖了person对象的 age 属性

2.defineProperty方法,用于添加或更改对象属性

Object.defineProperty(obj, prop, descriptor)

  • obj:要定义属性的对象
  • prop:要定义或修改的属性的名称
  • descriptor:要定义或修改的属性描述符

属性描述符分为数据描述符和存取描述符:

  • 数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的
  • 存取描述符是由 getter 函数和 setter 函数所描述的属性
  • 数据描述符和存取描述符不能混合使用
数据描述符描述
value属性值
writable属性值是否可更改
enumerable属性是否可枚举
configurable属性是否可以被删除
存取描述符描述
get()当访问该属性时,会调用此函数
set()当属性值被修改时,会调用此函数

数据描述符:

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

// 设置name属性为只读
Object.defineProperty(person, "name", { writable: false });
person.name = "Jackson";
console.log(person.name); // Bill

// 设置age属性为不可枚举
Object.defineProperty(person, "age", { enumerable: false });
for (var i in person) {
  console.log(i); // name addr
}

// 设置addr属性为不可删除
Object.defineProperty(person, "addr", { configurable: false });
delete person.addr;
console.log(person.addr); // Shanghai

存取描述符:

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

// 添加getter和setter
Object.defineProperty(person, "uage", {
  get() {
    return this.age;
  },
  set(value) {
    this.age = value;
  }
});

// 访问getter
console.log(person.uage); // 18

// 访问setter
person.uage = 28;
console.log(person.uage); // 28
console.log(person.age); // 28

3.defineProperties方法,用于添加或更改对象属性,可同时操作对象的多个属性
Object.defineProperties(obj, props)

  • 要定义属性的对象
  • 要定义其可枚举属性或修改的属性描述符的对象

数据描述符:

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

// 同时定义person对象的多个属性
Object.defineProperties(person, {
  name: {
    writable: false
  },
  age: {
    enumerable: false
  },
  addr: {
    configurable: false
  }
});

// name属性只读
person.name = "Jackson";
console.log(person.name); // Bill

// age属性不可枚举
for (var i in person) {
  console.log(i); // name addr
}

// addr属性不可删除
delete person.addr;
console.log(person.addr); // Shanghai

存取描述符:

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

// 同时定义person对象的多个属性
Object.defineProperties(person, {
  uname: {
    get() {
      return this.name;
    },
    set(value) {
      this.name = value;
    }
  },
  uage: {
    get() {
      return this.age;
    },
    set(value) {
      this.age = value;
    }
  }
});

// 访问uname属性
console.log(person.uname); // Bill
person.uname = "Jackson";
console.log(person.uname); // Jackson

// 访问uage属性
console.log(person.uage); // 18
person.uage = 28;
console.log(person.uage); // 28

4.getOwnPropertyDescriptorgetOwnPropertyDescriptors方法,用于获取对象属性的描述符

获取对象的单个属性的描述符:getOwnPropertyDescriptor(obj, prop)

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

var descriptor = Object.getOwnPropertyDescriptor(person, "name");
console.log(descriptor); // {value: 'Bill', writable: true, enumerable: true, configurable: true}

获取对象所有属性的描述符:getOwnPropertyDescriptors(obj)

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

var descriptor = Object.getOwnPropertyDescriptors(person);
console.log(descriptor.name); // {value: 'Bill', writable: true, enumerable: true, configurable: true}
console.log(descriptor.age); // {value: 18, writable: true, enumerable: true, configurable: true}
console.log(descriptor.addr); // {value: 'Shanghai', writable: true, enumerable: true, configurable: true}

5.getOwnPropertyNames方法,以数组返回所有属性的属性名

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

var properties = Object.getOwnPropertyNames(person);
console.log(properties); // ['name', 'age', 'addr']

6.getPrototypeOf方法,用于访问对象的原型

var person = {
  name: "Bill",
  age: 18,
  sayHi: function () {
    console.log("Hi");
  }
};

var student = Object.create(person); // 以person对象为原型,创建一个新对象student
var prototype = Object.getPrototypeOf(student); // 获取student的原型
console.log(prototype); // {name: 'Bill', age: 18, sayHi: ƒ}

// student的原型就是person对象,而person的原型是Object的原型
console.log(prototype == person); // true
console.log(Object.getPrototypeOf(person) == Object.prototype); // true

7.keys方法,以数组返回可枚举属性

var person = {
  name: "Bill",
  age: 18,
  addr: "Shanghai"
};

Object.defineProperty(person, "age", { enumerable: false }); // 设置age属性为不可枚举
console.log(Object.keys(person)); // ['name', 'addr']

2.5.2 保护对象

保护对象的方法:

方法描述
preventExtensions()防止向对象添加属性
isExtensible()如果对象可扩展,则返回 true
seal()防止添加和删除属性
isSealed()如果对象被密封,则返回 true
freeze()防止向对象进行任何更改
isFrozen()如果对象被冻结,则返回 true

总结:以上preventExtensionssealfreeze三个方法对于对象的限制层层递进。
1.preventExtensions:对象不可添加属性,但是可以修改和删除属性
2.seal:对象不可添加和删除属性,但是可以修改属性
3.freeze:对象不可添加、删除和修改属性

1.preventExtensions方法, 让一个对象变的不可扩展,防止向对象添加属性

var person = {
  name: "Bill",
  age: 18
};

Object.preventExtensions(person); // 防止对象添加属性
Object.defineProperty(person, "addr", { value: "Shanghai" }); // Uncaught TypeError

2.isExtensible方法,如果对象可扩展,则返回 true

var person = {
  name: "Bill",
  age: 18
};

Object.preventExtensions(person);
console.log(Object.isExtensible(person)); // false

3.seal方法, 封闭一个对象,阻止添加新属性,并且不允许删除现有属性

var person = {
  name: "Bill",
  age: 18
};

Object.seal(person); // 防止对象添加和删除属性
person.addr = "Shanghai"; 
delete person.age; 
console.log(person); // {name: 'Bill', age: 18}

4.isSealed方法,如果对象被密封,则返回 true

var person = {
  name: "Bill",
  age: 18
};

Object.seal(person);
console.log(Object.isSealed(person)); // true

5.freeze方法,冻结一个对象,即对象无法添加、删除和修改属性

var person = {
  name: "Bill",
  age: 18
};

Object.freeze(person); // 防止对象添加、删除和修改属性
person.addr = "Shanghai";
delete person.age;
person.name = "Jackson";
console.log(person); // {name: 'Bill', age: 18}

6.isFrozen方法,如果对象被冻结,则返回 true

var person = {
  name: "Bill",
  age: 18
};

Object.freeze(person);
console.log(Object.isFrozen(person)); // true

3. 函数进阶

3.1 函数的定义和使用

1.普通函数的声明与调用

// 函数声明
function getSum(x, y) {
  return x + y;
}

getSum(1, 2); // 函数调用

2.函数表达式

1.函数可以使用表达式来定义
2.使用表达式定义的函数是匿名函数

// 函数声明
var sum = function (x, y) {
  return x + y;
};

sum(1, 2); // 函数调用

3.函数提升

1.提升:将声明移动到当前作用域顶端的默认行为
2.使用函数表达式定义的函数不会被提升

// 函数调用
getSum(1, 2);

// 函数声明
function getSum(x, y) {
  return x + y;
}

分析:因为函数的声明提升到作用域顶端,实际上函数调用还是在函数声明之后,因此不会报错。

4.自调用函数

  • 函数表达式可以作为“自调用”
  • 表达式后面跟着(),那么函数表达式会自动执行
  • 无法对函数声明进行自调用
// 函数自调用
(function () {
  console.log("Hello World");
})();
var sum = (function (x, y) {
  return x + y;
})(1, 2);

console.log(sum); // 3

5.函数是对象

函数是function对象,因此函数拥有属性和方法

function getSum(x, y) {
  return x + y;
}

console.log(typeof getSum); // function

3.2 Function对象

Function对象:每个 js 函数都是一个 Function 对象。

Function对象的方法:

  • call():会调用函数,并改变函数内部的this指向
  • apply():会调用函数,并改变函数内部的this指向
  • bind():不会调用函数,可以改变函数内部的this指向

1.call、apply 和 bind 三个方法都可以改变函数内部的this指向
2.call 和 apply 方法会调用函数,bind 方法不会调用函数
3.call 和 apply 传递的参数不一样,call 传递参数的方式是 arg1,arg2,而 apply 必须是数组形式 [args]

3.2.1 call

语法:function.call(thisArg, arg1, arg2, ...)

  • function:函数名
  • thisArg:this指向的对象,可选
  • arg1, arg2, ...:函数的参数列表

1.使用call方法调用函数,可以改变函数内部的this指向

var person = {
  name: "Bill"
};

function fn() {
  console.log(this);
}

fn(); // Window {window: Window, ...}
fn.call(person); // {name: 'Bill'}

解析:
1.直接调用函数,this指向window对象
2.使用call方法,将this指向person对象

2.call方法可以传入参数,并获取函数的返回值

var person = {
  name: "Bill"
};

function fn(a, b) {
  console.log(this);
  return a + b;
}

var sum = fn.call(person, 1, 2); // {name: 'Bill'}
console.log(sum); // 3
      

3.call方法的主要应用是实现继承

例:子构造函数继承了父构造函数的属性

// 父构造函数
function Father(name, age) {
  // this指向父构造函数的对象实例
  this.name = name;
  this.age = age;
}

// 子构造函数
function Son(name, age) {
  // this指向子构造函数的对象实例
  Father.call(this, name, age); // 修改父构造函数的this为子构造函数的this
}

var son = new Son("Bill", 18);
console.log(son); // Son {name: 'Bill', age: 18}

3.2.2 apply

语法:function.apply(thisArg, argsArray)

  • function:函数名
  • thisArg:this指向的对象,可选
  • argsArray:数组形式的函数参数列表

1.使用apply方法调用函数,可以改变函数内部的this指向

var person = {
  name: "Bill"
};

function fn() {
  console.log(this);
}

fn.apply(person); // {name: 'Bill'}
      

2.apply方法可以传入参数(必须是数组形式),并获取函数的返回值

var person = {
  name: "Bill"
};

function fn(a, b) {
  console.log(this);
  return a + b;
}

var sum = fn.apply(person, [1, 2]); // {name: 'Bill'}
console.log(sum); // 3
      

3.apply方法的主要应用是操作数组

例:利用apply借助Math对象求最大值

var arr = [1, 3, 2];

// Math.max(1, 3, 2)
max = Math.max.apply(null, arr); // 通过apply将arr数组传递给了max方法
console.log(max); // 3

解析:
1.null表示不改变this指向
2.arr数组的内容作为了参数列表,传递给了max方法

3.2.3 bind

语法:function.bind(thisArg, arg1, arg2, ...)

  • function:函数名
  • thisArg:this指向的对象,可选
  • arg1, arg2, ...:函数的参数列表

1.使用bind方法会改变函数内部的this指向,但是不会调用函数

注:bind方法的返回值,是原函数改变this之后产生的新函数

var person = {
  name: "Bill"
};

function fn() {
  console.log(this);
}

var f = fn.bind(person);
f(); // {name: 'Bill'}

2.bind方法可以传入参数,并获取函数的返回值(新函数)

var person = {
  name: "Bill"
};

function fn(a, b) {
  console.log(this);
  return a + b;
}

var f = fn.bind(person, 1, 2); // f是返回的新函数
var sum = f(); // sum是f函数的返回值
console.log(sum); // 3

3.如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向时,使用bind方法

例:页面上有一个按钮,当点击按钮之后,就禁用这个按钮,3秒钟之后再开启这个按钮

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>点击一下</button>
    <script>
      var btn = document.querySelector("button");
      btn.onclick = function () {
        this.disabled = true; // 此处this指向的是btn
        setTimeout(
          function () {
            this.disabled = false; // 该回调函数绑定外部的this之后,此处的this也指向btn,否则指向window
          }.bind(this),
          3000
        );
      };
    </script>
  </body>
</html>

3.3 高阶函数

高阶函数:对其它函数进行操作的函数,称为高阶函数,它接收函数作为参数或将函数作为返回值。

1.函数作为参数传递

function fn(a, b, callback) {
  console.log(a + b);
  callback && callback(); // 调用回调函数
}

// 匿名函数作为参数传递
fn(1, 2, function () {
  console.log("Hello World");
});

2.函数作为返回值

function fn(a, b) {
  console.log(a + b);
  // 将匿名函数作为返回值
  return function () {
    console.log("Hello World");
  };
}

f = fn(1, 2);
f();

3.4 闭包

闭包:指有权访问另一个函数作用域中的变量的函数。

  • 一个作用域可以访问另一个函数内部的局部变量
  • 闭包是一个函数
  • 定义变量所在的函数,为闭包函数

1.闭包的产生

// fn为闭包
function fn() {
  var num = 10;
  function fun() {
    console.log(num); // 10
  }
  fun();
}

fn();

解析:
1.在fun函数作用域内,访问了另外一个函数fn的局部变量num,因此产生了闭包
2.num在fn函数内定义,因此fn为闭包

在产生闭包的地方打一个断点,可以看到右侧多了一个Closure (fn),代表fn是闭包。

在这里插入图片描述

2.闭包的主要作用:延伸了变量的作用范围

// fn为闭包
function fn() {
  var num = 10;
  return function () {
    console.log(num);
  };
}

var f = fn();
f(); // 10

解析:
1.在未没有闭包的情况下,局部变量num会随着函数fn调用结束,而随之销毁
2.有了闭包后,函数fn调用结束后,num并未销毁,而是等待函数 f 调用,使用了变量num后再销毁,从而延伸了局部变量num的作用范围

4. 异常

异常:执行代码的时候,可能会发生各种错误,发生错误的时候会抛异常。当抛异常后,程序会中断,不再执行后续代码,有时我们希望后续代码继续执行,这时候就需要使用处理异常的语句。

异常语句:

  • try 语句能够检测代码块中的错误
  • catch 语句允许你处理错误
  • throw 语句允许你创建自定义错误
  • finally 表示无论 try 和 catch 结果如何,都会执行的代码

1.不使用异常语句的情况下,当代码发生错误,程序会中断,后续代码不再执行

var x = y + 1; // ReferenceError
console.log("Hello World");

运行结果如下:可以发现,错误发生后,后续的输出语句并未执行

在这里插入图片描述

2.try...catch 语句

语法:先执行try中的代码块,如果出现错误,接着执行catch中的代码块,否则直接执行后续代码

try {
     // 用于检测错误的代码块
}
 catch(err) {
     // 错误出现后执行的代码块
} 
try {
  var x = y + 1; // ReferenceError
} catch (error) {
  console.log(error); // 打印错误信息
}

console.log("Hello World");

此处的 error 是当错误发生时提供错误信息的内置 error 对象,error对象还拥有两个属性:
1.name:设置或返回错误名
2.message:设置或返回错误消息

运行结果如下:可以发现,发生错误后,执行了catch语句,然后再执行后续代码,程序未发生中断

在这里插入图片描述

3.try...catch...finally 语句

语法:与try...catch 语句不同之处在于,无论如何都会执行finally中的代码块

try {
     // 用于检测错误的代码块
}
 catch(err) {
     // 错误出现后执行的代码块
} 
finally {
     // 无论结果如何都执行的代码块
}
try {
  var x = y + 1;
} catch (error) {
  console.log(error);
} finally {
  console.log("Hi");
}

console.log("Hello World");

4.throw 语句,用于创建自定义错误,当配合 try...catch 一起使用,就可以控制程序流并生成自定义错误消息

// 本例规定数字在5-10的范围,为有效数字
try {
  var x = prompt("请输入一个数字");
  if (x < 5) throw "太小";
  if (x > 10) throw "太大";
  alert("输入的数字有效");
} catch (error) {
  alert("输入的数字" + error);
}

5.js的六种错误类型

错误类型描述
EvalError已在 eval() 函数中发生的错误
RangeError已发生超出数字范围的错误
ReferenceError已发生非法引用
SyntaxError已发生语法错误
TypeError已发生类型错误
URIError在 encodeURI() 中已发生的错误

5. JSON

JSON:JavaScript 对象标记法(JavaScript Object Notation),是一种存储和交换数据的语法。

  • json是一种语法,是一种书写文本的格式
  • 可以将js中的json的概念一分为二的理解,一个是json对象,另一个是json字符串
  • json对象和json字符串之间可以相互转换

json对象:json格式的对象
json字符串:json格式的字符串

5.1 JSON语法

JSON 语法:

  • 数据为键值对
  • 数据由逗号分隔
  • 花括号容纳对象
  • 方括号容纳数组

注:
1.JSON 文件的后缀是.json
2.JSON 文本的 MIME 类型是application/json

1.JSON的数据是以键值对的形式存储

  • 键必须是字符串,且由双引号包围
  • 值只能是字符串、数字、json对象、数组、布尔和null,其中字符串必须由双引号包围

注:JSON不允许有注释

{ "name": "Bill", "age": 18 }

2.JSON中的对象和数组

JSON中的对象和数组,可以相互嵌套

{
  "name": "Bill",
  "age": 18,
  "cars": {
    "car1": "Porsche",
    "car2": "BMW",
    "car3": "Volvo"
  },
  "models": ["Cayenne", "X5", "XC60"]
}
{
  "name": "Bill",
  "age": 18,
  "cars": [
    { "name": "Porsche", "models": ["Cayenne", "Panamera"] },
    { "name": "BMW", "models": ["X5", "i3", "530Li"] },
    { "name": "Volvo", "models": ["XC60", "S60"] }
  ]
}

3.JS中的JSON对象

JSON对象的书写和JSON文本的书写,不同的地方在于JSON对象的键没有双引号包围,这是因为JSON对象是JS对象,而JS对象的键没有双引号包围。

// JSON对象
var json = {
  name: "Bill",
  age: 18,
  cars: [
    { name: "Porsche", models: ["Cayenne", "Panamera"] },
    { name: "BMW", models: ["X5", "i3", "530Li"] },
    { name: "Volvo", models: ["XC60", "S60"] }
  ]
};

// 使用objectName.property的语法访问对象属性
console.log(json.name); // Bill
console.log(json.age); // 18
console.log(json.cars[0]); // {name: 'Porsche', models: Array(2)}
console.log(json.cars[0].name); // Porsche
console.log(json.cars[0].models[0]); // Cayenne

// 使用objectName["property"]的语法访问对象属性
console.log(json["name"]); // Bill
console.log(json["age"]); // 18
console.log(json["cars"][1]); // {name: 'BMW', models: Array(3)}
console.log(json["cars"][1]["name"]); // BMW
console.log(json["cars"][1]["models"][0]); // X5
      

5.2 JSON解析

JSON解析:使用JSON.parse()方法,将json字符串转换为json对象

var jsonstr = '{"name":"Bill","age":18,"cars":{"car1":"Porsche","car2":"BMW","car3":"Volvo"}}';
var json = JSON.parse(jsonstr);
console.log(json); // {name: 'Bill', age: 18, cars: {…}}

还可以使用ES6中的模板字符串,保留JSON文本原有的写法:

// JSON字符串
var jsonstr = `{
  "name": "Bill",
  "age": 18,
  "cars": {
    "car1": "Porsche",
    "car2": "BMW",
    "car3": "Volvo"
  }
}`;

var json = JSON.parse(jsonstr);
console.log(json); // {name: 'Bill', age: 18, cars: {…}}
      

5.3 JSON字符串化

JSON字符串化:使用JSON.stringify()方法,将json对象转换为json字符串

// JSON对象
var json = {
  name: "Bill",
  age: 18,
  cars: {
    car1: "Porsche",
    car2: "BMW",
    car3: "Volvo"
  }
};

// 将JSON对象转换为字符串
var jsonstr1 = JSON.stringify(json);
console.log(jsonstr1);

// 将JSON对象转换为字符串,并指定缩进用于美化输出
var jsonstr2 = JSON.stringify(json, null, "\t");
console.log(jsonstr2);

运行结果如下:

在这里插入图片描述

6. 拷贝

拷贝分为浅拷贝和深拷贝:

  • 浅拷贝:只拷贝第一层的数据,当碰到更深层次的对象时,则只拷贝对象的地址
  • 深拷贝:拷贝每一层的数据

6.1 浅拷贝

1.使用Object.assign()方法,可以实现浅拷贝

var person = {
  name: "Bill",
  age: 18,
  info: {
    telephone: "13579",
    email: "13579@163.com"
  }
};

var student = {};
Object.assign(student, person); // 将person对象浅拷贝到student对象
person.age = 28;
person.info.telephone = "246810";
console.log(person);
console.log(student);

运行结果如下:发现person.age的修改没有影响到student对象,而person.info.telephone的修改却影响到了student对象。

分析:这是因为person.age的属性值是简单数据类型,因此拷贝到student对象中的age属性值是一个具体的值,而person.info的属性值是一个对象,因此拷贝到student对象中的info的属性值是一个地址,这个地址指向了person.info。也就是说student.info === person.info,因此person.age的修改不影响student,而person.info.telephone的修改会影响student

在这里插入图片描述

2.直接将对象赋值给另一个对象的话,只是相当于给对象取了个别名,既不是浅拷贝也不是深拷贝

var person = {
  name: "Bill",
  age: 18,
  info: {
    telephone: "13579",
    email: "13579@163.com"
  }
};

var student = {};
student = person; // 将person对象的地址赋值给student对象
person.age = 28;
person.info.telephone = "246810";
console.log(person);
console.log(student);

运行结果如下:person对象的任何属性发生改变,student对象的属性也跟着改变,因为student指向的就是person对象的地址,即 student === person

在这里插入图片描述

6.2 深拷贝

1.使用structuredClone()方法,可以实现深拷贝

var person = {
  name: "Bill",
  age: 18,
  info: {
    telephone: "13579",
    email: "13579@163.com"
  }
};

var student = structuredClone(person); // 将person对象深拷贝到student对象
person.age = 28;
person.info.telephone = "246810";
console.log(person);
console.log(student);

运行结果如下:发现person.ageperson.info.telephone的修改,没有影响到student对象。

分析:这是因为深拷贝会拷贝每一层的数据,当碰到更深层次的对象时,会继续遍历对象的属性和属性值并拷贝,而不是只拷贝对象的地址。

在这里插入图片描述

2.使用for...in封装一个深拷贝函数

// 深拷贝函数
function deepCopy(newObj, oldObj) {
  for (var i in oldObj) {
    var item = oldObj[i]; // 原对象的属性值
    // 判断属性值是否为数组对象
    if (item instanceof Array) {
      newObj[i] = []; // 将新对象的第i项属性设置为数组对象
      deepCopy(newObj[i], item); // 递归调用
    }
    // 判断属性值是否为普通对象
    if (item instanceof Object) {
      newObj[i] = {}; // 将新对象的第i项属性设置为普通对象
      deepCopy(newObj[i], item); // 递归调用
    }
    // 判断属性值是否为简单数据类型
    if (!(item instanceof Object)) {
      newObj[i] = item;
    }
  }
}

var person = {
  name: "Bill",
  age: 18,
  info: {
    telephone: "13579",
    email: "13579@163.com"
  },
  addr: ["Beijing", "Shanghai"]
};

var student = {};
deepCopy(student, person); // 将person对象深拷贝到student对象
person.age = 28;
person.info.telephone = "246810";
person.addr[0] = "Guangzhou";
console.log(person);
console.log(student);

运行结果如下:

在这里插入图片描述

3.使用JSON封装一个深拷贝函数

// 深拷贝函数
function deepCopy(oldObj) {
  oldObjstr = JSON.stringify(oldObj); // 将原对象转换为字符串
  newObj = JSON.parse(oldObjstr); // 再将字符串转换为对象
  return newObj;
}

var person = {
  name: "Bill",
  age: 18,
  info: {
    telephone: "13579",
    email: "13579@163.com"
  },
  addr: ["Beijing", "Shanghai"]
};

var student = deepCopy(person); // 将person对象深拷贝到student对象
person.age = 28;
person.info.telephone = "246810";
person.addr[0] = "Guangzhou";
console.log(person);
console.log(student);

运行结果:

在这里插入图片描述

7. ES6

参考后续ES6教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/275.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python】控制自己的手机拍照,并自动发送到邮箱

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 今天这个案例&#xff0c;就是控制自己的摄像头拍照&#xff0c; 并且把拍下来的照片&#xff0c;通过邮件发到自己的邮箱里。 想完成今天的这个案例&#xff0c;只要记住一个重点&#xff1a;你需要一个摄像头 思路…

8大主流编程语言的适用领域,你可能选错了语言

很多人学编程经常是脑子一热然后就去网上一搜资源就开始学习了&#xff0c;但学到了后面发现目前所学的东西并不是自己最喜欢的&#xff0c;好像自己更喜欢另一个技术&#xff0c;感觉自己学错了&#xff0c;于是乎又去学习别的东西。 结果竹篮打水一场空&#xff0c;前面所付…

蓝桥杯刷题冲刺 | 倒计时28天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.卡片2.数字三角形3.购物单4.回文日期1.卡片 题目 链接&#xff1a; 卡片 - 蓝桥云课 (lanqiao…

【计算机组成原理 - 第一章】计算机系统概论(完结)

本章参考王道考研相关课程&#xff1a; 【2021版】1.2.1_计算机硬件的基本组成_哔哩哔哩_bilibili 【2021版】1.2.2_认识各个硬件部件_哔哩哔哩_bilibili 【2021版】1.2.3_计算机系统的层次结构_哔哩哔哩_bilibili 【2021版】1.3_计算机的性能指标_哔哩哔哩_bilibili 目录 一、…

彻底搞懂nodejs事件循环

nodejs是单线程执行的&#xff0c;同时它又是基于事件驱动的非阻塞IO编程模型。这就使得我们不用等待异步操作结果返回&#xff0c;就可以继续往下执行代码。当异步事件触发之后&#xff0c;就会通知主线程&#xff0c;主线程执行相应事件的回调。 以上是众所周知的内容。今天…

14个Python处理Excel的常用操作,非常好用

自从学了Python后就逼迫用Python来处理Excel&#xff0c;所有操作用Python实现。目的是巩固Python&#xff0c;与增强数据处理能力。 这也是我写这篇文章的初衷。废话不说了&#xff0c;直接进入正题。 数据是网上找到的销售数据&#xff0c;长这样&#xff1a; 一、关联公式:…

人工智能轨道交通行业周刊-第35期(2023.2.20-2.26)

本期关键词&#xff1a;重庆智慧轨道、智能运维主机、标准轨距、地方铁路公报、景深、机器视觉应用 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通Rai…

【C/C++】必知必会知识点大总结

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;C/C知识点 &#x1f4e3;专栏定位&#xff1a;整理一下 C 相关的知识点&#xff0c;供大家学习参考~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;…

基于Reactor模式下的epoll多路复用服务器

文章目录一、认识Reactor模式1.1 Reactor 模式的概念1.2 Reactor 模式的组件1.3 Reactor 模式的流程1.4 Reactor 模式的优点二、Reactor模式下的 epoll ET服务器2.1 总体设计思路2.2 Connection 类结构2.3 封装 socket 实现 Sock 类2.4 封装 epoll 实现 Epoller 类2.4.1 Create…

2023年BeijngCrypt勒索病毒家族最新变种之.halo勒索病毒

目录 前言&#xff1a;简介 一、什么是.halo勒索病毒&#xff1f; 二、.halo勒索病毒是如何传播感染的&#xff1f; 三、感染.halo后缀勒索病毒建议立即做以下几件事情 四、中了.halo后缀的勒索病毒文件怎么恢复&#xff1f; 五、加密数据恢复情况 六、系统安全防护措施建…

宣布推出 .NET 社区工具包 8.1!

我们很高兴地宣布 .NET Community Toolkit 8.1 版正式发布&#xff01;这个新版本包括呼声很高的新功能、bug 修复和对 MVVM 工具包源代码生成器的大量性能改进&#xff0c;使开发人员在使用它们时的用户体验比以往更好&#xff01; 就像在我们之前的版本中一样&#xff0c;我…

STM32F1硬件SPI驱动nRF24L01通过按键控制数据收发带状态反馈

STM32F1硬件SPI驱动nRF24L01通过按键控制数据收发带状态反馈&#x1f4cc;相关篇《STM32F1基于STM32CubeMX配置硬件SPI驱动nRF24L01数据收发》 &#x1f3ac;功能演示 &#x1f33f;工程默认配置的是STM32F103VC单片机&#xff0c;其他型号的修改需要修改启动文件startup_st…

python+django+vue图书个性化推荐系统

整个系统是由多个功能模块组合而成的&#xff0c;要将所有的功能模块都一一列举出来&#xff0c;然后进行逐个的功能设计&#xff0c;使得每一个模块都有相对应的功能设计&#xff0c;然后进行系统整体的设计。 本图书个性化推荐系统结构图如图python manage.py runserver 开…

宇宙最强-GPT-4 横空出世:最先进、更安全、更有用

文章目录前言一、准确性提升1.创造力2.视觉输入3.更长的上下文二、相比于ChatGPT有哪些提升1.GPT-4 的高级推理能力超越了 ChatGPT2.GPT-4 在多种测试考试中均优于 ChatGPT。三、研究团队在GPT-4模型都做了哪些改善1.遵循 GPT、GPT-2 和 GPT-3 的研究路径2.我们花了 6 个月的时…

分享10个不错的C语言开源项目

今天跟大家分享10个重量级的C语言开源项目&#xff0c;C语言确实经得住考验&#xff1a; Redis&#xff1a;Redis是一个开源的高性能的键值对数据库。它以C语言编写&#xff0c;具有极高的性能和可靠性。 Nginx&#xff1a;Nginx是一个高性能的HTTP和反向代理服务器&#xff0…

刚工作3天就被裁了....

前言 还有谁&#xff1f;刚上三天班就被公司公司的工作不适合我&#xff0c;叫我先提升一下。 后面我也向公司那边讨要了一个说法&#xff0c;我只能说他们那边的说辞让我有些不服气。 现在之所以把这件事上记录一下&#xff0c;一是记录一下自己的成长轨迹&#xff0c;二是…

改进YOLO系列 | CVPR2023最新Backbone | FasterNet 远超 ShuffleNet、MobileNet、MobileViT 等模型

论文地址:https://export.arxiv.org/pdf/2303.03667v1.pdf 为了设计快速神经网络,许多工作都集中在减少浮点运算(FLOPs)的数量上。然而,作者观察到FLOPs的这种减少不一定会带来延迟的类似程度的减少。这主要源于每秒低浮点运算(FLOPS)效率低下。并且,如此低的FLOPS主要…

javaSE系列之类与对象

javaSE系列之类与方法什么是类类的定义书写事项什么是实例化this引用this的注意事项对象的初始化构造方法封装的概念访问限定符封装扩展之包static成员static的特性static的初始化代码块注意事项内部类1.实例内部类&#x1f497; &#x1f497; 博客:小怡同学&#x1f497; &am…

【LeetCode】1171. 从链表中删去总和值为零的连续节点、面试题 02.05. 链表求和

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1171. 从链表中删去总和值为零的连续节点 面试题 02.05. 链表求和 1171. 从链表中删去总和…

【面试题】面试官:如果后端给你 1w 条数据,你如何做展示?

最近一位朋友参加阿b的面试&#xff0c;然后面试官问了她这个问题&#xff0c;我问她咋写的&#xff0c;她一脸淡定的说&#xff1a;“虚拟列表。”大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面…
最新文章