沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 静态页面项目学习指导

    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • 静态页面学习指导
  • JS基础知识回顾

    • JS基础知识回顾
  • JS核心概念学习指导

  • 标准库与web api

    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
  • 经典案例学习指导

  • 基础领航考试

    • 基础领航考试题
    • 答案
  • HTML+CSS语言提升学习指导

  • Javascript语言提升

    • 2024前端发展
  • 网络与git学习指导

  • 第三方库与工程化学习指导

    • 第三方库与工程化学习指导
  • Vue入门学习指导

    • Vue入门学习指导
  • vue进阶学习指导

    • vue进阶学习指导
  • vue组件库学习指导

    • 前端性能优化
  • 入门到实战收官总结

    • MVVM
  • 38期直播课
  • 网络与git学习指导
luzhichang
2024-04-01
目录

JS核心概念学习指导

# 执行上下文

简而言之,其实就是评估和执行Javascript代码环境的抽象概念

上下文是一个英文语境context,其实建议大家换成中文语境,就是环境的意思。

执行任意一句代码,都需要一个执行时的环境。

**执行上下文简单理解:**就是一个隐形的对象,这个对象上记录了程序当前执行所依赖的环境因素

# 执行上下文的分类

  • 全局执行上下文
  • 函数执行上下文
  • eval执行上下文
  • 模块执行上下文

javascruot运行时首先会进入全局环境,对应就会生成全局上下文。

代码中都会存在函数,那么调用函数,就会进入函数执行环境。

代码中函数有多个,对应的函数执行上下文,就会存在多个,主要讨论的是这个函数执行上下文,我们都通过栈来管理执行上下文,一般称为执行栈,或者函数调用栈

# 栈的数据结构

image-20231219204842652

# 执行栈

全局执行上下文就是最大的代码片段。包括了程序中所有的代码,其中包括函数执行上下文分割的每一个小片的代码片段。函数的代码片段上都有执行上下文

而我们的执行栈,其实就是存放执行上下文的地方。

浏览器按照栈的执行顺序,依次执行。

function foo(){
  function bar(){
    return "I am bar";
  }
  return bar();
}

foo();

image-20231219205647745

function fn3(){
  return "hello world"
}
function fn2(){
  fn3()
}
function fn1(){
  fn2();
}
fn1();

伪代码:

// 创建执行栈
const ECStack = [];

ECStack.push(全局执行上下文)

ECStack.push(fn1执行上下文)

ECStack.push(fn2执行上下文)

// 执行fn3之后,没有其他内容了,开始出栈
ECStack.push(fn3执行上下文)

// fn3出栈
ECStack.pop();

// fn2出栈
ECStack.pop();

// fn1出栈
ECStack.pop();

面试题:

写法一:
function foo(){
  function bar(){
    return "I am bar";
  }
  return bar();
}

foo();

写法二:
function foo(){
  function bar(){
    return "I am bar";
  }
  return bar;
}

foo()();
写法一:
ECStack.push(foo的上下文)
ECStack.push(bar的上下文)

ECStack.pop() // bar出栈
ECStack.pop() // foo出栈

写法二:
ECStack.push(foo的上下文)
ECStack.pop() // foo出栈
ECStack.push(bar的上下文)
ECStack.pop() // bar出栈

# VO(variable object)

为了好理解,你可以直接把vo理解为全局上下文环境

VO用于存储当前执行环境所拥有的变量以及函数

其实最简单的理解,在浏览器环境,全局上下文,你就可以理解为,就是window

var a = 12;
console.log(this.a, window.a);
function b(){}
this.b();

this === window

# AO(activation object)

函数上下环境比较特殊:

AO = VO + arguments + params

分为分析(预编译)和执行的两个阶段:

1、如果当前上下文是函数上下文,首先分析函数所有的形参

  • 将形参名称与对应的值绑定到AO上,并将值挂到对应为止的arguments上
  • 如果没有对应实参的形参,值为undefined

2、函数声明

  • 如果遇到函数声明语句,将函数的名称与该函数的引用挂到当前上下文AO上
  • 如果AO身上已经存在该函数名称相同的表示符号,则覆盖

3、变量声明

  • 如果遇到了var声明的变量,将变量名与undefined挂载到当前上下文上
  • 如果AO上已经存在于该变量名相同的标识符,则忽略

面试题

function foo(a){
  var b = 2;
  function c(){}
  var d = function(){}
  b = 3;
}

foo(1);

执行完分析阶段之后,函数foo执行上下文身上AO是什么情况:

AO = {
  arguments:{
    0:1,
    length:1
  }
  a:1
  b:undefined
  c:function c(){}
	d:undefined
}

最终当foo函数开始执行的时候,将上面AO对象的初始状态进行处理,然后根据代码的状况发生对象状态的变化

AO = {
  arguments:{
    0:1,
    length:1
  }
  a:1
  b:3
  c:function c(){}
	d:function d(){}
}

# 面试题1:

function A(a,b){
  /*
  AO = {
    arguments={
      0:1
      1:2
      length:2
    }
    a:1
    b:function b(){}
  }
  */
  console.log(a,b)
  var b = 123;
	console.log(a,b);
	function b(){
		var d = 123;
  }
}

A(1,2)
AO = {
  arguments={
  	0:1
  	1:2
  	length:2
	}
	a:1
	b:function b(){}
}

# 面试题2

var g = 123;
var a = 2;
function A(a,b){
  console.log(a,b,g);
	var b = 123;
  function b(){}
  var a = function(){}
  console.log(a,b);
}
var g = 456;
A(1,2);

# 面试题3:

var foo = 1;
function bar(){
  /*
  AO = {
    argument:{}
    foo:undefined
  }
  */
  console.log(foo);
  if(!foo){
    var foo = 10;
  }
  console.log(foo);
}
bar();

# 面试题4:

var a = 1;
function b(){
  console.log(a);
  a = 10;
  return;
  function a(){}
}
b();
console.log(a);

# 面试题5:

console.log(foo); //function c
var foo = "A";
console.log(foo); // A
var foo = function(){
  console.log("B");
}
console.log(foo); // function B
foo(); // B
function foo(){
  console.log("C")
}
console.log(foo); // function B
foo(); // B

# 面试题6:

var foo = 1;
function bar(a){
  var a1 = a;
  var a = foo;
  function a(){
    console.log(a);
  }
  a1();
}
bar(3);

# 作用域

全局作用域

函数作用域

块级作用域(ES6 let const)

  • 声明变量不会提升
  • 禁止重复声明

# 作用域链

var a = 100;
function fn(){
  var b = 200;
  console.log(a);
  console.log(b);
}

fn();

# 静态作用域(词法作用域)

如果要去找到某个变量,要到创建这个函数的那个域去取值,注意这里强调的是创建,而不是调用

这个其实就是所谓的词法作用域

var x = 10;
function fn() { 
  console.log(x);
}

function show(f) { 
  var x = 20;
  f();
}

show(fn);

# 作用域链的构建过程

可以通过console.dir看到函数中有[[Scopes]]

function foo(){
  function bar(){
    ......
  }
}

当函数创建时,各自的[[Scopes]]为:

// 伪代码
foo.[[Scopes]]= [
  globalContext.VO
]

bar.[[Scopes]]= [
  fooContext.AO
  globalContext.VO
]
// 示例函数
var scope = "global scope";

function checkScope(){
  var scope2 = "local scope";
	return scope;
}

checkScope();
  1. 当遇到function checkScope()声明语句的时候,保存当前作用域链到其内部的属性[[Scopes]]上
checkScope.[[Scopes]] = [
  globalContext.VO
]
  1. 当遇到checkScope()执行的时候,创建该函数的执行上下文,并且激活AO
ECStack = [
  checkScopeContext
  globalContext
]
  1. 分析AO对象内容,并进入准备工作
checkScopeContext = {
  Scope:checkScope.[[Scopes]]
}
  1. 创建AO对象中的内容
checkScopeContext = {
  AO:{
    arguments:{
      length:0
    },
    scope2:undefined
  }
  Scope:checkScope.[[Scopes]]
}
  1. 将AO压入到函数作用域链的顶端
checkScopeContext = {
  AO:{
    arguments:{
      length:0
    },
    scope2:undefined
  }
  Scope:[AO, checkScope.[[Scopes]]]
}
  1. 分析工作结束,开始执行函数,AO给值
checkScopeContext = {
  AO:{
    arguments:{
      length:0
    },
    scope2:'local scope'
  }
  Scope:[AO, checkScope.[[Scopes]]]
}
  1. checkScope执行完成,销毁函数
ECStack = [
  globalContext
]

# 闭包

一般情况下,当函数内部定义了另外一个函数,内部函数使用了外部函数的变量,并且内部函数直接返回的情况下,就凸显了闭包的作用,因为延长了变量的生命周期。在这种情况下,内部函数可以访问外部函数的变量,因为他们共享了同一个作用域链

闭包:词法作用域 + 作用域链 + 执行上下文所产生的必然的结果

function fn1(){
  let a = 1;
  function fn2() { 
    let b = 2;
    console.log(a, b);
  }
  return fn2;
}

let f = fn1();

f(); // 1 2

// fn2.[[scope]] = fn1.Scope //----> [fn1.AO, globalContext.VO]

// fEC = {
//   AO: {
//     arguments: { ...},
//     b:2
//   }
//   Scope:[f.AO, fn1.AO, globalContext.VO]
// }
for(var i = 0; i < btns.length; i++) {
  btns[i].onclick = (function a(i){
    return function b() {
      alert(i);
    }
  })(i)
}

btns[0]()
btns[1]()
btns[2]()

// 比如执行到btns[1],对于父函数a
a.[[scope]] = [globalEC.VO]
aEC = {
  AO:{
    arguments:{'0':1,length:1}
    i:1
  }
  Scope:[aEC.AO, globalContext.VO]
}

b.[[scope]] = aEC.Scope = [aEC.AO,globalContext.VO]
bEC = {
  AO:{
    arguments:{}
  },
  Scope:[bEC.AO, aEC.AO,globalContext.VO]
}

# 函数柯里化

function outerFunction(x) { 
  return function innerFunction(y) { 
    return x + y;
  }
}
// 比如这个代码可以一开始先执行
// 对于外部,可能看到的就只有closure函数
const closure = outerFunction(5);

const num = closure(8);
console.log(num);

// 验证邮箱
function validateInput(validateFn,successFn,errorFn) { 
  return function (input) { 
    if (validateFn(input)) {
      successFn(input);
    }
    else {
      errorFn(input);
    }
  }
}
function isEmail(input) { 
  return input.includes('@');
}
function handleSuccess(input) { 
  console.log(`输入邮箱:${input},格式正确`);
}
function handleError(input) { 
  console.log(`输入邮箱:${input},格式错误`);
}

const validate = validateInput(isEmail, handleSuccess, handleError)

validate("xxx163.com")

# 惰性求值

// 模拟耗时操作
function expensiveOperation(n) { 
  console.log('进行了耗时的操作1!');
  console.log('进行了耗时的操作2!');
  console.log('进行了耗时的操作3!');

  return n * 10
}

function isBigNumber(n) { 
  return n >= 100;
}

function lazyEvaluation(condition, expensiveOperation) { 
  let result;
  return function (n) { 
    // 条件满足,进行运算
    if (condition(n)) { 
      // result没有值就进行耗时的运算,如果有值,直接返回第一次运行的值
      if (!result) { 
        result = expensiveOperation(n);
      }
      return result;
    }
    console.log(n + "----不满足条件")
    return undefined
  }
}
const lazyFn = lazyEvaluation(isBigNumber, expensiveOperation)

console.log(lazyFn(50))
console.log(lazyFn(100))
console.log(lazyFn(200))
console.log(lazyFn(300))

# this

# 全局this的指向

如果是严格模式下,全局this指向的就是undefined

浏览器全局this指向window

nodejs环境下,全局this指向空对象{}

# 函数内部的this指向

既然是函数内部的this指向,肯定是函数调用的时候才有this指向

1、函数如何被调用的

2、this指向和执行上下文相关

  • new 调用 ----》 this指向新对象
  • 直接调用 ----》 全局对象
  • 对象调用 ------》 调用的对象
  • call,apply,bind -----》 第一个参数

# 箭头函数:

箭头函数没有this,如果说在箭头函数中使用this,这个this其实就是闭包的调用

# 什么是类,什么是对象

绕口令:

具有相同特性(属性,数据元素)和行为(功能)的对象的抽象,就是类。

对象的抽象是类,类的实例化就是对象

类:其实就是一种类型,js有各种各样的基础类型,但是基础类型描述不了复杂的事务,所以,我们需要创建自己的复杂类型,用来描述具体的业务需要。复杂的类型,其实总是由简单的类型所组成的。

类型并不能直接使用,它只是一种对相同属性和行为的描述。要使用,就需要通过类来实例化对象。

const obj1 = {}

本质上和下面的代码没有区别

const obj2 = new Object();
obj1.name = "aaa";
obj1.show = function(){}

我们使用js,总是习惯用动态的操作给对象添加属性和功能

但是这种做法在典型的面向对象的语言中,是错误的,必须要现有类的概念,然后通过类实例化模板对象

然后才能通过对象进行操作,不能动态的给对象添加属性和功能

# 构造函数

在JavaScript中,面向对象的基础,其实并不是基于”类“的,而是基于构造函数(constructor)和原型链(prototype)的

为什么JavaScript有原型和原型链?

在典型的面向对象语言中,面向对象的三大特性:封装,继承和多态,js中的原型和原型链就是为了实现面向对象的继承关系。有了继承关系,才能够让js 的对象,比较方便的向上追溯源头。

function Animal(){
  this.name = "Animal;
  this.showName = function(){
    console.log(this.name)
  }
}

const a = new Animal();

一般有一个不成文的规定,那就是构造函数的函数名首字面大写,如果是普通函数,首字面小写

# 普通函数的二义性

为什么ES6要添加箭头函数和class语法糖?

目的就是为了区分普通函数和类

因为之前的构造函数,并不能很好的区分普通函数和构造函数

# new

const a1 = new Animal();
const a2 = new Animal;

console.log(a1);
console.log(a2);

new命令本身就可以执行构造函数,所以,如果构造函数没有参数的话,可以不用带括号

const a3 = Animal();
console.log(a3); // undefined

这样子写的话,构造函数就变成了普通函数,并不会生成实例对象,this这个时候代表全局对象,

如果要防止这种情况可以有下面的解决方案:

function Animal() {
  "use strict"
  this.name = "Animal"
  this.showName = function(){
    console.log(this.name)
  }
}

另外呢,如果使用ESM模块化的话,代码自动会变成use strict

也可以使用判断:

function Animal() {
  if (!(this instanceof Animal)) { 
    return new Animal();
  }
  this.name = "Animal"
  this.showName = function(){
    console.log(this.name)
  }
}

还可以使用new命令的属性

function Animal() {
  if(!new.target){
    throw new Error("必须使用 new 命令生成实例");
  }
  this.name = "Animal"
  this.showName = function(){
    console.log(this.name)
  }
}

# new命令的原理

使用new命令的步骤:

1、创建一个空对象,作为将要返回的对象实例

2、将这个空对象的原型,指向构造函数的prototype属性

3、将这个空对象赋值给函数内部的this关键字

4、开始执行构造函数内部的代码

所以,构造函数内部,this指的是一个新生成的空对象,所有针对this 的操作,都会发生在这个空对象上。

构造函数之所以称之为构造函数,其实就只在构造操作一个空对象(this对象),将其构造成你所需要的样子

function Animal() {
  this.name = "Animal"
  this.showName = function(){
    console.log(this) // Animal
    console.log(this.__proto__ === Animal.prototype) // true
  }
}

const a = new Animal();
console.log(a); // Animal
a.showName();

如果构造函数有返回值,分为两种情况

返回基本数据类型,没有影响

返回对象,this会指向返回的对象类型

function Animal() {
  this.name = "Animal"
  this.showName = function(){
    console.log(this) 
    console.log(this.__proto__ === Animal.prototype)
  }
  return {id:1,name:"jack"}
}

const a = new Animal();
console.log(a);  // 这里的a会指向返回的对象
a.showName(); // error
function show() { 
  function test() { 
    console.log("hello")
  }
}

const s = new show();
console.log(s); // {}
console.log(typeof s); // object
s.test(); //error

除了通过构造函数创建对象,也可以直接通过对象创建对象

# Object.create()

var p1 = {
  name: "jack",
  age: 18,
  showName: function () {
    console.log(this.name)
  }
}

var p2 = Object.create(p1);
console.log(p2.name);
console.log(p2.age);

create这个函数,其实是通过prototype的赋值,给另外一个对象

Object.create = function(o){
  function F(){};
  F.prototype = o;
  return new F();
}

# 原型

为什么有原型和隐式原型:JS没有记录类型的元数据,因此,需要一个内容去记录它。

所以才有了原型和隐式原型,目的就是为了确定其类型

# prototype

在javascript中,所有的函数都有prototype的属性,这个prototype其实就是一个空对象

我们一般把这个对象就称为函数的原型,

# __proto__

__proto__是每个对象都有的一个隐式原型

# 原型链

由于原型prototype本身是对象,因此,它也有隐式原型,指向的规则不变,这样一来,从某个对象触发,依次往上寻找隐式原型的指向,将形成一个链条,这个链条就叫做原型链

image-20240108215047860

  • 原型的本质是对象
  • 所有的函数都有原型属性prototype
  • prototype默认包含一个属性constructor,该属性指向函数本身
  • 所有的对象都有隐式原型,__proto__
  • 隐式原型指向该对象的构造函数原型prototype
  • 在查找对象成员属性或者方法的时候,如果对象本身没有该成员,则会到隐式原型上查找
  • 所有函数的隐式原型都指向Object的prototype
  • 两个特殊情况:
    • Function的隐式原型指向自己的原型
    • Object原型的隐式原型指向null

# 面试题

var F = function () { };
Object.prototype.a = function () {};
Function.prototype.b = function () { };

var f = new F();

console.log(f.a)
console.log(f.b)
console.log(F.a)
console.log(F.b)

答案:

[Function (anonymous)]
undefined
[Function (anonymous)]
[Function (anonymous)]
function A(){}
function B(a){
  this.a = a;
}
function C(a){
  if(a){
    this.a = a;
  }
}
A.prototype.a = 1
B.prototype.a = 1
C.prototype.a = 1

console.log(new A().a)
console.log(new B().a)
console.log(new C(2).a)

# ESMASciprt6中的类

传统的构造函数有二义性

属性和原型方法定义是分离的,降低了可读性

原型成员可以被直接枚举

默认情况下,构造函数可以被当做普通函数

# 类的特点

1、类声明不会被提升,与let,const一样

2、类中所有代码默认在严格模式下

3、类的所有方法都是不可被枚举的(enumerable:false)

4、类的方法不能作为构造函数

5、类的构造器必须使用new来调用

class Computer {
  constructor(name) {
    this.name = name;
  }
  show() {
    console.log(`这是一台${this.name}电脑`)
  }

  static staticShow() { 
    console.log("这是静态方法")
  }
}
const a = new Computer('联想');
a.show();
console.log(Computer.prototype)
console.log(a.__proto__)

for (let prop in a) { 
  console.log(prop)
}

Computer.prototype.show();
Computer.staticShow();

# 类的继承

两个新的关键字

  • extends
  • Super
class Animal {
  constructor(name) {
    if (new.target === Animal) { 
      throw new Error('不要直接创建Animal对象实例,应该通过子类创建');
    }
    this.name = name;
  }
  say() {
    console.log('name--' + this.name);
  }
}

class Dog extends Animal { 
  constructor(name,age) { 
    super(name)
    this.age = age;
  }
}

const dog = new Dog('小狗',3);
console.log(dog);

如何使用构造函数实现继承?

function Animal(name) { 
  this.name = name;
}
Animal.prototype.show = function () { 
  console.log("name---" + this.name);
}

function Dog(name, age) { 
  Animal.call(this, name);
  this.age = age;
}

Object.setPrototypeOf(Dog.prototype, Animal.prototype);

const d = new Dog("小黑", 3);
d.show();
console.log(d);
console.log(d.__proto__);

# Object常用的API

# 1、Object.is

其实基本和严格相等 (===)一致,只是有一些特殊的相等处理

  • NaN和NaN相等
  • +0和-0不相等
console.log(NaN === NaN)// false
console.log(Object.is(NaN, NaN))// true
console.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // false

# 2、Object.assign

用来混入对象,相当于浅拷贝

let a = {
  id: 1,
  name: "jack"
}

let b = {
  age: 20
}

const c = Object.assign({}, a, b);
console.log(a)
console.log(b)
console.log(c)

ES7还有更方便的做法

const d = { ...a, ...b }
console.log(d)

# 3、Object.setPrototypeOf

可以设置某个对象的隐式原型

let a = {
  id: 1,
  name: "jack"
}

let b = {
  age: 20
}
Object.setPrototypeOf(b, a);
for (let key in b) {
  console.log(key)
}

age
id
name

# 4、Object.getPrototypeOf

查找一个对象的原型对象

let a = {
  id: 1,
  name: "jack"
}

let b = {
  age: 20
}
// Object.setPrototypeOf(b, a);
// for (let key in b) {
//   console.log(key)
// }
// console.log(Object.getPrototypeOf(b));
// 通过下面的打印,其实大家可以看出,简写的let b = {},实际上就是let b = new Object();
// console.log(Object.getPrototypeOf(b).constructor.name);
class Person { 
  constructor(name, age) { 
    this.name = name;
    this.age = age;
  }
}
let p = new Person("jack", 20);

console.log(Object.getPrototypeOf(p).constructor.name)

Object.setPrototypeOf(b, p);

console.log(Object.getPrototypeOf(b).constructor.name);

# 5、keys,values,entries

let obj = { name: "jack", age: 20 };
console.log(Object.keys(obj));
for (let key of Object.keys(obj)){
  console.log(key, obj[key])
}
console.log(Object.values(obj));
for (let value of Object.values(obj)){
  console.log(value)
}
console.log(Object.entries(obj));
for (let [key, value] of Object.entries(obj)){
  console.log(key, value)
}

# 7、Object.fromEntries

可以将键值对(包括类似于双层数组的Object.entries())类型转换为Object

let authors = [["0", "天蚕土豆"], ["1", "唐家三少"], ["2", "我吃西红柿"], ["3", "火星引力"]];
let obj1 = Object.fromEntries(authors);
console.log(obj1);

let map = new Map();
map.set("name", "斗破苍穹");
map.set("author", "天蚕土豆");
map.set("类型", "玄幻");
map.set("price", "100");

let obj2 = Object.fromEntries(map);
console.log(obj2);

把对象的值翻倍

let obj = {
  x: 1,
  y: 2,
  z: 3
}

let obj2 = Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, value * 2]));
console.log(obj2);

# 8、Object.defineProperty

定义属性描述符:

属性描述符一共有6个:

  • value:设置属性值,默认Undefined
  • writable:属性值是否可写。默认为true
  • enumerable:是否可枚举。是否可以使用for...in或者Object.keys()进行遍历访问。默认为true
  • configurable: 是否可设置属性特性,默认为true,如果设置为false,将无法删除该属性,不能修改属性的值,也不能修改属性的属性描述符
  • get: 取值函数,默认Undefined
  • set: 存值函数,默认Undefined

有些属性不能一起出现,出现value或者wriable,就不能出现get或者set,反之一样

let obj = {
  name: "张三",
  age: 18,
  score: 90,
  _type:"admin"
}

Object.defineProperty(obj, "sex", {
  value: "男",
  writable: false,
  enumerable: false,
  configurable: true
})
obj.sex = "女";

console.log(obj.sex)

Object.defineProperty(obj, "tel", {
  get(val) { 
    console.log("get---->" + val)
  },
  set(val) {
    console.log("set---->" + val)
  }
})

Object.defineProperty(obj, "type", {
  get() { 
    console.log("get---->" + this._type)
    return this._type
  },
  set(val) {
    console.log("set---->" + val)
    this._type = val
  }
})

// get set其实就是参考的后端语言的写法
// class Admin { 
//   private int id;

//   public void setId(int id) {
//     this.id = id;
//   }
//   public int getId() {
//     return this.id;
//   }
// }

console.log(obj.type)
obj.type = "user"
console.log(obj.type)

# 9、Object.defineProperies

给对象添加或者修改多个属性描述符

let obj = {
  name: "张三",
  age: 18,
  score: 90,
}

Object.defineProperties(obj, {
  tel: {
    value: "123456",
    writable: true,
    enumerable: true,
    configurable: true
  },
  sex: {
    value: "男",
    writable: true,
    enumerable: true,
    configurable: true,
  }
})

console.log(obj)

# Promise

具体见代码...

Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式