1.原型&原型链
1.1概念
- 原型(Prototype):由于JS是用构造函数来创建对象的,而构造函数内部都有一个prototype属性,属性值为对象,当使用构造函数创建一个实例时,该实例内部会存有一个指针,指针指向构造函数的prototype, 该指针被称为实例的原型
- 显式原型:每一个类(构造函数)都有一个显示原型prototype(本质就是个对象)
- 隐式原型:每一个实例都有一个隐式原型__proto__
- 原型链(Prototype Chain):当在对象中查找某个属性或方法时候,会先在自身查找,如果自身没有则会顺着原型对象的引用关系向上查找,直到找到该属性或方法或者到达原型链的末端(null),这个过程将对象连接起来形成的链表结构称为原型链。
- 注意⚠️:多数浏览器都支持通过__proto__去访问对象的原型,但它不是规范中规定的,故建议使用es5提供的Object.getPrototypeOf()方法获取
1.2关系
// 构造函数
function Person () {
constructor() {}
//***
}
// 构造实例
const newPersonObj = new Person();
newPersonObj.__proto === Person.prototype; // === Person.prototype = {..., constructor: Person };
obj.__proto.constructor === obj.constructor === Person.prototype.constructor = Person;
1.3引申
- 原型链尽头 由于原型链上的所有原型都是对象,所有的对象最终都由于Object构造,故原型链的终点最终都会走到Object.prototype.proto,而 Object.prototype.proto=== null // true,所以,原型链尽头是 null。
- 原型链特点:由于构造函数的prototype属性是通过引用传递的,每个实例并没有自身的原型副本,故当某个实例对原型上的方法或属性进行改写时,其余实例也会收到影响
2.作用域&作用域链
2.1作用域分类
- 全局作用域:全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。
- 最外层函数和最外层函数外面定义的变量拥有全局作用域
- 所有未定义直接赋值的变量自动声明为全局作用域
- 所有 window 对象的属性拥有全局作用域
- 函数作用域
- 在函数体内声明的变量,只有该函数或嵌套的函数可以访问
- 函数作用域的访问规则:只有内层可以访问外层,外层不可访问内层
- 块级作用域:ES6中新增的let,const指令创建
- let,const:不可以进行变量提升,也不可以重复声明
- 在循环中,适合绑定块级作用域,将计数器变量控制在循环体内部
2.2作用域链
概念:在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到 window 对象就被终止,这一层层的关系就是作用域链。
作用:保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找
3this
3.1对this的理解
this是执行上下文中的一个属性,它指向最后调用这个方法的对象。实际开发中,this的指向可以通过四种调用方法来判断
- 函数调用:当一个函数不是一个对象中的属性来调用,而是直接作为函数来调用时,this指向全局,如fn();
- 方法调用:当函数作为一个对象中的属性来调用,this指向该对象,如obj.fn();
- 构造器调用:如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象;
- 显示绑定(即通过call,apply,bind)指定this指向
3.1.1this——函数调用
<script>
const haha = "hello";
function fn(){
// do something
console.log(this.haha);
}
fn() // hello
</script>
3.1.2this——方法调用
const haha = "hi";
const obj = {
haha: "hello",
fn: function() {
console.log(this.haha);
}
}
obj.fn(); //hello
3.1.3this——构造器调用
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.sayHi = function (){
console.log('你好,我叫'+this.name)
}
let person = new Person('frank', 18)
person.name === 'frank' // true
person.age === 18 // true
person.sayHi() // 你好我叫frank
3.1.4this——显示绑定
call apply bind的区别
- call(指定this, ...params): 多个参数,返回函数结果(立即调用)
- apply(指定this, [...params]): 两个参数,返回函数结果(立即调用)
- bind(指定this, ...params): 多个参数,返回函数(需要手动调用) // TODO: 手写call apply bind
3.1.5this——箭头函数
- 箭头函数没有自己的this指向,它的this是捕获的上下文的this,这个this不会被改变。
- 前四种方式,都是调用时才能确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
3.1.6引申
【new 一个箭头函数会发生什么】 首先,new一个实例,会发生以下四步
- 创建一个新对象
- 将该对象的指针指向构造函数的prototype
- 将构造函数的this指向该实例
- 返回对象 而箭头函数没有自己的this指向,也没有原型,故上述第二三步无法做到
- TODO
4闭包
4.1概念
闭包 = 函数 + 函数能够访问的自由变量 闭包(closure)是一个函数以及它所引用环境的组合。 创建闭包的常见方式是在一个函数内部创建另一个函数,而该内部函数可以访问外部函数的局部变量,当外部函数执行完毕后,通常会释放其局部变量,但闭包可以防止这种情况发生,闭包中的局部变量会继续存在,因为闭包会引用这些变量。——引申:内存泄漏 简单来说:一个函数和他周围状态的饮用捆绑在一起的组合,内部和外部存在引用捆绑
4.2闭包的形式
4.2.1函数作为返回值
function mail(){
const mailMeassage = '这是一封信的内容'
return function(){
return mailMeassage;
}
}
const envelop = mail();
envelop();
- 函数可以作为返回值传递
- 函数外部可以通过一定方式获取到内部局部作用域的变量
- 副作用:导致内部局部变量不能被GC
4.2.2函数作为参数
let mailMeassage;
// 通用存储:envelop仅存储信息内容,如何使用取决于fn
function envelop(fn){
mailMeassage = 1;
fn()
}
// 业务逻辑
function mail(){
console.log(mailMeassage);
}
envelop(mail)
4.2.3函数嵌套
let counter = 0;
function outerFn(){
function innerFn(){
counter++;
console.log(counter);
}
return innerFn;
}
outerFn()();
4.2.4事件处理(异步)的闭包
——引申:推动js的模块化发展——立即执行函数。即拥有独立作用域
let list = document.getElementsByTagName('li');
for(var i = 0; i<list.length,i++){
(function(i){
list[i].onClick = function(){
console.log(i)
}
})
}
for(let i = 0; i<list.length,i++){
}
4.2.4实现私有变量
// 非私有,items可直接外部改变 userFn().items.push(item)
function userFn(){
return {
items: [],
setItems(item){
this.items.push(item);
return this.items;
}
}
}
// 私有。items属于selfFn独有,外部不可直接修改
function selfFn(){
const items = [];
return {
setItems(item){
items.push(item);
return items;
}
}
}
selfFn().setItems("1") // 得到 ["1"]
selfFn().setItems("2") // 得到 ["2"]