JS高级(续)及部分知识点

JS高级

作用域及作用域链

1.作用域

1.理解
● 就是一块“地盘”,一个代码段所在的区域
● 它是静态的(相对于上下文对象),在编写代码时就确定了、
2.分类
● 全局作用域
● 函数作用域
3.作用
● 隔离变量,不同作用域下同名变量不会有冲突

2.作用域及执行上下文的区别与联系

1.区别1
● 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
● 全局执行上下文环境是在全局作用域 确定之后,js代码马上执行之前创建
● 函数执行上下文是在调用函数时,函数体代码执行之前创建
2.区别2
● 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
● 上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会自动被释放
3.联系
● 上下文环境(对象)是从属于所在的作用域
● 全局上下文环境=>全局作用域
● 函数上下文环境=>对应的函数作用域

3.作用域链

1.理解
● 多个上下级关系的作用域链形成的链,它的方向是从下向上的(从内到外)
● 查找变量时就是沿着作用域链来查找的
2.查找一个变量的查找原则
● 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
● 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
● 再次执行2的相同操作,直到全局作用域,如果还找不到,就抛出找不到的异常

闭包

1.闭包理解

捕获了函数外的函数就是闭包

1.如何产生闭包?
● 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
2.闭包到底是什么?
● 使用chrome调试查看
● 理解一:闭包是嵌套的内部函数(绝大部分人)
● 理解二:包含被引用变量(函数)的对象(极少数人)
● 注意:闭包存在于嵌套的内部函数
3.产生闭包的条件?
● 函数嵌套
● 内部函数引用了外部函数的数据(变量/函数)
● 闭包产生几个就看外部函数执行几次

常见的闭包

1.将函数作为另一个函数的返回值
在这里插入图片描述
注意这里不能直接fn1()(),因为fn1会重复调用,会销毁上一次的执行环境
调用两次函数,之所以a的值没有刷新,一直递增,是因为外部函数只调用了一次,调用的两次fn()本质是调用两次fn2()
闭包最终存在没有消失
理解一: 根本原因是 f=fn1()这个语句,f指向fn2这个函数对象,这个函数关联着闭包,而闭包中有a
fn2在函数执行完后会释放,但是a依然存在,因为f=fn1()导致变量没有释放,函数将fn2赋值给f,实际是将地址值给了f,此时fn2已经不存在
理解二:函数中的内容执行完就会自动销毁,但是fn2这个赋值给了f,系统认为他不是垃圾数据,就不会自动销毁
理解三: var f 创建了f个对象,var f=fn1()指向了这个函数内部的地址fn2,也就是说有两个方法fn1 和 f 都能访问到fn2地址,调用这个f方法,也就是能访问到这fn2函数内部的属性
2.将函数作为实参传递给另一个函数调用

3.闭包作用

1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2.让函数外部可以操作(读写)到函数内部的数据
问题:
1.函数执行完后,函数内部声明的局部变量是否还存在?
一般不存在, 存在是因为还在闭包(内函数)里面
2.在函数外部能直接访问函数内部的局部变量吗
不能,但可以通过闭包让函数操作

在这里插入图片描述

4.闭包的生命周期

1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2.死亡:在嵌套的内部函数成为垃圾对象时

在这里插入图片描述

5.闭包的应用

1.定义js模块
● 具有特定功能的js文件
● 将所有的数据和功能都封装在一个函数内部(私有的)
● 只向外暴露一个包含n个方法的对象或函数

法一:
在这里插入图片描述
在这里插入图片描述
法二
在这里插入图片描述
在这里插入图片描述

6.闭包的缺点

1.缺点:
● 函数执行完后,函数内的局部变量没有释放,占用内存的时间会变长,容易造成内存泄漏
2.解决
● 及时释放
在这里插入图片描述

7.内存溢出&内存泄露

1.内存溢出
● 一种程序运行出现的错误
● 当程序运行需要的内存超过了剩余的内存时,就出现抛出内存溢出的错误
2.内存泄露
● 占用内存没有及时释放
● 内存泄露积累多了就容易导致内存溢出
● 常见的内存泄露
*意外的全局变量
*没有及时清理的计时器或回调函数
* 闭包

理解:假如有8M内存,占用的内存没有及时释放,只剩下5M内存可用,现在想使用6M空间,由于空间不够,导致内存溢出

对象创建模式 (五种)

1.Object构造函数模式

● 套路:先创建Object 对象,再动态添加属性/方法
● 适用场景:起始时不确定对象的内部数据
● 问题:语句太多

var obj=new Object();
p={};
p.name="Tom"
p.setName=function(name){
  this.name=name;
}
console.log(p.name)//Tom
p.setName("Bob");
console.log(p.name);//Bob

2.对象字面量模式

● 使用{}创建对象,同时指定属性/方法
● 适用场景:起始时对象内部数据是确定的、
● 问题:如果创建多个对象,有重复代码

var p{
  name:"Tom"
  setName:function(name){
    this.name=name;
  }
}
console.log(p.name);
p.setName("Bob");
console.log(p.name);

3.工厂模式

● 通过工厂函数动态创建对象并返回
● 适用场景:需要创建多个对象
● 问题:对象没有一个具体的类型,都是Object类型

function Person(name){
var obj={
  name:name;
  setName:function(name){
    this.name=name;
  }
}
  return obj;
}
var p=Person("Tom");

4.自定义构造函数模式

● 套路:自定义构造函数,通过new创建对象
● 适用场景:需要创建多个类型确定的对象
● 问题:每个对象都有相同的数据,浪费内存

<script type="text/javascript">
  //定义类型
  function Person(name, age) {
    this.name = name
    this.age = age
    this.setName = function (name) {
      this.name = name
    }
  }
  var p1 = new Person('Tom', 12)
  p1.setName('Jack')
  console.log(p1.name, p1.age)
  console.log(p1 instanceof Person)

  function Student (name, price) {
    this.name = name
    this.price = price
  }
  var s = new Student('Bob', 13000)
  console.log(s instanceof Student)

  var p2 = new Person('JACK', 23)
  console.log(p1, p2)

</script>

5.构造函数+原型的组合模式

● 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
● 适用场景: 需要创建多个类型确定的对象

<script type="text/javascript">
  function Person(name, age) { //在构造函数中只初始化一般函数
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }

  var p1 = new Person('Tom', 23)
  var p2 = new Person('Jack', 24)
  console.log(p1, p2)

</script>

继承模式(八种)

1.原型链继承

是js中最基本的继承方式
通过将子构造函数的原型对象指向父构造函数的实例,实现继承

function Parent() {
  this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

function Child() {
  this.name = 'Child';
}
Child.prototype = new Parent();

var child = new Child();
child.sayHello(); // Hello

在这个例子中,Child对象的原型对象被设置为Parent的一个实例。这样,当我们调用child.sayHello()时,它会首先在子对象上查找sayHello方法,然后在父对象原型链上找到并执行该方法。注意:原型链继承的缺点是,所有子对象共享同一个原型对象,对原型对象的修改会影响到所有子对象。

注意:原型链继承的缺点是,所有子对象共享同一个原型对象,对原型对象的修改会影响到所有子对象。

2.构造函数继承(经典继承)

通过在子构造函数中调用父构造函数来实现继承
在构造函数继承中,通过在子构造函数中使用**call()或apply()**方法,将父构造函数的上下文设置为子对象的上下文,从而实现继承。

function Parent(name) {
  this.name = name;
}

function Child(name,age) {
  Parent.call(this, name);//相当于 this.Person(name);类似于super(name);
  this.age=age;
  
}

var child = new Child('Child','11');
console.log(child.name); // Child

3.组合继承(原型链继承和构造函数继承)

通过调用父构造函数的方式实现属性的继承,通过将子构造函数的原型对象指向父构造函数的实例实现方法的继承。
继承了父构造函数的属性,又继承了父构造函数原型对象上的方法

function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

function Child(name) {
  Parent.call(this, name);
}
Child.prototype = new Parent();

var child = new Child('Child');
child.sayHello(); // Hello

缺点是在创建子对象时会调用两次父构造函数,一次是在设置原型时,一次是在创建子对象时。这样会产生一些不必要的开销。

4.原型式继承

通过使用一个临时构造函数和**Object.create()方法来实现继承。
本质是创建一个新对象,将其原型对象指向另一个已有的对象。
创建了一个parent对象,然后使用
Object.create()**方法创建了一个新对象child,并将其原型对象指向parent对象

var parent = {
  name: 'Parent',
  sayHello: function() {
    console.log('Hello');
  }
};

var child = Object.create(parent);
console.log(child.name); // Parent
child.sayHello(); // Hello

缺点是可以实现属性和方法的继承,但是不能传递构造函数的参数

5.Object.create()

把现有对象的属性,挂到新建对象的原型上,新建对象为空对象

let person = {
  name: 'mjy',
  age: 19,
  hoby: ['唱', '跳'],
  showName() {
    console.log('my name is: ', this.name)
  }
}
 
let child1 = Object.create(person)
child1.name = 'xxt'
child1.hoby.push('rap')
let child2 = Object.create(person)
 
console.log(child1)
console.log(child2)
console.log(person.hoby) // ['唱', '跳', 'rap']

优点: 不需要单独创建构造函数。
缺点: 属性中包含的引用值始终会在相关对象间共享,子类实例不能向父类传参

6.寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}
 
function createAnother(obj) {
  let clone = objectCopy(obj);
  clone.showName = function () {
    console.log('my name is:', this.name);
  };
  return clone;
}
 
let person = {
     name: "mjy",
     age: 18,
     hoby: ['唱', '跳']
}
 
let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy
 
let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']

优点: 写法简单,不需要单独创建构造函数。
缺点: 通过寄生式继承给对象添加函数会导致函数难以重用。使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似.

7.寄生组合式继承

前面讲过,组合继承是常用的经典继承模式,不过,组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数;一次是在创建子类型的时候,一次是在子类型的构造函数内部。寄生组合继承就是为了降低父类构造函数的开销而实现的。

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}
 
function inheritPrototype(child, parent) {
  let prototype = objectCopy(parent.prototype);
  prototype.constructor = child;
  Child.prototype = prototype;
}
 
function Parent(name) {
  this.name = name;
  this.hoby = ['唱', '跳']
}
 
Parent.prototype.showName = function () {
  console.log('my name is:', this.name);
}
 
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
 
inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
  console.log('my age is:', this.age);
}
 
let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
 
let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']

8.ES6继承

class Parent {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log('Hello');
  }
}

class Child extends Parent {
  constructor(name) {
    super(name);
  }
}

const child = new Child('Child');
console.log(child.name); // Child
child.sayHello(); // Hello

深拷贝&浅拷贝

1.浅拷贝

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。
方法有:Object.assign(); {…目标对象} ,Array.prototype.slice(),Array.prototype.concat()

2.深拷贝

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。
方法有:JSON.stringify();

数据类型检测的方式

typeof
instanceof
constructor
Object.Prototype.call()

call bind apply

1、相同点
三个都是用于改变this指向;
接收的第一个参数都是this要指向的对象;
都可以利用后续参数传参。
2、不同点
call和bind传参相同,多个参数依次传入的;
apply只有两个参数,第二个参数为数组;
call和apply都是对函数进行直接调用,而bind方法不会立即调用函数,而是返回一个修改this后的函数。

总结

这两周的学习结束了,通过考核,发现了很多知识点被我忽略了,导致出错,比如map方法,bind和call的区别

接下来要开始和后端合作写项目了,我没想到我会是组长,毕竟我只想当个小小组员按照安排来做,希望我这次的分工算合理吧,要是到最后项目完成不了那就真的无了;就两个人来写,估计接下来的30天晚上睡不好觉了,毕竟我最擅长内耗自己;真的好想摆烂,我希望不要到最后沦落到让我逃课去小组写页面的地步,毕竟我原本逃课是来玩的,还有那该死的早操早读,我也没想到运动会田径项目的跳远我被选上了,还有我作死报的词达人英语竞赛;真一个头两个大
接下来的一个月要挺过去啊,多交流多沟通

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值