深入对象
创建对象的三种方式
- 利用对象字面量创建对象
- 利用new Object 创建对象
- 利用构造函数创建对象
构造函数
- 构造函数:是一种特殊的函数,主要用来初始化对象
- 使用场景:可以通过构造函数来快速创建多个类似对象
- 说明:
- 使用new 关键字调用函数的行为被称为实例化
- 构造函数内部无需写return,返回值即为新创建的对象,return的返回值无效
- 实例化过程:
- 创建新对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新对象
实例成员&静态成员
- 实例成员:通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)
- 静态成员:构造函数的属性和方法被称为静态成员(静态属性和静态方法)
- 静态成员说明:
- 静态成员只能构造函数来访问
- 静态方法中的this指向构造函数
内置构造函数
- 字符串,数值,布尔等基本数据类型有专门的构造函数,这些我们称为包装类型,JS中几乎所有的数据都可以基于构造函数创建
Object
Object是内置的构造函数,用于创建普通对象
- Object.keys 静态方法获取对象中所有属性名(键)
- 语法:
const o = {name:'佩奇',age:6};
// 获得对象的所有键,并且返回是一个数组
const arr = Object.key(o);
console.log(arr) // ["name",'age'];
-
Object.values 静态方法获取对象中所有属性值,以数组的形式返回
-
Object.assign 静态方法常用于对象拷贝
- 语法:
const o = {name:'佩奇',age:6};
const obj = {};
Object.assign(obj,o); // 把o拷贝给obj
Object.assign(o,{gender:"女"});
- 使用场景:给对象添加属性
Array
- Array是内置的构造函数,用于创建数组
- 数组常见实例方法-核心方法
- forEach-----遍历数组,用于查找遍历数组元素,不返回数组
- filter-----过滤数组,返回新数组,返回的是筛选满足条件的数组元素
- map-----迭代数组,返回新数组,返回的是处理之后的数组元素,新数组长度和原数组一致
- reduce-----累计器,返回累计处理的结果,经常用于求和等
arr.reduce(function(){},起始值)
arr.reduce(function(上一次值,当前值){},初始值)
- reduce的执行过程:
-
如果没有起始值,则上一次值以数组的第一个数组元素的值
-
每一次循环,把返回值作为下一次循环的上一次值
-
如果有起始值,则起始值作为上一次值
-
数组常见方法-其他方法
- 实例方法join数组元素拼接为字符串,返回字符串(重点)
- 实例方法find查找元素,返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回undefined(重点)
- 实例方法every检测数组所有元素是否都符合指定条件,都符合返回true,否则false(重点)
- 实例方法some检测数组中的元素是否满足指定条件,如果数组中有元素满足条件返回true,否则返回false
- 实例方法concat合并两个数组,返回新数组
- 实例方法sort对元数组单元值排序
- 实例方法splice删除或替换原数组单元
- 实例方法 reverse反转数组
- 实例方法 findIndex 查找元素的索引值
- 静态方法Array.from()—把伪数组转为真数组
String
- 常见的实例方法
- 实例属性length用来获取字符串的长度(重点)
- 实例方法split(‘分隔符’)用来将字符串拆分成数组(重点)
- 实例方法substring(需要截取的第一个字符的索引,[,结束的索引号])用于字符串截取(重点)
- 实例方法startsWith(检测字符串[,检测位置索引号])检测是否以某字符开头(重点)
- 实例方法include(搜索的字符串[,检测位置索引号])判断一个字符是否包含在另一个字符串中,根据情况返回true或false(重点)
- 实例方法toUpperCase 用于将字母转换成大写
- 实例方法toLowerCase 用于将字母转换成小写
- 实例方法indexof 检测是否包含某字符
- 实例方法endsWith 检测是否以某字符结尾
- 实例方法replace 用于替换字符串,支持正则匹配
- 实例方法match 用于查找字符串,支持正则匹配
Number
Nuber 是内置的构造函数,用于创建数值
- 常用方法:toFixed()设置保留小数位的长度(四舍五入规则)
构造函数
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立,互不影响
- 构造函数存在浪费内存的问题,特别是在创建使用引用数据类型的时候
原型
- 可以解决构造函数中内存浪费的问题
- 构造函数通过原型分配的函数是所有对象所共享的
- JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
- 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
- 构造函数和原型对象中的this都指向实例化对象
constructor属性
- 每个原型对象里面都有个constructor属性,该属性指向该原型对象的构造函数
对象原型
-
对象都会有一个属性__proto__ 指向构造函数的prototype 原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在
-
注意:
- __proto__是JS非标准属性,[[prototype]]和__proto__意义相同(只读性)
- 用来表明当前实例对象指向哪个原型对象prototype
- proto 对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数
- 对象原型指向原型对象
原型继承
- 继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript中大多是借助原型对象实现继承的特性
原型链(高频面试题)
- 基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
原型链-查找规则
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
- 如果还没有就查找原型对象的原型(Object的原型对象)
- 依次类推一直找到Object为止(null)
- __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
- 可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
深浅拷贝
- 浅拷贝和深拷贝只针对引用数据类型
浅拷贝
- 浅拷贝:拷贝的是地址
- 语法:
const obj = {
uname:'pink',
age:18
}
// 1. 使用展开运算符
const o = {...obj};
o.age = 20;
console.log(o);
console.log(obj)
// 2. 使用Object.assign(0,obj)方法
const o = {}
Object.assign(o,obj);
o.age = 20;
console.log(o);
console.log(obj)
深拷贝
- 拷贝的是对象,不是地址
- 常见方法:
- 使用递归实现深拷贝
- js库lodash里面cloneDeep内部实现了深拷贝 lodash/cloneDeep
- 语法:
var objects = [{ 'a': 1 }, { 'b': 2 }];
// deep就是拷贝过来的对象
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
- 通过JSON.stringify()和JSON.parse()实现
异常处理
- 异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
throw抛异常
- throw 抛出异常信息,程序也会终止执行
- throw后面跟的是错误提示信息
- Error 对象配合throw使用,能够设置更详细的错误信息
try/catch 捕获异常
- try…catch用于捕获错误信息
- 将预估可能发生错误的代码写在try代码段中
- 如果try代码段中出现错误后,会执行catch代码段,并截获到错误信息
- finally 不管是否有错误,都会执行
debugger
处理this
this的指向
- 对于普通函数的this:
- 谁调用我,this指向谁(注意:在严格模式下,原来在非严格模式下指向window的this指向undefined)
- 对于箭头函数的this:
- 箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
- 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义
- 箭头函数this指向的注意情况
- 事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
- 同样由于箭头函数this的原因,基于原型的面向对象也不推荐采用箭头函数(是普通函数时,原型对象里的this指向实例化对象)
改变this
- JavaScript中还允许指定函数中this的指向,有3个方法可以动态指定普通函数中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:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此apply主要跟数组有关系,比如使用Math.max()求数组的最大值
- bind()
- bind()方法不会调用函数,但是能改变函数内部this指向
- 语法:
fun.bind(thisArg,arg1,arg2);
- thisArg:在fun函数运行时指定的this值
- 传递的其他参数
- 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
- 因此当我们只想改变this指向,并且不想调用中国函数的时候,可以使用bind,使用场景--改变定时器内部的this指向
call apply bind 总结
- 相同点:都可以改变函数内部的this指向
- 区别点:
- call 和 apply 会调用函数,并且改变函数内部this指向
- call 和 apply 传递的参数不一样,前者是指,后者是数组
- bind 不会调用函数,可以改变函数内部this指向
- 主要应用场景:
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系,比如借助数学对象实现数组最大最小值
- bind 不调用函数,如改变定时器内部的this指向
防抖
- 单位时间内,频繁触发事件,只执行最后一次
- 使用场景:搜索框输入,只需用户最后一次输入完再发送请求
- 实现方式:
- lodash 提供的防抖来处理
- 语法:
_.debounce(function,时间)
- 手写一个防抖函数来处理
节流
- 单位时间内,频繁触发事件,只执行一次
- 使用场景:鼠标移动mousemove,页面尺寸缩放resize,滚动条滚动scroll等等
- 实现方式:
- lodash节流函数
- 手写一个节流函数
案例拓展
- ontimeupdate 事件在视频/音频(audio/video)当前的播放位置发生改变时触发
- onloadeddata 事件在当前帧的数据加载完成并且还没有足够的数据播放视频/音频的下一帧触发(即页面打开时触发)