1.原型对象/ 构造函数/实例
- 原型对象:构造函数prototype属性指向的对象
- 构造函数:创建对象的一种方式
- 实例:构造函数创建的一个对象(new一下)
- constructor: 一个属性,对象独有,指向构造函数
- proto: 一个隐式属性,函数和对象都有这个属性
- prototype:一个属性仅函数独有,指向该函数的原型对象、
prototype属性可以给函数共享(继承)方法、属性,而_proto_是查找某函数或对象的原型链方式
2. 原型链
属性查找: 从实例本身一层层向上查找,直到访问到对象的原型。
属性修改:只会修改实例本身的属性,如果不存在,则添加该属性。如果需要修改原型的属性时,可以使用例如Vue.prototype.xxx = xxx;但是这样会造成所有Vue的实例属性会发生改变。
3. 闭包
函数外部访问函数内部的方法;闭包其实就是一个函数,这个函数能够访问其他函数的作用域中的变量。
在js中,如果一个对象不再被引用,这个对象会被GC回收,否则会被一直保存在内存中。
《JavaScript高级编程》书中建议:由于闭包会携带包含它的函数的作用域,因为会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多。
4. 对象的拷贝
-
浅拷贝
:以赋值的形式拷贝引用对象,指向同一个地址,修改时原对象也会受到影响(寻址操作,例如访问数据类型是数组),常用Object.assign, 展开运算符(...)
. -
深拷贝
: 完全拷贝一个新对象,修改时原对象不受任何影响(寻值操作:例如访问数据类型为字符串),常用JSON.parse(JSON.stringify(obj)), 递归进行逐一赋值
5. new运算符的执行过程
- 新生成一个对象
- 链接到原型; obj.proto = constructor.prototype
- 绑定this: apply
- 返回新对象(如果构造器有自己的return时,则返回该值)
6. instanceof 原理
objA instanceof objB: objA的原型链上能否找到objB的原型
instanceof 跟 typeof区别:typeof 返回该参数数据类型,返回的是string;instanceof 判断参数是否存在继承关系,返回时Boolean
7. 模块化
通用的js模块规范主要有:CommonJS 、AMD 、ES6
- CommonJS :
require/module.exports/exports
- AMD:
require/defined
- ES6:
import / export
require 跟import的区别
- require 是AMD引入的规范,import是ES6的规范
- import是编译时调用只能放在文件开头,require 是运行时调用,理论上是可以运行在代码任何地方
- require 是值拷贝过程,导出值改变不会影响导入值,require的结果就是obj、number、string、fun,再把结果赋值给一个变量
- import是解构赋值的过程,指向
内存地址
,导入值会随着导出值变化,但是目前所有的引擎还没有实现import,所以使用babel+ES6, ES6转码成ES5在执行,import最终会被转码成require
8. 函数节流、函数防抖
-
函数节流
是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。因为滚动事件,是一个高频触发的事件
函数节流的要点是,声明一个变量当标志位,记录当前代码是否在执行。 如果空闲,则可以正常触发方法执行。如果代码正在执行,则取消这次方法执行,直接return。
let canRun = true;
document.getElementById("body").onscroll = function(){
if (!canRun) {
// 判断是否已空闲,如果在执行中,则直接return
return;
}
canRun = false;
setTimeout(function(){
console.log("函数节流");
canRun = true;
}, 300);
}
- 函数防抖
函数防抖的应用场景,最常见的就是用户注册时候的手机号码验证和邮箱验证了。只有等用户输入完毕后,前端才需要检查格式是否正确,如果不正确,再弹出提示语
函数防抖的要点,也是需要一个setTimeout来辅助实现。延迟执行需要跑的代码。
如果方法多次触发,则把上次记录的延迟执行代码用clearTimeout清掉,重新开始。
如果计时完毕,没有方法进来访问触发,则执行代码
let timer = false;
document.getElementById("debounce").onInput = function(){
clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
timer = setTimeout(function(){
console.log("函数防抖");
}, 300);
};
两者的区别: 节流是每隔一段时间执行一次,防抖只执行一次
9. this
- 普通对象:this指向器所在函数对象,谁调用函数指向谁
- 构造函数: this指向被创建的新对象的实例对象
- DOM事件: this指向触发事件的DOM元素
- call()和apply():this指向第一个参数对象
- setTimeout和setInterval: this 指向全局
- 箭头函数: this 指向最初定义函数时的作用域
var b = 11
var obj = {
b:22,
say: () => {
console.log(this.b)
}
}
obj.say() // 输出11
改变this的方式
call: fn.call(target, 1, 2)
apply: fn.call(target, [1, 2])
-
bind: fn.bind(target)(1, 2)
apply 和 call 的用法几乎相同, 是为了借用别的对象的方法. 唯一的差别在于:当函数需要传递多个变量时, apply 可以接受一个数组作为参数输入, call 则是接受一系列的单独变量 .
和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 printName 中的 this 并没有被改变,依旧指向全局对象 window。
var obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.call(obj, 'zhang', 'xiao') 输出 My name is: zhang xiao;
getName.apply(obj, ['zhang', 'xiao']) 输出 My name is: zhang xiao;
let fn = getName.bind(obj)
console.log(fn)
// 输出 getName(firstName, lastName) {
// console.log(this.message + firstName + ' ' + lastName)
// }
fn('zhang', 'xiao') // 输出 My name is: zhang xiao
10.数组
map 、forEach相同点
- 都是循环遍历数组中的每一项,支持三个参数,item, index, arr
- 匿名函数this指向window
- 只能遍历数组,而且都不会改变原数组
map 、forEach区别
map返回的是一个新数组,数组中的元素为处理之后的值。arr为空时,map返回的也是空数组,forEach对于空数组是不会调用回调函数的,物理arr是否为空,forEach返回的都是underfine. 这个方法只是将数组中的每一项作为callback执行一次。