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 对象访问器
Getter
和 Setter
的作用:
- 提供了更简洁的语法
- 允许属性和方法的语法相同
- 可以确保更好的数据质量
注:
getter
和setter
的方法名不能与属性名相同
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
分析:使用构造函数创建了两个对象实例
teacher
和student
,两个对象实例拥有着相同的属性和方法
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() | 以数组返回可枚举属性 |
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.name
和student.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.getOwnPropertyDescriptor
和 getOwnPropertyDescriptors
方法,用于获取对象属性的描述符
获取对象的单个属性的描述符: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 |
总结:以上
preventExtensions
、seal
、freeze
三个方法对于对象的限制层层递进。
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指向的是btnsetTimeout(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.age
和person.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教程
相关文章:
JS高级知识总结
文章目录1. this指向问题2. 对象进阶2.1 对象的定义和使用2.2 对象访问器2.2.1 Getter2.2.2 Setter2.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 函数的…...
Jenkins+Docker+Maven+gitlab实现自动构建、远程发布
前言 一个项目完整的生命周期是从开发的coding阶段和coding阶段的质量测试,再到多次发布投入使用。目前大部分的测试阶段并不是从coding结束后开始的,而是和coding同步进行的。可能今天早上coding完成一个功能,下午就要投入测试。在这期间&a…...
centos7克隆虚拟机完成后的的一些配置介绍
系列文章目录 centos7配置静态网络常见问题归纳_张小鱼༒的博客-CSDN博客 文章目录 目录 系列文章目录 前言 一、配置Hadoop要下载的压缩包 1、下载对应版本的Hadoop压缩包 2、我们如何查看自己电脑的端口号 3、下载jdk对应的版本 二、虚拟机centos7克隆虚拟机完成后的一些基本…...
C语言/动态内存管理函数
C程序运行时,内存将被划分为三个区域,而动态开辟的内存区间位于堆区。 文章目录 前言 一、内存划分 二、malloc函数 三、calloc函数 四、realloc函数 五、free函数 总结 前言 在使用C语言编写程序时,使用动态内存是不可避免的&#x…...
华为OD机试题,用 Java 解【任务调度】问题
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不要…...
河南农业大学2023春蓝桥杯赛前训练第一场
A 滑板上楼梯 贪心 要求最少次数,尽量多跳三阶的,不能连续跳三阶,三阶后面一定要跟着一个一阶,相当于直接跳四阶 每次跳四阶都是两步(3、1),如果 % 4 之后,正好剩下 3 ,…...
docker-dockerfile
1.常用保留字指令 FROM : 基础镜像MAINTAINER: 维护者姓名和邮箱RUN : Run ["可执行文件",参数1]; Run [shell命令]EXPOSE: 暴露出的端口号WORKDIR: 登录后的位置USER: 执行用户,默认是rootENV: 构建过程的环境变量ADD: 将宿主机的文件拷贝到…...
【JavaEE】浅识进程
一、什么是进程1.1 操作系统学习进程之前首先要了解我们的操作系统(OS),我们的操作系统实际上也是一款软件,属于系统软件的范畴,操作系统早期采用命令提示框与用户交互,我们启动某个软件,打开某…...
Java_Spring:1. Spring 概述
目录 1 spring 是什么 2 Spring 的发展历程 3 spring 的优势 4 spring 的体系结构 1 spring 是什么 Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspec…...
使用Maven实现第一个Servlet程序
目录 前言: Maven 什么是Maven 创建Maven项目 Mevan目录介绍 Servlet程序 引入Servlet依赖 创建目录结构 编写代码 打包程序 部署程序 验证程序 idea集成Tomcat 下载Tomcat插件 配置Tomcat的路径 Smart Tomcat工作原理 小结: 前言&#…...
【MySQL】MySQL的优化(一)
目录 查看SQL执行频率 定位低效率执行SQL 定位低效率执行SQL-慢查询日志 定位低效率执行SQL-show processlist 查看SQL执行频率 MySQL 客户端连接成功后,通过 show [session|global] status 命令可以查看服务器状态信息。通 过查看状态信息可以查看对当…...
win kubernetes dashbord部署springboot服务
文章目录前言一、新建springboot工程二、制作镜像1.编写dockerfile2.使用阿里云镜像仓库3.使用dashbord部署服务总结前言 使用win版docker desktop安装的k8s,kubenetes dashbord。 一、新建springboot工程 就是简单一个接口。没什么说的 二、制作镜像 1.编写dock…...
Linux之进程终止
本节目录1.进程终止2.exit与_exit函数1.进程终止 进程终止时,操作系统做了什么? 释放进程中申请的相关内核数据结构和对应的数据和代码。本质就是释放系统资源。 进程终止的常见方式 a.代码跑完,结果正确 b.代码跑完,结果不正确…...
全网独家首发|极致版YOLOv7改进大提升(推荐)网络配置文件仅24层!更清晰更方便更快的改进YOLOv7网络模型
有不少小伙伴和我交流YOLO改进的时候,都说YOLOv7的网络配置文件长达104层,改起来很费力,数层数都要数很久,还很容易出错,而且基于YOLOv5代码架构,Debug起来也确实比较费时,所以博主对YOLOv7网络…...
C++入门 谁都能看懂的类和对象
类 C语言结构体中只能定义变量. 在C中,结构体内不仅可以定义变量,也可以定义函数。 //c语言 typedef struct ListNode {int val;struct ListNode* next; }LTN; //c struct ListNode {int val;//c中可以直接用这个,不用加structListNode* next…...
C++ STL:string类的模拟实现
目录 前置说明 一. 构造函数和析构函数的模拟实现 1.1 构造函数 1.2 析构函数 二. string类对象容量及成员相关的函数 2.1 获取字符串有效字符数、容量及_str成员变量获取相关函数 2.2 扩容及变长相关函数 2.3 字符串清空和判空函数 三. 运算符重载函数 3.1 赋值运算…...
并发编程---线程池(六)
阻塞队列的应⽤——线程池一 线程池基本概念二 线程池三种常⽤创建⽅式2.1.newFixedThreadPool线程池:2.2.newSingleThreadExecutor线程池:2.3.newCachedThreadPool线程池:2.4. 线程池代码演示三 线程池创建的七个参数四 线程池底层原理理解&…...
【Java实战】不会还有人用if else进行参数校验吧
当请求参数很多,几乎每一个参数都需要后端去兜底校验时,你还在写if else去判断参数是否为空吗??要校验为空的参数三四个还好,要是十几个,业务逻辑还没开始就写二三十行代码开始堆山了嘛,教给大家…...
深度学习部署(十六): CUDA RunTime API _vector-add 使用cuda核函数实现向量加法
1. 知识点 nthreads的取值,不能大于block能取值的最大值。一般可以直接给512、256,性能就是比较不错的 (input_size block_size - 1) / block_size;是向上取整 对于一维数组时,采用只定义layout的x维度,若处理的是二维ÿ…...
堆结构的两个应用
堆排序 堆结构很大的一个用处,就是用于堆排序了,堆排序的时间复杂度是O(n∗log2n)O(n*log_2n)O(n∗log2n)量级的,在众多排序算法中所处的地位也是高手级别的了。 但很多人在使用堆排序的时候,首先认为我必须得有一个堆数据结构…...
Java中的 static
1 static 静态变量 1.1 静态变量的使用 static变量也称作静态变量,也叫做类变量 静态变量被所有的对象所共享,在内存中只有一个副本 当且仅当在类初次加载时会被初始化 静态变量属于类 通过类名就可以直接调用静态变量 也可以通过对象名.静态变量…...
基于Vision Transformer的图像去雾算法研究与实现(附源码)
基于Vision Transformer的图像去雾算法研究与实现 0. 服务器性能简单监控 \LOG_USE_CPU_MEMORY\文件夹下的use_memory.py文件可以实时输出CPU使用率以及内存使用率,配合nvidia-smi监控GPU使用率 可以了解服务器性能是否足够;运行时在哪一步使用率突然…...
服务器相关常用的命令
cshell语法 https://www.doc88.com/p-4985161471426.html domainname命令 1)查看当前系统域名 domainname2)设置并查看当前系统域名 domainname example.com3)显示主机ip地址 domainname -Iwhich命令 which 系统命令在 PATH 变量指定的…...
今天是国际数学日,既是爱因斯坦的生日又是霍金的忌日
目录 一、库函数计算 π 二、近似值计算 π 三、无穷级数计算 π 四、割圆术计算 π 五、蒙特卡罗法计算 π 六、计算800位精确值 从2020年开始,每年的3月14日又被定为国际数学日,是2019年11月26日联合国教科文组织第四十届大会上正式宣布…...
Qt Quick - StackLayout 堆布局
StackLayout 堆布局一、概述二、attached 属性三、例子1. 按钮切换 StackLayout 页面一、概述 StackLayout 其实就是说,在同一个时刻里面,只有一个页面是展示出来的,类似QStackWidget 的功能,主要就是切换界面的功能。这个类型我…...
C/C++网络编程笔记Socket
https://www.bilibili.com/video/BV11Z4y157RY/?vd_sourced0030c72c95e04a14c5614c1c0e6159b上面链接是B站的博主教程,源代码来自上面视频,侵删,这里只是做笔记,以供复习和分享。上一篇博客我记录了配置环境并且跑通了࿰…...
RK3568平台开发系列讲解(网络篇)什么是Socket套接字
🚀返回专栏总目录 文章目录 一、什么是socket ?二、socket 理解为电话机三、socket 的发展历史四、套接字地址格式4.1、通用套接字地址格式4.2、IPv4 套接字格式地址4.3、IPv6 套接字地址格式4.4、几种套接字地址格式比较沉淀、分享、成长,让自己和他人都能有所收获!😄 …...
2022年全国职业院校技能大赛(中职组)网络安全竞赛试题——渗透测试解析(详细)
渗透测试 任务环境说明: 服务器场景:Server9服务器场景操作系统:未知(关闭连接)系统用户名:administrator密码:123456通过本地PC中渗透测试平台Kali对靶机场景进行系统服务及版本扫描渗透测试,以xml格式向指定文件输出信息(使用工具Nmap),将以xml格式向指定文件输出…...
尚融宝03-mybatis-plus基本CRUD和常用注解
目录 一、通用Mapper 1、Create 2、Retrieve 3、Update 4、Delete 二、通用Service 1、创建Service接口 2、创建Service实现类 3、创建测试类 4、测试记录数 5、测试批量插入 三、自定义Mapper 1、接口方法定义 2、创建xml文件 3、测试条件查询 四、自定义Serv…...
vue多行显示文字展开
这几天项目里面有一个需求,多行需要进行展开文字,类似实现这种效果 难点就在于页面布局 一开始就跟无头苍蝇似的,到处百度 ,后面发现网上的都不适合自己,最终想到了解决方案 下面是思路: 需求是超过3行&a…...
优秀手机网站模板图片/软件外包
《刑法专论》作业 一、单项选择题 1、下列哪种刑法的解释属于有权解释( )。 A、司法解释 B、学理解释 C、论理解释 D、文理解释 2、罪刑法定原则最早见于( )。 A、1215年英王签署的《大宪章》 B、1789年法国《人权宣言》 C、…...
怎么卖wordpress主题/网站优化是做什么的
计算机技术与高中英语课程整合的探索…………………………………孙晓瑜灵活运用多媒体技术、增强英语课堂学习效果…………………………段秀珍充分利用多媒体优势,优化英语阅读课教学……………………………孙晓瑜多媒体在英语教学中的运用…………………………………...
php做网站优势/收录优美图片手机版
什么是集群 在了解什么是集群之前,我们先来聊聊与集群相关的术语 服务硬件: 承载服务的硬件系统,比如PC机,Linux服务器 服务实体: 服务软件本身和服务硬件本身 节点: 与构成集群相关联的连接点ÿ…...
上海网站建设 公司案例/企业网站优化推广
Background 2021 关键词 : 认知 + 积累 2021 年已经过去了, 回顾过去的一年的主线任务, 上半年一直在找工作, 中期都在公司实习, 下半年也在找其他公司和准备paper。 整体来看我的2021年主线好像并没有做什么, 而且research 也停止了push。 然而事实不是这样的。 这一年…...
网站开发图书管理系统/免费建站模板
公司有SQL语句规范的参考文档,这里特别做个笔记。书写风格1. 语句关键字应全部使用小写。2. 引用字符时应使用单引号。如:update testable set idcolabcd。3. 连接符或运算符or、in、and、=、<、>, ,- 等前后宜加…...
电子商城网站开发价格/百度seo排名主要看啥
伪类(pseudo-class)& 伪元素(pseudo-element) 伪类和伪元素在web开发中用的好的话,可以说犹如神助。 但一定要分清楚,什么是伪类,什么是伪元素。 如何区分伪元素与伪类? 答&…...