高阶面向对象
值类型和引用类型
判断方式
typeof 判断值类型
instanceof 判断引用类型
存储机制
基本数据类型存放于栈内存。在 JS 中用于在编译器和内存中保存变量、方法调用。
引用类型存放于堆内存。它的赋值是堆内存地址的引用(指针), 所以两个变量指向的还是同一个对象,对任何一个的操作都会相互的影响。(浅拷贝概念)。
实例对象创建方法
1.new 操作符 + 2.对象字面量表示
代码块解析
对于采用大括号表示的语句,JS 一律解释为代码块。
在大括号外加上圆括号,解释为对象。
静态方法
点表示法和方括号表示法
方括号表示法总是能代替点表示法,但点表示法却不一定能全部代替方括号表示法。
方括号表示法可以用变量名作为属性名,点表示法不能。
方括号表示法可以用纯数字为属性名,点表示法不能。
方括号表示法可以用打空格的字符串为属性名,点表示法不能。
in 运算符
in 运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回 true, 否则返回 false。它的左边是一个字符串,表示属性名,右边是一个对象。
hasOwnProperty
hasOwnProperty(propertyName)方法 是用来检测属性是否为对象的自有属性,如果是,返回 true,否者 false; 参数 propertyName 指要检测的属性名; 用法:object.hasOwnProperty(propertyName) // true/false
hasOwnProperty() 方法是 Object 的原型方法(也称实例方法),它定义在 Object.prototype 对象之上,所有 Object 的实例对象都会继承 hasOwnProperty() 方法。
hasOwnProperty() 只会检查对象的自有属性,对象原形上的属性其不会检测;但是对于原型对象本身来说,这些原型上的属性又是原型对象的自有属性,所以原形对象也可以使用 hasOwnProperty()检测自己的自有属性;
属性遍历
它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
它不仅遍历对象自身的属性,还遍历继承的属性。
如果继承的属性是可遍历的,那么就会被 for...in 循环遍历到。但是,一般情况下,都是只想遍 历对象自身的属性,所以使用 for...in 的时候,应该结合使用 hasOwnProperty 方法,在循环内部判 断一下,某个属性是否为对象自身的属性。
属性查看
查看一个对象本身的所有属性,可以使用 Object.keys 方法。该方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致。
属性名获取
返回一个数组,该数组对元素是 obj 自身拥有的枚举或不可枚举属性名称字符串。
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c" };
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
// 使用 Array.forEach 输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function (val, idx, array) {
console.log(val + " -> " + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c
//不可枚举属性
var my_obj = Object.create(
{},
{
getFoo: {
value: function () {
return this.foo;
},
enumerable: false,
},
}
);
my_obj.foo = 1;
console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]
属性删除
delete 命令删除对象 obj 的 p 属性。删除后,再读取 p 属性就会返回 undefined,而且 Object.keys 方法的返回值也不再包括该属性。
注意:
删除一个不存在的属性,delete 不报错,而且返回 true。
delete 命令只能删除对象本身的属性,无法删除继承的属性。
get&set
对象中有 get 和 set 方法,在读取和设定值的时候触发。vue 中的数据绑定就是通过这个来实现的。
- 直接在对象内使用
get 用法:
var user = {
info: {
name: "张三",
},
get name() {
return this.info.name;
},
};
console.log(user.info.name); // '张三'
console.log(user.name); // '张三'
作用:
在对象内属性嵌套层级过多时,可以直接在对象下读取到对应属性,简化调用;
在 get 时可以任意设置属性名,可以不暴露组件内部属性名。
set 用法:
var user = {
info: {
name: "张三",
},
set name(val) {
console.log("我改名了");
this.info.name = val;
},
};
console.log(user.name); // '张三'
user.name = "李四"; // '我改名了'
console.log(user.name); // '李四'
作用:
- 在对象内属性嵌套层级过多时,可以直接在对象下设置到对应属性,简化层级;
- set 方法内的逻辑在赋值时会自动执行,可以监听属性值的改变
- 使用 Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineproperty( object,‘ propName ’ ,descriptor);
参数:
object :要定义或修改属性的对象;
propName :要定义或修改的属性的名称;
descriptor:要定义或修改的属性描述符。
descriptor四个属性:
value:设置属性的值,默认undefined
writable:值是否可重写。默认false
enumerable:目标属性是否可以被枚举。默认false
configurable:目标属性是否可以被删除或是否可以再次修改特性。默认false
var user = {
user_name: "张三",
};
Object.defineProperty(user, "name", {
get() {
return user.user_name;
},
set(val) {
console.log("我改名了");
user.user_name = val;
},
});
console.log(user.name); // '张三'
user.name = "王二"; // '我改名了'
console.log(user.name); // '王二'
console.log(user.user_name); // '王二'
作用: set 方法可以监听对应属性值的改变,vue 的数据动态绑定就是通过这个方法实现的,监听到 vue 实例中的 data 属性发生改变时,在 set 方法中触发模版重新渲染逻辑。
- 使用 Object.defineProperties()
var user = {
name: "张三",
};
Object.defineProperties(user, {
nameGet: {
value: function () {
console.log("读取");
return this.name;
},
},
nameSet: {
value: function (name) {
console.log("设置");
this.name = name;
},
},
});
console.log(user.nameGet); // '读取' '张三'
user.nameSet = "王二"; // '设置'
console.log(user.nameSet); // '王二'
作用: 和方法 1 直接在对象中设置效果和原理相似
其他静态方法
(1)对象属性模型的相关方法
Object.getOwnPropertyDescriptor():获取某个属性的描述对象。
Object.defineProperty():通过描述对象,定义某个属性。
Object.defineProperties():通过描述对象,定义多个属性。
(2)控制对象状态的方法
Object.preventExtensions():防止对象扩展。
Object.isExtensible():判断对象是否可扩展。
Object.seal():禁止对象配置。
Object.isSealed():判断一个对象是否可配置。
Object.freeze():冻结一个对象。
Object.isFrozen():判断一个对象是否被冻结。
(3)原型链相关方法
Object.create():该方法可以指定原型对象和属性,返回一个新的对象。
Object.getPrototypeOf():获取对象的 Prototype 对象。
实例方法
toString & valueOf
js 中一切皆对象,Number、string、boolean 都有 tostring 和 valueof,但是 null 和 undefined 没有。
- toString()
返回一个表示该对象的字符串。
每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用 对象时,该方法被自动调用。
- valueOf()
返回指定对象的原始值
调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值) 默认情况下, valueOf() 会被每个实例对象(Object)继承。
- toString()和 valueOf()同时存在
- 函数
当函数 fn 用+连接一个字符串或者是数字的时候,如果我们没有重新定义 valueOf 和 toString, 其隐式转换会调用默认的 toString()方法,将函数本身内容作为字符串返回; 如果我们自己重新定义 toString/valueOf 方法,那么其转换会按照我们的定义来,其中 valueOf 比 toString 优先级更高。
- 对象/数组
和函数结果一样。
- Date
默认调用 toString()。字符串拼接调用 toString
涉及数值类型类型转换调用 valueOf()。
总结
toString:所有方法均可调用,自动调用。可覆盖。
valueOf:如果对象没有原始值,返回对象本身。
同时存在:默认 toString,覆盖时 valueOf 优先级更高。
特殊情况:alert() []调用 toString date()
数据类型转换
强制转换
- Number
Number:原值
String:
1.可以被解析为数值,则转为数值;否则 NaN
2.空字符串转为 0
Boolean:true 为 1,false 为 0
undefined:NaN
null:0
Object:Number 方法的参数是对象时,返回 NaN,除非对象为单元素数值数组 [10]。
转换规则:
1)调用对象自身的 valueOf 方法。如果返回原始类型的值,则直接对该值使用 Number 函数, 不再进行后续步骤。
2)如果 valueOf 方法返回的还是对象,则改为调用对象自身的 toString 方法。如果 toString 方法返回原始类型的值,则对该值使用 Number 函数,不再进行后续步骤。
3)如果 toString 方法返回的是对象,就报错。
- 特有 toString 方法
1)toString 方法可以接受一个参数,表示输出的进制。
2)数字调用 tostring 要加括号(这样表明后面的点表示调用对象属性), 不加括号,这个点 会被 JavaScript 引擎解释成小数点,从而报错。
3)只要能让 JavaScript 引擎 不混淆小数点和对象的点运算符,各种写法都能用。
- String
1.原始类型
不解释
2.对象
对象,返回一个类型字符串;数组,返回该数组的字符串形式。
转换规则:
1)先调用对象自身的 toString 方法。如果返回原始类型的值,则对该值使用 String 函数,不 再进行以下步骤。
2)如果 toString 方法返回的是对象,再调用原对象的 valueOf 方法。如果 valueOf 方法返回原 始类型的值,则对该值使用 String 函数,不再进行以下步骤。
3)如果 valueOf 方法返回的是对象,就报错。
- Boolean
undefined null -0 +0 NaN ""(空串) 全为 false
其他全为 true
所有对象(包括空对象)转换结果都是 true
自动转换
不同类型数据互相运算。
非布尔值类型数据求布尔值。
非数值类型的值使用一元运算符(+ - )。
自动转换规则:
预期什么类型的值,就调用该类型的转换函数。
- 自动转换为布尔值
JavaScript 遇到预期为布尔值的地方(比如 if 语句的条件部分),就会将非布尔值的参数自动 转换为布尔值。系统内部会自动调用 Boolean 函数。
- 自动转换为字符串
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。
先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串。
- 自动转换为数值
遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用 Number 函数。 除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。
null 转数值为 0,undefined 转数值为 0
基本包装类型
对象是 JavaScript 语言最主要的数据类型,在 js 中三种原始类型的值——数值、字符串、布 尔值——在一定条件下,也会转为对象,也就是原始类型的“包装对象”(wrapper)。
所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的 Number、String、Boolean 三 个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。
在读取字符串、数字和布尔值的属性值或方法(实际上是 它们对应包装对象的属性值或方法)表现的像对象一样。但是如果你试图给属性赋值,则会忽略这 个操作:修改只是发生在临时对象身上,而这个临时对象并不会继续保留下来,意思是不允许对包 装对象 设置属性,他的属性是只读的比如 length。
设计目的
首先是使得“对象”这种类型可以覆盖 JavaScript 所有的值,整门语言有一个通用的数据模型, 其次是使得原始类型的值也有办法调用自己的方法。
实例方法
数值的自定义方法,只能定义在它的原型对象 Number.prototype 上面,数值本身是无法自定义属性的。
valueOf() toString()
原始类型与实例对象自动转换
某些场合,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。这时, JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例。
执行机制分析
JS 引擎的执行过程,分为三个阶段:
语法分析 → 预编译阶段 → 执行阶段
说明:浏览器先按照 js 的顺序加载< script >标签分隔的代码块,js 代码块加载完毕之后,立刻进入 到上面的三个阶段,然后再按照顺序找下一个代码块,再继续执行三个阶段,无论是外部脚本文件 (不异步加载)还是内部脚本代码块,都是一样的,并且都在同一个全局作用域中。
语法分析
js 的代码块加载完毕之后,会首先进入到语法分析阶段,该阶段的主要作用: 分析该 js 脚本代码块的语法是否正确,如果出现不正确会向外抛出一个语法错误 (syntaxError),停止该 js 代码的执行,然后继续查找并加载下一个代码块;如果语法正确,则 进入到预编译阶段。
预编译阶段
js 代码块通过语法分析阶段之后,语法都正确的下回将进入预编译阶段。
1.运行环境
运行环境分类:1.全局环境;2、函数环境;3、eval 环境(不建议使用)。
每进入到一个不同的运行环境都会创建 一个相应的执行上下文(execution context),那么 在一段 js 程序中一般都会创建多个执行上下文,js 引擎会以栈的数据结构对这些执行进行处理,形 成函数调用栈(call stack),栈底永远是全局执行上下文(global execution context),栈顶则 永远是当前的执行上下文。
2.执行环境
- 栈数据结构
先进后出、后进先出。
- EC 执行环境(执行上下文)Execution Context
创建变量对象 → 创建作用域链 → 确定 this 指向。
- 变量对象 VO(Variable Object)
过程:
创建 arguments 对象 → 检查 function 函数声明创建属性 → 检查 var 变量声明创建属性
1、 创建 arguments 对象,检查当前上下文的参数,建立该对象的属性与属性值,仅在函数环境 (非箭头函数)中进行的,全局环境没有此过程。
2、 检查当前上下文的函数声明,按照代码顺序查找,将找到的函数提前声明,如果当前上下文的 变量对象没有该函数名属性,则在该变量对象以函数名建立一个属性,属性值则指向该函数所在堆 内存地址引用,如果存在,则会被新的引用覆盖掉。
3、 检查当前上下文的变量声明,按照代码顺序查找,将找到的变量提前声明,如果当前上下文的 变量对象没有变量名属性,则在该变量对象以变量名建立一个属性,属性值为 undefined;如果存 在,则忽略该变量声明。
说明:在全局环境中,window 对象就是全局执行上下文的变量对象,所有的变量和函数都是 window 对象的属性方法。
函数声明提前和变量声明提升是在创建变量对象中进行的,且函数声明优先级高于变量声明
- scope 属性 指向作用域
作用域链由当前执行环境的变量对象(未进入到执行阶段前)与上层环境的一系列活动对象组成, 保证了当前执行环境对符合访问权限的变量和函数有序访问。
1、 作用域链的第一项永远是当前作用域(当前上下文的变量对象或者活动对象);
2、 最后一项永远是全局作用域(全局上下文的活动对象);
3、 作用域链保证了变量和函数的有序访问,查找方式是沿着作用域链从左至右查找变量或者函 数,找到则会停止找,找不到则一直查找全局作用域,再找不到就会排除错误。
- this 指针
1、 在全局环境下,全局执行的上下文中变量对象的 this 属性指向为 window;
2、 在函数环境下的 this 指向比较灵活,需要根据执行环境和执行方法确定,列举典型例子来分析
- 执行环境栈(Execution Context Stack)
当一个脚本第一次执行的时候,js 引擎会解析这段代码,并将其中的同步代码按照执行顺序加 入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么 js 会向执行栈中添加这个方法 的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返 回结果后,js 会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程 反复进行,直到执行栈中的代码全部执行完毕。
- AO 激活对象(Active Object)
有了变量对象存每个上下文中的东西,但是它什么时候能被访问到呢?就是每进入一个执行上 下文时,这个执行上下文儿中的变量对象就被激活,也就是该上下文中的 函数标示符、形参 arguments、变量声明等就可以被访问到了。
其实变量对象和激活对象都是一个东西,只是变量对象是全局,激活对象是函数内部。
执行环境的作用
除此之外,在 js 代码的任何位置打印 this,它总是有值的,尽管大多数情况下 this 都指向 window 对象(this 的具体指向以后会提到),这说明 this 也是在执行环境中就准备好的。
现在能清楚执行环境中准备了哪些东西了。
变量声明、函数声明、arguments(函数体)、this,以上内容都会被保存在变量对象中。由此可见,执行环境的作用就是生成一个变量对象,将代码执行过程中可能用到的所有变量都提前准备好。
执行流和执行环境
JS 执行流其实是一个压栈出栈的过程——执行上下文栈
1.JavaScript 执行在单线程上,所有的代码都是排队执行。
2.一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
3.每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
4.浏览器的 JS 执行引擎总是访问栈顶的执行上下文。
5.全局上下文只有唯一的一个,它在浏览器关闭时出栈。
执行环境的生命周期
总生命周期:创建 → 执行 → 出栈等待销毁
创建阶段:
创建作用域链
创建变量对象 AO:初始化函数参数 arguments→ 初始化函数声明 → 初始化变量。
执行阶段:执行变量赋值、代码执行。
回收阶段:执行上下文出栈等待,回收执行。
执行阶段
解释阶段
词法分析:JavaScript 中在调用函数的那一瞬间之前,会先进行词法分析(当函数调用的前一瞬间, 会先形成一个激活对象:Avtive Object(AO),并进行分析)。
JS 词法分析分为 3 个步骤:
1.分析形参 →2.分析变量声明 →3.分析函数声明
作用域规则确定:判断一个变量是什么时,根据它定义时的作用域,代码在解释阶段就要确定好。
执行阶段
创建执行上下文 → 执行函数代码 → 垃圾回收
JS 代码整体运行分为两个阶段:词法分析期和运行期。在 JS 代码自上而下执行前,会有一个“词法分析过程”。
词法分析主要有 3 个步骤:分析函数参数、分析变量声明、分析函数声明。
具体步骤:
1.分析函数参数
将函数的参数添加为 AO 属性,属性默认值为 undefined。
接收函数的实参,覆盖原属性值。
2.分析变量声明/分析局部变量
若 AO 中不存在与声明的变量所对应的属性,则添加 AO 属性为 undefined。
若 AO 中已存在与声明的变量所对应的属性,则不做任何修改。
3.若 AO 中存在与函数名所对应的属性,则覆盖原属性为一个函数表达式。
Class 类
继承
(1)class 关键字创建,首字母大写
(2)构造函数(有参):constructor()
(3)constructor 不写也会自动生成。new 的时候自动调用
(4)生成实例 new 不能省略。
(5)注意语法规范,创建类,类名后面不要加小括号。实例后面加小括号,构造函数不需要 function。
类的继承
子类可继承父类构造方法之外的方法
super():访问和调用对象父类的函数,可以调用父类的构造函数也可以调用普通函数。
方法重写
指继承父类的子类将父类的同名方法重写为子类独有方法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
return "我的名字是" + this.name;
}
set birth(value) {
this._birth = `${value}-`;
}
get birth() {
return this._birth;
}
}
class Animal extends Person {
// 继承拿到所有Person的属性
constructor(name, age) {
super(name, age); // 继承name
this.age1 = age; // 通过继承的属性新建一个属性
}
say() {
// 重写say这个函数
return "My name is" + this.name;
}
}
const animal = new Animal("嘻嘻", 5); // 实例化对象并赋值
const person = new Person("哈哈", 11);
console.log("animal", animal);
console.log("person", person);
person.birth = 123; // 相当于用set赋值
console.log("birth", person.birth); // get拿到值
console.log("birth", person.say);
console.log("birth", animal.say);
super
super 作为函数使用,代表父类的构造函数,只能用在子类的构造函数中;
super 作为对象使用,在普通方法之中指向父类的原型对象,在静态方法之中指向父类;在普通方法中调用父类方法,super 内部的 this 指向子类的实例,在静态方法中指向子类。
本质
类的本质还是 function,也可以简单认为类就是构造函数另一种写法。
1.类有原型对象 prototype
2.类原型对象 prototype 里面有 constructor 指向类本身
3.类可以通过原型对象添加方法
4.类创建的实例对象有__proto__
原型指向类的原型对象
ES6 类其实就是语法糖(一种便捷写法,简单理解,有两种方法可以实现同样的功能,更加清晰方便的方法就是语法糖)
构造函数
构造函数习惯首字母大写
构造函数和普通函数区别就是调用方式不同,普通函数是直接调用,而构造函数需要 new 来调用。
执行流程:
立刻创建一个新对象 → 新建对象设置为 this→ 逐行执行函数中的代码 → 将新建对象返回
instance of 与 java 一致,所有对象都是 Object 的后代。
方法理解:
在 Person 构造函数中,为每一个对象都添加了一个方法,构造函数每执行一次就会创建一个新的方法,所有实例的方法都是唯一的。
重点:函数体写在对象内部 or 全局作用域
1.首字母大写
2.和 new 一起使用
function Man(name) {
this.name = name;
this.age = 18;
}
var man = new Man("张三"); // 创建一个name名为张三的构造函数Man
实例成员和静态成员
实例成员就是构造函数内部通过 this 添加的成员。只能通过实例化的对象访问。
静态成员是指在构造函数本身上添加的成员,只能通过构造函数访问。不能通过对象访问。
构造函数原型 prototype
我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype。这个属性对应着一个对象,这个对象就是我们所谓的原型对象。
1.作为普通函数调用 prototype 没有任何作用。
2.以构造函数调用,创建对象中隐含属性指向该构造函数的原型对象,通过 __proto__
来访问该属性。
3.当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果没有则在原型对象中寻找,如果找到则直接使用。如果原型中没有则去原型的原型中寻找,直到找到 Object 的原型,Object 的原型没有原型,如果在 Object 中仍然没有找到,则返回 undefined。
4.创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有属性和方法。
__proto__
对象原型 对象有一个属性__proto__
,指向 prototype 原型对象。
__proto__
对象原型和原型对象 prototype 等价
__proto__
对象原型的意义在于为对象的查找机制提供一个方向,但属于非标准属性,实际开发中不可以使用这个属性,它只是指向原型对象 prototype。
constructor 构造函数
在 JavaScript 中, constructor 属性返回对象的构造函数。
返回值是函数的引用,不是函数名:
JavaScript 数组 constructor 属性返回 function Array() { [native code] }
JavaScript 数字 constructor 属性返回 function Number() { [native code] }
JavaScript 字符串 constructor 属性返回 function String() { [native code] }
如果一个变量是数组你可以使用 constructor 属性来定义。
原型链(重要)
成员查找规则
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是
__ proto __
指向的 prototype 原型对象)。如果还没有就查找原型对象的原型(Object 的原型对象)。
以此类推一直找到 Object 为止(null)。
__ proto __
对象原型的意义在于为对象成员查找机制提供方向。
扩展内置对象须知
数组和字符串内置对象不能给原型对象覆盖操作Array.prototype = {}
,只能是Array.prototype.xxx = function() {}
的方式。
函数进阶
调用方式
普通函数、对象方法、构造函数、绑定时间函数、定时器函数、立即执行函数。
this 指向(重要)
调用方式 | this 指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象,原型对象里面的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含参数就是 this,this 指向的是一个对象,这个对象称为函数执行的上下文对象。根据函数的调用方式不同,this 会指向不同对象。
1.以函数的形式调用时,this 永远都是 window;2.以方法的形式调用时,this 就是调用方法的那个对象
2.this 适用于函数作为某个对象的方法存在时使用,用于表示该对象。
3.使用工厂方法批量创建对象,使用的构造函数都是 Object,导致我们无法区分多种不同类型对象。
this 总结
1.当以函数形式调用时,this 就是 window。
2.当以方法的形式调用时,谁调用方法 this 就是谁
3.当以构造函数形式调用时,this 就是新创建的那个对象。
改变 this 指向
call
call()方法调用一个函数,简单理解为一次函数的立即调用,但它可以改变函数的 this 指向。
fun.call(thisArg, arg1, arg2, ...) // 第二个参数开始表示执行该函数传递的参数
thisArg:在 fun 函数运行时执行的 this 值
arg1, arg2:传递的其他参数
返回值就是函数的返回值,因为它就是一次函数调用
apply
apply()方法调用一个函数,简单理解为一次函数的立即调用,但它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray]);
thisArg:在 fun 函数运行时指定的 this 对象
argsArray:传递的参数,必须以数组形式呈现
返回值就是函数的返回值,因为它就是一次函数调用
bind
bind()方法不会调用函数,但是能改变函数内部 this 指向
fun.bind(thisArg, arg1, arg2. ...)
thisArg:在 fun 函数运行时执行的 this 值
arg1, arg2:传递的其他参数
返回由指定的 this 值和初始化参数改造的原函数拷贝
小结
相同点:都可以改变函数内部的 this 指向。
区别:
1.call 和 apply 会调用函数,并且改变函数内部 this 指向。
2.call 和 apply 传递参数不一样,call 传递参数,apply 必须数组形式传参。
3.bind 不会调用函数,可以改变函数内部 this 指向。
主要应用场景:
1.call 经常做继承。
2.apply 经常跟数组有关系,比如借助于 Math 求值。
3.bind 不调用函数,能改变 this 指向,比如改定时器内部的 this 指向。
严格模式
严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上的版本浏览器中才会被支持,旧版本浏览器会被忽略。
严格模式的更改:
1.消除了 JavaScript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了 EMCAScript 的未来版本中可能定义的一些语法,为未来新版本 JS 做铺垫。一些保留字:class、enum、export 等不能做变量名。
开启严格模式
分为 为脚本开启严格模式、为函数开启严格模式。
<script>
"use strict";
console.log("这是严格模式");
</script>
将整个脚本文件放在一个立即执行函数中开启严格模式,独立创建作用域不影响其他脚本文件。
(function () {
"use strict";
var num = 10;
function fn() {}
})();
把"use strict"声明放在函数体所有语句之前,仅该函数体内执行严格模式。
严格模式简要规则
不能使用未定义的变量
不允许删除变量或者对象或者函数
不允许参数重名
禁止 this 关键字指向全局对象
闭包(重要)
定义
闭包指有权访问另一个函数作用域中变量的函数。
简单理解:一个作用域可以访问另一个函数内部的局部变量。
概念:一个函数 f 内创建另一个函数 f1,f1 可以访问到 f 的局部变量,返回 f1。
案例:利用闭包获得 ul>li 的索引号
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i);
};
})(i);
}
立即执行函数也称为小闭包,因为立即执行函数里任何一个函数都可以使用它的 i 变量。
闭包不一定是最优选项,容易造成变量短期内无法销毁,易造成内存泄漏。
作用
延伸变量的作用范围。
递归
一个函数在内部调用其本身,函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。
必须加退出条件 return,否则容易堆栈溢出。
步骤
假设递归函数已经写好
寻找递推关系
将递推关系的结构转换为递归体
将临界条件加入到递归体中
案例 1:求和
function sum(n) {
if (n == 1) return 1;
return sum(n - 1) + n;
}
案例 2:斐波拉契数列
// 递归方法
function fib(n) {
if (n === 1 || n === 2) return n - 1;
return fib(n - 1) + fib(n - 2);
}
console.log(fib(10)); // 34
//非递归方法 //
function fib(n) {
let a = 0;
let b = 1;
let c = a + b;
for (let i = 3; i < n; i++) {
a = b;
b = c;
c = a + b;
}
return c;
}
console.log(fib(10)); // 34
案例 3:爬楼梯(动态规划)
假如楼梯有 n 个台阶,每次可以走 1 个或 2 个台阶,请问走完这 n 个台阶有几种走法
function climbStairs(n) {
if (n == 1) return 1;
if (n == 2) return 2;
return climbStairs(n - 1) + climbStairs(n - 2);
}
对象拷贝
浅拷贝(单层)
仅第一层对象脱离原址,第二层对象仍为引用。
let obj = {
id: 1,
name: "ZhangSan",
msg: {
age: 18,
address: "China",
},
};
let newObj = {};
for (const key in obj) {
newObj[key] = obj[key];
}
obj.msg.age = 20;
console.log(newObj);
深拷贝
通过递归调用,实现全部对象内容拷贝,全部脱离原引用地址。
function deepCopy(newObj, oldObj) {
for (const key in oldObj) {
if (oldObj[key] instanceof Array) {
let tempObj = [];
newObj[key] = deepCopy(tempObj, oldObj[key]);
} else if (oldObj[key] instanceof Object) {
let tempObj = {};
newObj[key] = deepCopy(tempObj, oldObj[key]);
} else newObj[key] = oldObj[key];
}
return newObj;
}
let obj = {
id: 1,
name: "ZhangSan",
msg: {
age: 18,
address: "China",
},
arr: [
{
student: "LiHua",
code: 0,
},
{
student: "LiLei",
code: 1,
},
],
};
let newObj = {};
let deepObj = deepCopy(newObj, obj);
obj.msg.age = 20;
console.log(newObj);