JS相关

95 阅读6分钟

1.原型&原型链

1.1概念

  • 原型(Prototype):由于JS是用构造函数来创建对象的,而构造函数内部都有一个prototype属性,属性值为对象,当使用构造函数创建一个实例时,该实例内部会存有一个指针,指针指向构造函数的prototype, 该指针被称为实例的原型
    • 显式原型:每一个类(构造函数)都有一个显示原型prototype(本质就是个对象)
    • 隐式原型:每一个实例都有一个隐式原型__proto__
  • 原型链(Prototype Chain):当在对象中查找某个属性或方法时候,会先在自身查找,如果自身没有则会顺着原型对象的引用关系向上查找,直到找到该属性或方法或者到达原型链的末端(null),这个过程将对象连接起来形成的链表结构称为原型链。
  • 注意⚠️:多数浏览器都支持通过__proto__去访问对象的原型,但它不是规范中规定的,故建议使用es5提供的Object.getPrototypeOf()方法获取

1.2关系

截屏2024-06-12 12.18.09.png

// 构造函数
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引申

  1. 原型链尽头 由于原型链上的所有原型都是对象,所有的对象最终都由于Object构造,故原型链的终点最终都会走到Object.prototype.proto,而 Object.prototype.proto=== null // true,所以,原型链尽头是 null。
  2. 原型链特点:由于构造函数的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指向,也没有原型,故上述第二三步无法做到
  1. TODO

4闭包

4.1概念

闭包 = 函数 + 函数能够访问的自由变量 闭包(closure)是一个函数以及它所引用环境的组合。 创建闭包的常见方式是在一个函数内部创建另一个函数,而该内部函数可以访问外部函数的局部变量,当外部函数执行完毕后,通常会释放其局部变量,但闭包可以防止这种情况发生,闭包中的局部变量会继续存在,因为闭包会引用这些变量。——引申:内存泄漏 简单来说:一个函数和他周围状态的饮用捆绑在一起的组合,内部和外部存在引用捆绑

4.2闭包的形式

4.2.1函数作为返回值

function mail(){
  const mailMeassage = '这是一封信的内容'
  return function(){
    return mailMeassage;
  }
}

const envelop = mail();
envelop();
  1. 函数可以作为返回值传递
  2. 函数外部可以通过一定方式获取到内部局部作用域的变量
  3. 副作用:导致内部局部变量不能被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"]

5参数传递