JS高级+考核

学习总结

JS高级

一、执行上下文与执行上下文栈

变量提升与函数提升

1.变量声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到
值:undefined
2.函数声明提升
通过function声明的函数,在之前就可以直接调用
值:函数定义(对象)
3.问题:变量提升和函数提升如何产生的

执行上下文
代码分类

1.全局代码
2.函数(局部)代码

全局执行上下文

1.在执行全局代码前将window确定为全局执行上下文
2.为全局数据进行预处理
var定义的全局变量==>undefined,添加为window的方法
function声明的全局函数==>数值(fun),添加为window的方法
this==>数值(window)
3.开始执行全局代码

函数执行上下文

1.在调用函数,准备执行函数体之前,创建对应函数执行上下文对象(虚拟的,存在于栈中)
2.对局部数据进行预处理
形参变量== > 赋值(实参)== >添加为执行上下文的属性
argument==>赋值(实参列表),添加为执行上下文的属性
var定义的局部变量== >undefined,添加为执行上下文的属性
function声明的函数== >赋值(fun),添加为执行上下文属性
this==>赋值(调用函数的对象)
3.开始执行函数体代码

执行上下栈

栈:后进先出
队列:先进先出
1.在全局代码执行前,JS引擎就会创建一个栈来储存管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window

//依次输出:
// gb:undefined
// fb:1
// fb:2
// fb:3
// fe:3
// fe:2
// fe:1
// ge:1
//过程中执行了5个上下文

console.log('gb:' + i)
var i = 1
foo(1)
function foo(i){
  if(i == 4){
    return
  }
  console.log('fb:' + i)
  foo(i + 1)//递归调用:在函数内部调用自己
  console.log('fe:' + i)
}
console.log('ge:' + i)

二、作用域与作用域链

作用域

理解:一个代码所在的区域;静态的(相对于上下文对象)
作用:隔离变量,不同作用域下同名变量不会有冲突

作用域和执行上下文

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

作用域链

实例:

var a = 1
function fn1() {
  var b = 2
  function fn2() {
    var c = 3
    console.log(c)	//3
    console.log(b)	//2
    console.log(a)	//1
    console.log(d)	//报错
  }
  fn2()
}
fn1()

面试题

var x = 10
function fn(){
  console.log(x)
}
function show(f){
  var x = 20
  f()
}
show(fn)
//输出10
var fn = function(){
  console.log(fn)
}
fn()
var obj = {
  fn2: function(){
    console.log(fn2)
    //修改:console.log(this.fn2)
  }
}
obj.fn2()

在这里插入图片描述
ES5中只有全局作用域和函数作用域,这表示在全局作用域中,只有函数具有切割作用域的功能,所以obj后面的括号是没有切割作用域的功能的,fn2这个函数作用域往外走直接看到全局作用域,所以此时上下文视野里只有obj和上面的fn两个变量,是找不到fn2的于是报错
:只有两个能形成作用域,一个是全局,一个是函数,函数外面没有包函数,那就直接看到全局

三、闭包

一)循环遍历加监听
var btns = document.getElementsByTagName('button')
for (var i = 0, length = btns.length; i < length; i++) {
  var btn = btns[i]
  btn.onclick = function () {
    alert('第' + (i + 1) + '个')
  }//点击按钮会执行显示第四个
  //原因:for循环里的函数绑定了点击事件,而点击事件得触发才能执行,
  //而for循环一开始就会执行,所以当触发点击事件时,循环其实已经执行完成了
}
for (var i = 0, length = btns.length; i < length; i++) {
  var btn = btns[i]
  btn.index = i
  btn.onclick = function () {
    alert('第' + (this.index + 1) + '个')
  }
}
二)闭包
如何产生闭包

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数变量(函数)时,就产生了闭包

闭包是什么

使用chrome调试查看
理解一:闭包是嵌套的内部函数(绝大多数人)
理解二:包含被引用变量(函数)的对象(极少数人)
注意:闭包存在于嵌套的内部函数中

产生闭包的条件

函数嵌套
内部函数引用了外部函数的数据(变量/函数)

特点

函数内部的变量会一直存在于内存中,不会被立即释放

//1.将函数作为另一个函数的返回值
function fn1(){
  var a = 2
  function fn2(){
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() //3
f() //4


//2.将函数作为实参传递给另一个函数调用
function showDelay(msg,time){
  setTimeout(function(){
    alert(msg)
  },time)
}
showDelay('tjc',2000)
三)闭包的作用

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

四)闭包的生命周期

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

function fn1(){
  //此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
  var a = 2
  function fn2(){
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() //3
f() //4
f = null   //闭包死亡(包含闭包的函数对象成为垃圾对象)
五)闭包的应用

定义JS模块
*具有特定功能的JS文件
*将所有的数据和功能都封装在一个函数内部(私有的)
*只向外暴露一个包含n个方法的对象或函数
*模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

第一种
<script type="text/javascript">
    var module = myModule()
    module.doSomething()
    module.doOtherthing()
</script>
function myModule(){
  //私有数据
  var msg = 'My tjc'
  function doSomething(){
    console.log('doSomething()' + msg.toUpperCase)
  }
  function doOtherthing(){
    console.log('doOtherthing()' + msg.toLowerCase())
  }
  //向外暴露对象(给外部使用的方法)
  return {
    doSomething:doSomething,
    doOtherthing:doOtherthing
  }
}

在这里插入图片描述

第二种
<script type="text/javascript">
    myModule2.doSomething()
    myModule2.doOtherthing()
</script>
(function () {
  //私有属性
  var msg = 'MMy tjc'
  function doSomething() {
    console.log('doSomething()' + msg.toUpperCase)
  }
  function doOtherthing() {
    console.log('doOtherthing()' + msg.toLowerCase())
  }
  window.myModule2 = {
    doSomething:doSomething,
    doOtherthing:doOtherthing
  }
})()
六)闭包的缺点和解决
缺点

1.函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
2.容易造成内存泄漏

内存溢出

一种程序运行出现的错误
当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误

内存泄漏

占用的内存没有及时释放
内存泄漏积累多了,就容易导致内存溢出
常见的内存泄漏:
意外的全局变量
没有及时清理的计时器或回调函数
闭包

解决

1.能不用闭包就不用
2.及时释放(让内部函数成为垃圾对象–>回收闭包)

七)面试题

1、

var name = "TheWindow"
var Object = {
  name: "My Object",
  getNameFunc : function () {
    return function () {
      return this.name;
    }
  }
};
alert(Object.getNameFunc()());
//输出的为The Window

寻找this --> 谁调用了内层的函数,但内层函数并不是通过调用执行的,而是因为后面加了(),使得其立即执行,立即执行函数的this是指向window的
2、

var name2 = "The Window"
var object2 = {
  name2:"My Object",
  getNameFunc:function(){
    var that = this
    return function(){
      return that.name2
    }
  }
}
alert(object2.getNameFunc()())
//输出My Object

此时要找this–>谁调用了外层函数,显然是Object通过方法调用了外层函数,所以that = this 指向object

四、对象创建模式、继承模式

对象创建模式
方式一:Object构造函数模式

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

方式二:对象字面量模式

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

方式三:工厂模式

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

function createPerson(name,age){   //返回一个对象的函数===>工厂函数
  var obj = {
    name:name,
    age:age,
    setName:function(name){
      this.name = name
    }
  }
  return obj
}
 
//创建2个人
var p1 = createPerson('Tom',12)
var p2 = createPerson('Bob',13)
方式四:自定义构造函数模式

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

function Person(name,age){
  this.name = name
  this.age = age
  this.setName = function(name){
    this.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',130000)
console.log(s instanceof Student)
方式五:构造函数+原型的组合模式

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

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.setName = function(name){
  this.name = name
}
var p1 = new Person('Tom', 12)
var p2 = new Person('Bob', 23)
console.log(p1, p2)
继承模式
原型链的继承

套路
1.定义父类型构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.创建父类型的对象赋值给子类型的原型
5.将子类型原型的构造属性设置为千类型
6.给子类型原型添加方法
7.创建子类型的对象;可以调用父类型的方法
关键
1.子类型的原型为父类型的一个实例对象

//父类
function Supper(){
  this.SupProp = 'Supper property'
}
Supper.prototype.showSupperProp = function(){
  console.log(this.SupProp)
}

//子类型
function Sub(){
  this.SubProp = 'Sub property'
}
//子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
Sub.prototype.showSubProp = function (){
  console.log(this.SubProp)
}

var sub = new Sub()
sub.showSupperProp()
console.log(sub.constructor)

在这里插入图片描述

  1. 这里可以补充一下constructor 的作用:constructor中文就是构造函数
  2. 其作用是让某个构造函数产生的 所有实例对象(比如f) 能够找到他的构造函数(比如Fun),用法就是f.constructor
  3. 此时实例对象里没有constructor 这个属性,于是沿着原型链往上找到Fun.prototype 里的constructor,并指向Fun 函数本身
  4. 由于这里的继承是直接改了构造函数的prototype 的指向,所以在 sub的原型链中,Sub.prototype 没有constructor 属性,反而是看到了一个super 实例
  5. 这就让sub 实例的constructor 无法使用了。为了他还能用,就在那个super 实例中手动加了一个constructor 属性,且指向Sub 函数
    修正:
Student.prototype.constructor = Student
借用构造函数继承(假)

套路:
1.定义父类型构造函数
2.定义子类型构造函数
3.在子类型构造函数中调用父类型构造
关键:
1.在子类型构造函数中通用super( )调用父类型构造函数

function Person(name,age){
  this.name = name
  this.age = age
}
function Student(name,age,price){
  Person.call(this,name,age)  //相当于:this.person(name,age)
  this.price = price
}

var s = new Student('Tom',20,12000)
console.log(s.name,s.age,s.price)
组合继承(原型链的继承+借用构造函数继承)

缺点:父类型会被调用两次

function Parent () {
    this.name = 'parent';
    this.play = [1, 2, 3];
}
Parent.prototype.getName = function () {
    return this.name;
}

function Child() {
    // 第二次调用 Parent()
    Parent.call(this);
    this.type = 'child';
}
// 第一次调用 Parent()
Child.prototype = new Parent();
// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child;

console.log(new Child());

var child1 = new Child();
var child2 = new Child();
child1.play.push(4);
console.log(child1.play); // [1, 2, 3, 4]
console.log(child2.play); // [1, 2, 3]
console.log('child1 getName...', child1.getName()); // 正常输出'parent'
console.log('child2 getName...', child2.getName()); // 正常输出'parent'
ES6类的继承-extends + super(props)

利用 ES6 的 class,结合 extends 关键字 和 super(props) 方法实现对父类属性和方法的继承。

class Person {
    constructor(money) {
        this.money = money;
    }
    getMoney() {
        console.log(this.name + " get Person's money $" + this.money)
    }
}
class Child extends Person {
    constructor(money, name) {
        // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
        super(money);
        this.name = name;
    }
}
const child = new Child(1000, 'child');
child.getMoney();// child get Person's money $1000

五、进程与线程

进程

程序的一次执行,它占有一片独有的内存空间
可以通过Windows任务管理器查看进程
多进程:一应用程序可以同时启动多个实例运行

线程

是进程内的一个独立执行的单元
是程序执行的一个完整流程
是CPU的最小的调度单元
JS为单线程运行的
浏览器运行有的是单进程(Firefox)有的是多进程(chrome、新版IE)

单线程与多线程
多线程

在一个进程内,同时有多个线程运行
优点:能够有效提升CPU的利用率
缺点:创建多线程开销;线程间切换开销;死锁与状态同步问题

单线程

但使用H5中的Web Workers可以多线程运行
优点:顺序编程简单易懂
缺点:效率低
图解
在这里插入图片描述

相关知识

应用程序必须运行在某个进程的某个线程上
一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
一个进程内的数据可以供其中的多个线程直接共享
多个进程之间的数据是不能直接共享的
线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用

六、浏览器内核

支撑浏览器运行的最核心的程序
不同的浏览器可能不一样

chrome,Safari:webkit
Firefox:Gecko
IE:Trident
360,搜狐等国内浏览器:Trident + webkit

内核由很多模块组成
主线程

js引擎模块:负责js程序的编译与运行
html,css文档解析模块:负责页面文本的解析
DOM/CSS模块:负责dom/css在内存中的相关处理
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
. . . . .

分线程

定时器模块:负责定时器的管理事
件响应模块:负责事件的管理
网络请求模块:负责aiax请求,存在回调函数

七、H5 Web Workers多线程

当在浏览器端用户交互需要处理大量数据时,在主线程参与运算过程中,用户对页面进行任何操作都没有响应,很影响用户体验,此时就可以利用H5提供的Web Workers多线程来将主线程需要进行大量运算的代码添加到分线程中进行运算,这样即使运算过程中用户也依然可以的页面进行操作

Web Workers使用步骤:

运行在后台的js代码,独立于其他脚本,不会影响页面的性能
注意: 分线程是单独创建的一个js文件,且Window的方法在这里不能调用,因为在分线程里全局对象不是Window,而是DedicatedWorkerGlobalScope,因此在分线程中不能更新界面,这是特意这样设计的,就是为了防止js多线程对同一个对象处理时复杂得到同步问题
缺点:1.慢 ,直接在主线程计算会更快,但是在主线程计算过程会冻结界面,但是分线程计算不会阻塞界面
2.不能跨域加载js
3.分线程里代码不能访问window全局对象
4.不是所有浏览器都支持
5.worker内代码不能操作DOM
-1. 主线程中创建一个worker对象,并传入分线程的url
eg: var worker = new Worker(“worker.js”);
-2. 主线程中绑定接收消息的监听
eg: worker.onmessage = function(event) {
console.log("主线程接收分线程返回的数据: " + event.data);
}
-3. 主线程向分线程发送数据 由于该操作是异步进行,所有和onmessage代码没有顺序要求 eg: worker.postMessage(inputNum);
-4. 分线程接收主线程传递的数据
eg: var onmessage = function(event){}
-5. 分线程对数据进行计算处理
-6. 分线程返回处理结果
eg: postMessage(result)

相关API

● Worker: 构造函数,加载分线程执行的js文件
● Worker.prototype.onmessage: 用于接收另一个线程的回调函数
● Worker.prototype.postMessage: 向另一个线程发送消息

考核

什么是浅拷贝,和深拷贝,并举出拷贝的方法

l 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。
方法有:Object.assign(); {…目标对象} ,Array.prototype.slice(),Array.prototype.concat()
l 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。
方法有:JSON.stringify();

数据类型检测的方式有哪些

l typeof
l instanceof
l constructor
l Object.Prototype.call()

下周计划

开始进展项目,按今天分好的进程走,毕竟第一次写项目,早下手为上,得开始练一练算法题了,比较简单的算法都写不出来,这蓝桥杯300块,真的是要白花了

生活总结

这周感觉忙来忙去,一点都没有闲着,也不知道在忙些什么,周六的宿舍文化节忙了一下午,拔个河还把腰扭了,一个宿舍,腰扭了三个,每个人都腰酸背痛的,为了点积分,实在是对手太势均力敌了。
下周项目就要开始写了,今天我们小组开了个小会把分工明确了一下,第一次写项目还真是迷茫,无从下手。总之加油吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值