如图,在准备创建aidl文件时选项置灰
仔细看其实就是配置文件的问题,在build.gradle(:app)下的配置添加
1 | gradle复制代码 buildFeatures { |
完整配置如下:
1 | gradle复制代码plugins { |
本文转载自: 掘金
开发者博客 – 科技是第一生产力
如图,在准备创建aidl文件时选项置灰
仔细看其实就是配置文件的问题,在build.gradle(:app)下的配置添加
1 | gradle复制代码 buildFeatures { |
完整配置如下:
1 | gradle复制代码plugins { |
本文转载自: 掘金
你是否曾瞥一眼手机屏幕, 发现某个应用的图标看起来焕然一新, 与众不同? 这不仅仅是视觉上的炫耀, 而是动态应用图标的魔力在发挥作用. 这项迷人的功能允许 Android 应用动态更改图标, 而无需从 Play Store 进行更新! 这是一种微妙而强大的策略, 可以吸引用户并为你的应用增添活力. 虽然这看似高深莫测, 但掌握动态应用图标绝对不是难事. 让我们一起踏上学习之旅吧!
如果你是 Android 开发的新手, 或者即使你已经在游戏中摸爬滚打多年, 你可能还没有探索过动态应用图标这个有趣的世界. 不过不用担心! 本指南旨在揭开这一过程的神秘面纱, 提供在 Android 中创建动态应用图标的逐步过程. 无论你是刚刚开始编码冒险, 还是希望为自己的工具包添加另一项技能, 本篇文章都将指导你以独特的方式增强应用的用户体验.
试想一下, 你的应用的图标会随着节日的到来而改变, 会根据用户偏好的主题进行调整, 或者会显示出新的功能. 这不仅仅是为了美观, 而是为了在应用和用户之间创建动态互动. 让我们深入了解如何实现这一点.
在跳转到代码之前, 让我们先澄清一些事情, 以确保每个人都站在同一起跑线上. 在 Android 的世界里, 图标代表着应用的大门. 它是用户与之交互的第一件事, 因此让它充满活力可以显著提升用户体验.
在本节中, 我们将通过实现动态更改应用图标的逻辑, 深入了解 Android 中动态应用图标的核心. 我们将逐步介绍代码, 解释每个部分的作用.
AndroidManifest.xml
中设置我们需要在 AndroidManifest.xml
文件中处理一个重要步骤. 如果你想让你的应用通过动态图标变化大显身手, 你必须为你的主Activity设置一个activity-alias
.
activity-alias
在 Android 中, 你可以为清单文件中的主Activity创建activity-alias, 以启用动态应用图标更改. 这些别名允许你将不同的图标与同一个主Activity关联起来, 你可以通过编程启用或禁用这些别名来动态更改应用的图标.
可以将其视为主应用图标的诱饵或替身. 它允许你的应用在同一名称下的不同图标之间切换.
以下是如何在 AndroidManifest.xml
文件中定义activity-alias
的示例:
1 | ini复制代码<application |
在此示例中, .MainActivityAlias
是别名的名称, .MainActivity
是别名指向的目标Activity. <intent-filter>
就像是在告诉你的应用: “嘿, 这个别名也是启动应用的一种方式!”
请记住, 将代码中的 MainActivityAlias
替换为你在 AndroidManifest.xml
文件中为activity-alias
命名的实际名称. 这样可以确保在动态图标更改时, 代码和配置保持一致.
现在, 要在 Android 中创建动态应用图标, 请按照以下步骤操作:
我们先创建一个 BroadcastReceiver
, 它将监听特定的广播事件, 并负责图标切换. 让我们创建一个新的 IconChangeReceiver
类:
1 | kotlin复制代码class IconChangeReceiver : BroadcastReceiver() { |
在这段代码中, IconChangeReceiver
将充当勤勉的监听器, 同时热切地等待信号com.example.app.ACTION_CHANGE_ICON
. 当它听到这个信号时, 就会调用 changeAppIcon
- 这是你切换图标的提示.
在 AndroidManifest.xml
文件中, 确保使用意图过滤器注册 IconChangeReceiver
, 以监听自定义操作:
1 | ini复制代码<receiver |
在IconChangeReceiver
的onReceive
方法中, 实现动态更改应用图标的逻辑. 你可以使用条件语句, 根据特定条件选择不同的图标. 下面是一个简化示例:
1 | kotlin复制代码class IconChangeReceiver : BroadcastReceiver() { |
在这里, 我们首先要检查上下文(应用的当前状态)是否为空. 我们需要上下文, 因为它能让我们访问特定于应用的资源和类.
然后, 我们根据特定条件决定显示哪个图标. 这就是你可以发挥创意的地方. 例如, 你可以根据一天中的时间, 节假日或应用中的特殊事件来更改图标.
最后, 有了选定的图标资源, 我们就可以开始关键的一步: 更改应用图标. 我们使用 PackageManager
启用代表新图标的activity-alias
, 并禁用当前的activity-alias
. 这就好比告诉 Android: “嘿, 别用这个图标了, 改用这个吧”.
我们通过在主Activity及其别名上调用 setComponentEnabledSetting
来实现这一点. 使用新图标启用别名并禁用当前图标, 可以有效地切换图标.
要真正触发图标更改, 我们要发送一个广播, 其中包含我们的 IconChangeReceiver
正在监听的特定操作. 具体方法是创建一个包含动作 com.example.app.ACTION_CHANGE_ICON
的 Intent
并在上下文中调用 sendBroadcast
. 通常是这样做的:
如果你是在 Activity
或其他持有 Context
的组件中调用它, 你可以使用:
1 | scss复制代码val iconChangeIntent = Intent("com.example.app.ACTION_CHANGE_ICON") |
而如果你在应用的另一部分, 并拥有对 Context
的引用, 你可以使用:
1 | ini复制代码val iconChangeIntent = Intent("com.example.app.ACTION_CHANGE_ICON") |
请记住, 我们的 IconChangeReceiver
将接收广播, 然后根据我们设置的逻辑更改应用的图标.
在不断发展的 Android 应用开发世界中, 如何让你的应用脱颖而出, 关键在于如何吸引用户并加入一些个人特色. 动态应用图标可以让你获得身临其境的全新用户体验. 它们能让你的应用图标随时演变, 无需更新或重新安装. 你可以尽情发挥: 庆祝特殊时刻, 切换主题或展示新功能, 一切尽在动态变化之中!
本文转载自: 掘金
欢迎关注微信公众号:FSA全栈行动 👋
BiliBili: www.bilibili.com/video/BV1mt…
Pigeon
是一个可以帮助我们生成 Flutter
与 原生
的通信代码的工具,我们只需要关注其两侧主要的数据处理逻辑即可,从而提升效率。
Flutter
端对于视频缓存功能主要还是依赖原生端比较成熟的实现方案,如下两个开源库
其功能是:丢给它一个视频链接,它将生成一个具备缓存功能的播放代理链接。
接下来我们一起看看,如何使用 Pigeon
并结合上述两个库来实现视频缓存插件。
Plugin
使用如下命令生成插件项目,这里我指定iOS使用的是 Swift
,安卓使用的是 Kotlin
1 | shell复制代码flutter create --template=plugin --platforms=android,ios -i swift -a kotlin 项目名 |
打开在 ios
目录下的 podspec
文件(这里是 video_cache.podspec
),添加相关的第三方库依赖,比如我这里依赖的是 KTVHTTPCache
。
1 | diff复制代码# |
打开在 android
目录下的 build.gradle
文件,添加
1 | diff复制代码... |
然后在 example/android
目录下的 build.gradle
和 settings.gradle
文件添加如下 maven
,否则会找不到依赖库
1 | diff复制代码// build.gradle |
1 | diff复制代码// settings.gradle |
在 pubspec.yaml
的 dev_dependencies
下添加 pigeon
依赖
1 | yaml复制代码dev_dependencies: |
在 lib
目录外创建一个用来定义通信接口的 dart
文件。
这里我们新建了一个与 lib
目录同级的 pigeons
文件夹,来存放与 Pigeon
相关的文件
1 | shell复制代码. |
cache.dart
就是我用来定义视频缓存功能相关的通信接口的文件。
1 | dart复制代码import 'package:pigeon/pigeon.dart'; |
再执行如下命令,指定根据 cache.dart
来生成相应的繁杂且重要的交互代码。
1 | shell复制代码flutter pub run pigeon --input pigeons/cache.dart |
一定一定,一定要自定义 kotlinOptions
里的 errorClassName
,不然它会给你生成默认的 FlutterError
,单单自己的插件编译可能不会怎样,但是一旦集成的项目里也有用到其它用 Pigeon
生成了 FlutterError
的插件时,就会报如下错误了
1 | shell复制代码Type FlutterError is defined multiple times |
自定义 kotlinOptions
里的 errorClassName
:
1 | dart复制代码@ConfigurePigeon(PigeonOptions( |
进入到 example/ios
目录下,安装依赖
1 | shell复制代码cd example/ios |
使用 Xcode
打开 Runner.xcworkspace
开始编写原生代码
1 | swift复制代码// VideoCachePlugin.swift |
使用 AndroidStudio
打开 example/android
,找到外层的 android
项目开始编写原生代码
1 | kotlin复制代码package com.lxf.video_cache |
上述视频缓存插件已开源,并发布至 GitHub
:github.com/LinXunFeng/…
你可以通过如下步骤集成使用:
在 pubspec.yaml
中添加 video_cache
依赖
1 | yaml复制代码dependencies: |
使用
1 | dart复制代码// 导入 |
然后把转换后的 url
丢给播放器就可以了~
以上就是 Flutter
与原生交互拿到代理 url
的例子,使用的是 @HostApi
,而如果你如果在原生端去调用 Flutter
的 api
,则使用 @FlutterApi
去标注相关抽象类即可,使用方法是差不多的。
需要注意的是,当你使用 Swift
去写插件,且使用了 @FlutterApi
去生成相应的原生代码后编译,可能会遇到这个错误
1 | bash复制代码type 'FlutterError' does not conform to protocol 'Error' |
添加如下拓展即可
1 | swift复制代码// https://github.com/flutter/flutter/issues/136081 |
如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有
iOS
技术,还有Android
,Flutter
,Python
等文章, 可能有你想要了解的技能知识点哦~
本文转载自: 掘金
- github地址:github.com/xxxtai/Arth…
我们在测试环境进行开发调试的时候,会有想要热更新几个文件的需求
例如想要把下面的张三
改成李四
如果只是做了一小部分的修改,就去重新发布的,有点得不偿失(因为发布常常得几分钟甚至十几分钟),这时候我们就可以使用ArthasHotSwap
这个插件帮我们进行热更新,并且操作特别简单
只需要下载该插件,然后修改代码,进行编译(因为需要class文件)
然后使用插件
再到服务器上粘贴就好了!😁
这里需要注意的是,该插件是将修改后的字节码文件上传到了oss中,然后目标服务器再下载下来进行热更新的,如果需要自定义上传的对象存储的话可以去作者github issue中找到方法
我们可以看到修改已经成功了!
本文转载自: 掘金
由于公司有一个业务需求:是要小程序中动态加载卡片内容。由于卡片内容经常变化,并且样式布局也可能改动较大。还有一种情况是卡片内容也可能由服务商提供,代码内容无法把控。出于此背景,我们调研了可能的解决方案,感觉都不能满足现状要求:
1 | tsx复制代码import React from "react"; |
1 | vue复制代码<template> |
+ props: 导出的远程组件的属性;
+ externals: 远程组件的依赖库;
点击访问 GitHub:Import-Remote-Component,👏🏻👏🏻👏🏻 欢迎 Star,欢迎批评指正。
本文转载自: 掘金
今天我们来聊聊Vue中的钩子函数, 例如生命周期的钩子函数,常见的 Vue.js 生命周期钩子包括 beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeDestroy
和 destroyed
。
再是路由的钩子函数以及keep-alive
所谓生命周期就是让函数在页面加载的过程中去自动执行,而这些生命周期钩子函数都是去接受一个回调函数。
那么我们就可以将函数放在这些生命周期钩子函数中,去控制它们什么时候执行。
首先,我们在template
中写入一段代码时,那么Vue就会先去编译这段template
,编译完之后经过一系列操作然后进行一个挂载mount
,然后生命周期就会去执行。
该钩子函数在组件挂载之前被调用,注意: 这个钩子在服务器端渲染期间不会被调用。
该钩子函数在组件挂载完成后被调用,同样,这个钩子在服务器端渲染期间不会被调用。
1 | js复制代码<script setup> |
这段代码的打印顺序是怎么样的呢?
首先,先执行的是全局的console.log
,这个执行是在编译之后执行的,全局函数最优先执行,所以打印hello
。
当模板template
完之后,进行挂载,那么再挂载之前,执行onBeforeMount
,所以打印onBeforeMount
。
再挂载时,执行onMounted
,所以,执行onMounted
。
如果我们想要获得拿到一个DOM结构,那么在哪个生命周期可以拿到呢?
1 | js复制代码<template> |
这里我们通过ref
去获得一个标签的DOM
结构,并且在全局和两个生命周期中都去获取DOM结构el
。
我们发现,只能在挂载完成onMounted
中去获取一个元素的DOM结构。
只有挂载了之后,我们才能去获取元素的DOM
结构,而在全局中和在组件挂载之前我们是并不能去获取DOM结构的。
再拓展一下:如果我们向后端发送一个网络请求,那么这段请求代码放在哪里执行比较合适呢?
其实以上三个地方放哪里都可以,但是我们一般会放在onMounted当中,因为请求到的数据可能会对DOM结构进行操作。
该钩子函数在页面卸载时的时候执行,也就是离开页面的时候。
可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
这个钩子在服务器端渲染期间不会被调用。
该钩子函数在页面卸载之前执行。
当这个钩子被调用时,组件实例依然还保有全部的功能。
这个钩子在服务器端渲染期间不会被调用。
该钩子函数在组件因为响应式状态变更而更新其 DOM 树之后调用。
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行 (考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
1 | js复制代码<template> |
通过ref
将count
变成响应式数据,点击p
标签时,count
值+1。
onUpdated
在响应式状态变更时执行,所以,我们每次将count
值增加,都会打印一遍onUpdated
。
该钩子函数在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。
这个钩子在服务器端渲染期间不会被调用。
用法跟onUpdated
一样。
路由的钩子函数一般写入路由配置文件router/index.js
中,我们也可以写入main.js
中进行配置。
该钩子函数接受三个参数to, from, next
。
to
表示我们想要跳转到的页面
from
表示我们从哪个页面跳转过来
next
表示跳转
1 | js复制代码import { createRouter, createWebHistory } from 'vue-router' |
这里我们先去创建一份路由, 有两个页面, 一个是/
, 一个是/about
。
1 | js复制代码<template> |
这就是我们的页面,当我们点击关于跳到/about
页面时:
第一行打印to
,也就是我们想要去到的地方/about
第二行打印from
,也就是我们来的地方/
。
当用户想要去其他页面时,我们可以使用这个钩子函数去判断用户有没有登录,如果没有登录就送他去登录页面。
并且我们还可以使用此钩子去给页面设置一个标题。
1 | js复制代码router.beforeEach((to, from, next) => { |
这里我们通过一个简单的办法来模拟一下:
如果用户登录了,我们就在本地存储localStorage
当中存入用户信息。
先使用to.path
去判断用户是不是想去非登录页面,如果是的话,就去检查localStorage
当中是否有用户信息。如果没有的话,就不使用next()
进行一个跳转,并弹出一个警告。
如图,注意看url地址栏
,我们这里点击关于页面,那么它本该会跳去/about
但是通过路由守卫判断出我们并没有登录,所以弹出警告,跳转失败。
我们看左上角,发现多了一个标题关于我们
,这是因为我们在路由配置中加入了meta
,去设置一个路由的详细信息。
然后通过document.title = to.meta.title
,这样就实现了一个标题。
但是这样去判断一个用户是否登录是不严谨的,用户可以手动的去localStorage
中设置一个isLogin
为true
的字段。
解析钩子和全局前置区别不大,解析钩子是在路由被解析,代码被编译之前触发。
我们同样可以使用该钩子去处理上面的事情。
它只有两个参数, to
和from
1 | js复制代码router.afterEach((to, from) => { |
该方法表示路由跳转后去干什么事情,它没有next
参数
同样to
和from
表示去哪和从哪来。
上面我们所介绍的三个全局钩子函数, 它们是全局的,我们只要用了这三个钩子中的任意一个,那么它将会影响项目中所有路由的跳转。
接下来我们来看看独享守卫。
独享守卫是什么意思?
在有些场景,我们希望跳去某些页面时,钩子触发,而跳去别的页面时,钩子不触发。
1 | js复制代码import { createRouter, createWebHistory } from 'vue-router' |
比如,我们只想跳到关于页面去触发钩子函数,那么我们只需要在/about
的路由配置中加入beforeEnter
。
同样,to
, from
, next
的作用都是与全局守卫钩子相同。
从/
跳去/about
:
从about
跳去/
:
可以看出,是没有触发钩子函数的。
我们还可以想象一个场景:
假设一个项目有一百个页面,只有一个页面才需要登录后才能查看,那么我们只需要给那个页面单独配置一个独享守卫钩子
, 去进行一个判断是否登录的逻辑。
它可以用来在我们离开时做的一个操作。
比如我们常见的: 当我们跳转一个页面时,浏览器弹出一个: 你确定要离开该页面吗?
1 | js复制代码<template> |
我们将该钩子写在about
组件中,当我们跳转到/
时,该钩子触发:
当我们两个页面共同用到了一个组件时,当它们进行相互跳转时,那么该钩子函数就会触发。
在我们去开发一个Vue项目中的时候,若是两个页面共用到了一个组件,当跳转页面时,这个共用的组件也是会进行再次渲染的。因为跳转路由的时候页面内所有组件都是会重新销毁并加载的。
但是很多组件是没有必要再次渲染的,这样可以减少性能开销。
比如每个页面都有广告,且广告的内容是一样的,如果每次去重新加载该广告组件也是没有必要的。我们为了减少一个性能开销,就可以使用`keep-alive’
keep-alive
的作用就是包裹一个需要去缓存的组件。
首先我们有两个页面,一个首页页面/
,一个关于页面/about
,首页中我们加入onMounted
去检测是否重新加载了这个组件。
我们在App.vue
中使用keep-alive
去缓存首页页面:
1 | js复制代码<template> |
首先,我们进入到首页页面:
可以看到是执行了onMounted
,因为组件是初次加载,加载完之后才会缓存。
然后我们再跳转到关于页面。
然后我们从/about
页面跳到首页/
时,再看打印:
发现我们成功缓存住了首页组件,onMounted
没有触发。
当被缓存的组件生效时触发
当离开缓存的组件时触发
这两个钩子一定根缓存有关, 所以一定要跟keep-alive
一起使用
同样,我们利用上面的代码来举例:
我们在首页Home
组件中加入这两个钩子函数
1 | js复制代码<template> |
当我们进入首页页面, onActivated
触发,因为被缓存的Home
组件生效了
当我们进入关于页面, onDeactivated
触发,因为我们离开了缓存的组件Home
若是在面试的时候,面试官有提到生命周期,那么提到两个跟缓存有关的钩子是十分加分的,因为往往我们在学习前端的过程中容易去忽视掉这两个钩子函数。
写文章不易,如果帮助到了小伙伴们,可以给本文点赞收藏评论三连呀。有不懂的地方欢迎到评论区留言,我会及时回复。
本文转载自: 掘金
我们也经常做平板和大屏项目的时候常常会遇到要开发网站壳子,一般网站前端用的Nodejs之类的框架来交互的,但是与JSP交互的少之又少,他们一般不会前端开发更加重视后端内容,导致一些交互他们无法实现,这时候我就来分享了我对于这块的解决方案了。
WebView 对象允许您将 Web 内容显示为活动布局的一部分,但缺乏成熟浏览器的一些功能。当您需要增强对 UI 和高级配置选项的控制(允许您将网页嵌入到为应用程序专门设计的环境中)时,WebView 非常有用。
这里我着重描述与JS的交互,我们需要写以下代码来实现android方面的交互
android部分
1 | kotlin复制代码class MainActivity : AppCompatActivity(){ |
前端部分
android调用前端:
JS
1 | xml复制代码<script> |
前端调用android
H5
1 | xml复制代码<body> |
JS
1 | javascript复制代码var app=new Vue({ |
android原生与JS交互以上方法均可以正常运行,但是与JSP交互的话,我实际发现的是android方面引用前端方法的话无法引用到,这时候应该怎么处理的呢?
我们可以采用android的webview直接loadURL刷新页面,通过拼接字符串的方式来解决以上问题:
1 | dart复制代码var url="file:///android_asset/android_test/index.html" |
读取url完成以后,页面刷新加载数据,最终JSP前端得到了android主动调用的数据。
android原生和前端交互的有几处需要对应
1 | less复制代码webView.addJavascriptInterface(JsInterface(), "control") |
这里的”control“需要跟前端约定好,所以前端调用的时候 也必须在这里用control
1 | css复制代码javascript:control.showToast(value); |
关于方法方面android部分@JavascriptInterface 下面的方法也必须与前端部分的要调用的方法相一致
1 | less复制代码@JavascriptInterface |
和
1 | css复制代码javascript:control.showToast(value); |
以上代码里面的showToast必须一致
本文转载自: 掘金
公司使用uniapp开发的app,后期可能要对接银行的一些服务,可能会用到U盾之类的,这种没做过也不太懂,其次就是对接大华摄像头视频播放也要用到SDK之类的,设备初始化之类的。就先研究下怎么生成可供uniapp调用的原生插件了。
2. 安装java环境,这个很简单,下载JDK,我装的是jdk11,安装完成配置下环境变量即可。
3. Android Studio 下载安装
2. 创建名称为MyDemoPlugin
的测试项目,按照下面的配置就行。
新建完成后,项目如下图:
3. 接下来我们就要创建名为testPlugin
插件模块,依次点击File>New>New Module。
4.创建完成,我们的项目里就多了个testPlugin的文件夹:
5.下面就要把上面第一步下载的SDK中的UniPlugin-Hello-AS下的app>libs下的文件复制到自己项目的app>libs
6. 然后就是修改我们的testPlugin
中的build.gradle
文件里的配置信息,选中文件,双击打开,将dependencies下默认生成的依赖注释掉,添加uni-app所需库依赖。最后一项是引入我们上面复制过来的libs文件目录。
1 | php复制代码compileOnly 'androidx.recyclerview:recyclerview:1.0.0' |
7.配置完保存,点击文件上方的立刻同步:
8. 上面基本配置完了,剩下的就是写个测试代码,在我们所创建的testPlugin下面,下图所示位置右键创建个TestModule类:
1 | scala复制代码package com.test.testplugin; |
根据自身项目修改后:
1 | json复制代码{ |
10. 至此,就可以打包插件了。菜单build->make moudule,下图所示:
打包需要等一会,完成后会生成如下文件,arr目录中的就是打包后的插件:
这样java开发的demo插件就算完成了,剩下的就是uniapp调用的问题了。算是完成了第一步吧,过程有点繁琐。
本文转载自: 掘金
当 SwiftUI 需要显示日期时,可以有多种选择,下面总结一些常见的使用方式。
比较常见的方式是通过日期选择器选择某个日期后显示。代码如下:
1 | swift复制代码import SwiftUI |
效果如下:
借助于DateFormatter
,首先格式化成需要的日期格式,然后显示。代码如下:
1 | swift复制代码import SwiftUI |
效果如下:
SwiftUI 1.0 时 Text 就可以显示日期字符串,而且可以同时使用DateFormatter
。代码如下:
1 | vbnet复制代码import SwiftUI |
效果如下:
SwiftUI 2.0 之后,Text 可以直接显示日期,而且支持多种不同的形式。代码如下:
1 | swift复制代码import SwiftUI |
效果如下:
WWDC21 推出了获取当前日期与格式化日期的新方法,因此 SwiftUI 3.0 之后显示日期更加方便。代码如下:
1 | swift复制代码import SwiftUI |
效果如下:
本文转载自: 掘金
我们执行代码的过程,在 JavaScript 引擎的眼里可以分为两个重要的步骤,分别是预编译和执行。
预编译阶段会处理一些语法解析、变量声明提升等工作,为后续的代码执行做好准备;在预编辑完成后代码才开始执行。
我会用底层逻辑详细讲解代码执行的过程需要经历的过程。并且解答:
我们在开始了解什么是预编译和预编译要经历的过程之前,我们需要先了解函数的自带属性、作用域和作用域连。
在JavaScript中,函数有一些自带的属性,一下是一些常见的属性:
length
:表示函数的参数个数。prototype
:指向函数的原型对象,原型对象用于定义构造函数的公共属性和方法。name
:函数的名称。arguments
:函数调用时传递的参数数组。除了这些常见的属性外,函数还存在隐式属性,其中就包括[[scope]]
属性。[[scope]]
是 JavaScript 中函数的一个隐式属性,其中scope
翻译为域或范围。[[scope]]
属性仅供 JavaScript 引擎使用,我们无法直接访问。
在函数定义时,系统会通过scope
的内部原理定期去调用它,但不会让用户去用。当函数执行时,系统会创建一个执行期上下文的内部对象,此时[[scope]]
的值会发生变化。在函数内部访问变量时,实际访问的就是变量的scope
(作用域),scope
里有作用域链,系统会从作用域链底端依次向下去找变量。
我们用一个例子深入了解一下。
1 | javascript复制代码function a() { |
这段代码会输出什么呢?
让我们通过这段代码一起跟着JavaScript 引擎进入底层世界。
该代码大致流程:首先在全局预编译代码,然后全局执行,然后调用函数a;停止全局执行,开始预编译函数体a,预编译结束后执行函数a,最后调用函数b;停止执行函数a,预编译函数b,预编译完成后执行b;执行完b函数后返回执行a函数,a函数执行完返回全局,然后结束。
1. 创建全局上下文对象(Global Object)用于存储全局的有效标识符。
2. 在全局找变量声明,将变量名作为Global Object的属性名,属性值为undefined。
3. 在全局找函数声明,将函数名作为Global Object的属性名,属性值为该函数体。在这几个预编译的步骤中,只会在寻找变量声明和函数声明,其他语句一律跳过。按顺序依次执行完这些步骤后,我们可以得到一个Global Object。
1 | javascript复制代码function a() {} |
在执行到a()
时开始调用函数,这时JavaScript 引擎会停止执行代码而去调用a函数并且对a函数进行预编译再执行。
3. 在函数体a中进行预编译(发生在函数体中):
1. 创建一个函数上下文对象(Activation Object)用于存储函数中的有效标识符。
2. 在函数体里找形参和变量声明,将形参和变量名作为Activation Object的属性名,属性值值为undefined。
3. 形参和实参相互统一。
4. 在函数体内找函数声明,将函数名作为Activation Object的属性名,属性值为该函数体。在这几个预编译的步骤中,需要按顺序依次执行。
进行a步骤:创建一个函数上下文对象(Activation Object)
进行b步骤:在函数体里找形参和变量声明
1 | javascript复制代码Activation Object={ |
进行c步骤:形参和实参相互统一。
1 | javascript复制代码Activation Object={ |
进行d步骤:在函数体内找函数声明
1 | javascript复制代码Activation Object={ |
1 | javascript复制代码function a() { |
当执行到var a = 200
时
1 | css复制代码Activation Object={ |
当执行到b()
时调用b函数,停止执行函数a,对函数体b进行预编译。
1. 创建一个函数上下文对象(Activation Object)用于存储函数中的有效标识符。
2. 在函数体里找形参和变量声明,将形参和变量名作为Activation Object的属性名,属性值值为undefined。
3. 形参和实参相互统一。
4. 在函数体内找函数声明,将函数名作为Activation Object的属性名,属性值为该函数体。你会发现函数预编译的方法是一样的。
我们会得到函数b的函数上下文对象为
1 | javascript复制代码Activation Object={ |
1 | javascript复制代码Activation Object={ |
代码执行完成。
预编译的具体步骤。
变量声明的声明提升和函数声明的整体提升是如何实现的?
根据预编译的流程,JavaScript 引擎找到变量声明和函数声明后会在运行前赋值,分别赋值为undefined和function(函数体),然后再运行。这样就实现了变量声明的声明提升和函数声明的整体提升。
作用域是执行期上下文对象的集合,这种集合呈链式连接,我们把这种链状关系称之为作用域链。
我们通过这个代码进行解释。
1 | javascript复制代码function a() { |
在这个代码中有3个作用域,分别是全局作用域和a.[[scope]]和b.[[scope]]。它们之间的关系是这样的。
这个关系是怎么形成的呢?
通过这些步骤就可以理解作用域链是什么,怎么形成的。
为什么外层作用域无法访问内层作用域,内层作用域为什么可以访问外层作用域?
因为在作用域中只能从低位向高位查找,不能从高位找回低位。
我们通过在这张图进行理解。a函数执行阶段通过作用域的0号位查找需要的有效标识符,如果没有找到便通过作用域的1号位继续查找需要的有效标识符。
我们通过预编译的底层逻辑解答了
并且了解了预编译的具体步骤:
最后我们用运行代码结尾吧
1 | javascript复制代码function test(a, b) { |
自己动手试试会输出什么。
本文转载自: 掘金