Web前端开发面试题整理

导语

研究面试题是一件很有意义的事情,面试题里很多的知识点或许并不完全在工作中会用到,但是经常会涉及到一些底层知识,这些知识如果可以理解领悟的话,对程序设计还是会有不少的帮助的,这里就把之前见到的一些面试问题整理一下放在这里,然后我也可以一边分享一边学习。 如果有小伙伴发现有哪些问题我的答案有误,欢迎指正

ES6 相关

ES6 的新特性有哪些

const 和 let 变量声明

在 ES6 之前,变量声明都是采用的var,而var有一个特型叫做作用域提升,就是你无论在哪里声明这个变量,在运行时都会把这个声明提升到代码最前面执行,然后这样就会导致逻辑的混乱,而letconst解决了这个问题,同时这两者都是块级作用域,只会在一个花括号{}内生效,可以很好的避免作用域混乱产生逻辑错误。 一般情况下,使用let表示变量,用const表示常量。

var a = 1;
b = 2;
console.log(b); // 输出 2
function test(){
	let c = 3;
	const d = 4;
	console.log(a); // 输出 1
	console.log(b); // 输出 0,这里因为是在下面的 var b = 0 后面执行,所以 b 被赋值 0
	console.log(c); // 输出 3
	console.log(d); // 输出 4
}
var b = 0;
// 这里看做 var b 和 b = 0,var b 被提升到作用域顶部
// 所以 b 会被先赋值 2 ,在这里再被赋值 0
test()
console.log(a); // 输出 1
console.log(b); // 输出 0
console.log(c); // 报错 c 未定义
console.log(d); // 报错 d 未定义

模板字符串

在 ES6 之前,我们往往这么处理模板字符串:

$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");

里面充满了+"\\,可读性和可维护性就不是很好。 但是在 ES6 之后,我们可以这样来处理模板字符串:

$("body").html(`This demonstrates the output of HTML content to the page, 
including student's ${name}, ${seatNumber}, ${sex} and so on.`);

箭头函数

这个本质就是语法糖了,不用写function,如果是直接返回一个值或者表达式的场合,还可以省略掉return{},而且可以继承当前上下文的this对象,用起来会比较方便。

// ES6 之前
var add = function (a, b) {
    return a + b;
};
// ES6 之后使用箭头函数
var add = (a, b) => a + b;

// ES6 之前
[1,2,3].map((function(x){
    return x + 1;
}).bind(this));
// ES6 之后使用箭头函数
[1,2,3].map(x => x + 1);

函数参数的默认值

在 ES6 之前,函数的参数是不能直接设置默认值的,我们只能通过判断参数是否存在,再对其进行赋值,而 ES6 简化了这一点,可以直接赋初始值了。

// ES6 之前
function who(name) {
    if (!name) {
        name = '张三'
    }
    console.log(name);
}

// ES6 之后
function who(name = '张三') {
    console.log(name);
}

who('李四'); // 输出 李四
who(); // 输出 张三

Vue相关

谈一谈Vue.nextTick()函数

Vue中是异步更新DOM的,Vue.nextTick()是Vue框架提供给我们的DOM更新后的回调函数,当数据变化,DOM更新后,就会执行该函数,常见于需要处理DOM场合,例如。

// 改变数据
vm.message = 'changed'

// 想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'

// 这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
    console.log(vm.$el.textContent) // 可以得到'changed'
})
「技术」打造一个漂亮的Android标题栏

Cover: 仮想都歌姫 - [email protected]

前言

最近因为正在做新坑Pixiu,本身的计划是照着Pivision做一个UI几乎没区别的复刻版,但是苦于已经好久没有正经开发过安卓App了,很多UI自己根本不会做,一个滑动的题图卡了我一天的时间,不过最后总算是连抄带蒙的做出来了,而且实际上也很简单,所以就写一个日志来记录一下。

效果演示

开发过程

整个效果实现需要三部分的工作,分别解决三个问题

  • StatusBar透明化
  • 带图片可滑动的ToolBar
  • 图片延伸到StatusBar

StatusBar透明化

首先需要在style.xml里添加如下的style,名字随便起。

<style name="AppTheme.NoActionBar.TranslucentStatusBar" parent="AppTheme.NoActionBar">
	<item name="android:windowTranslucentStatus" tools:targetApi="KITKAT">true</item>
	<item name="android:statusBarColor" tools:targetApi="lollipop">
		@android:color/transparent
	</item>
</style>

首先需要先继承NoActionBar,否则会带上默认的ActionBar,就没办法实现效果。

其次中间有一个tools:targetApi="KITKAT",这个android:windowTranslucentStatus就是设置是否开启透明StatusBar的参数,这个是从Android4.4,也就是Kitkat开始加入的参数,如果不这么写,在value-19里的style.xml写也是可以,不过我比较偷懒,这么写就不用做很多的value文件夹了。

而另一个参数则是Android5.0后加入的,设置StatusBar颜色的参数,这个把他设置成透明的就可以了。

最后则是把目标的Activity设置上这个新的主题。

<activity
	android:name=".view.MainActivity"
	android:theme="@style/AppTheme.NoActionBar.TranslucentStatusBar" />

这样咱们就获得了一个没有标题栏,而且也是透明的,还可以在里面放控件的Activity了。

带图片可滑动的ToolBar

这一步我玩了一上午,但是只是实现一个这样的简单效果并没有多难,只是我坑踩太多。

打开咱们Activity的布局文件,例如这里是activity_main.xml,把根布局换成CoordinatorLayout,然后在里面塞一个AppBarLayout,AppBarLayout里再塞一个CollapsingToolbarLayout,最后在CollapsingToolbarLayout里再分别塞一个ImageView,和一个ToolBar。最后把咱们的主要的布局放在CoordinatorLayout下面,这里放了个LinearLayout

<androidx.coordinatorlayout.widget.CoordinatorLayout
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<com.google.android.material.appbar.AppBarLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content">

		<com.google.android.material.appbar.CollapsingToolbarLayout
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
			app:titleEnabled="false">

			<ImageView
				android:id="@+id/iv_title/cover"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:scaleType="centerCrop"
				app:layout_collapseMode="pin"/>

			<androidx.appcompat.widget.Toolbar
				android:id="@+id/tb_main"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				app:layout_collapseMode="pin"
				app:title="@string/app_name" />

		</com.google.android.material.appbar.CollapsingToolbarLayout>

	</com.google.android.material.appbar.AppBarLayout>

	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:orientation="vertical"
		app:layout_behavior="@string/appbar_scrolling_view_behavior">

	</LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

这里解释一下各个细节,首先是这种滑动变化的布局,必须在CoordinatorLayout里进行,所以说根布局要换成这个,不过如果这个只是页面UI一部分的话,外面也是可以再套其他布局的。

因为要做有多种元素还能滚动的复杂TitleBar,所以要用AppBarLayout包裹ToolBar,这样才能配合CoordinatorLayout实现滚动。

CollapsingToolbarLayout则是相当于一个高级的ToolBar,需要包裹ToolBar才能实现效果。参数中的app:layout_scrollFlags代表了这个控件的可滑动状态。这个参数一共有五种,可以共存,分别是。

参数 功能
scroll 代表这个控件可以被滚动
enterAlways 代表这个控件会被优先滚动,需要和scroll共用
enterAlwaysCollapsed 代表这个控件会被分段滚动,首先先滚动会显示最小高度,到达滚动边界后再完全显示,需要和enterAlways共用
exitUntilCollapsed 代表这个控件会被保留最小高度,不会完全滚动出窗口,需要和scroll共用
snap 代表这个控件滚动时会有边缘吸附效果,需要和scroll共用

因为咱们的控件属于保留最小高度,同时有吸附效果,所以最后需要的是scroll|exitUntilCollapsed|snap

至于app:titleEnabled则是代表是否使用系统自带的标题,这里因为要用ToolBar,所以关了就好。

接下来是ToolBar中的,app:layout_collapseMode,这个参数是开启了滚动后才会生效的效果,这个有三种参数。

参数 功能
none 代表这个控件会被滚出屏幕
pin 代表这个控件不会被滚出屏幕
parallax 代表这个控件滚动时会有滚动差

最后是LinearLayout中的app:layout_behavior,behavior就是在CoordinatorLayout中负责处理各种效果的工具,这里使用了一个系统默认的参数@string/appbar_scrolling_view_behavior,效果为跟随appbar滚动,所以这个控件就相当于咱们整个窗口中的根布局了,其他控件放这里就好。

这样咱们就有了一个具有带图片可滑动的ToolBar,而且StatusBar也是透明的Activity了。

图片延伸到StatusBar

这一步卡的时间最久,因为我改了好多布局的方案,最后图片总是不能好好地显示到StatusBar上,要么是不显示,要么是UI错位,最后发现最简单的办法就是给ToolBar设置一个顶部padding,大小为StatusBar的高度,就可以完美实现了。

ToolBar tb_main = findViewById(R.id.tb_main);
ViewGroup.LayoutParams toolbarParams = tb_main.getLayoutParams();
ViewGroup.MarginLayoutParams toolbarMarginParams;
if (toolbarParams instanceof ViewGroup.MarginLayoutParams) {
	toolbarMarginParams = (ViewGroup.MarginLayoutParams) toolbarParams;
} else {
	toolbarMarginParams = new ViewGroup.MarginLayoutParams(toolbarParams);
}
toolbarMarginParams.setMargins(0, ViewSize.getStatusBarHeight(this), 0, 0);
tb_main.setLayoutParams(toolbarMarginParams);

里面的ViewSize.getStatusBarHeight(Activity activity)如下。

public static int getStatusBarHeight(Activity activity) {
	int statusBarHeight = 0;
	int resourceId_status = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
	if (resourceId_status > 0) {
		statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId_status);
	}
	return statusBarHeight;
}

这样就可以给ToolBar设置上padding了,我们的布局也终于正常了。

结语

于是我终于可以开发其他模块了233。

部署一个Django的项目

准备

首先准备好最新版本的Python

rm -rf /usr/local/python3
./configure --prefix=/usr/local/python3/
make && make install
rm -rf /usr/local/bin/python3
ln -s /usr/local/python3/bin/python3 /usr/local/bin/python3
rm -rf /usr/local/bin/pip3
ln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3

因为我用的是宝塔环境,宝塔自带了Nginx和Supervisor,所以不用单独安装了

然后把代码上传到opt目录,我的上传到了YojigenAPI目录下

这里我是使用了PyCharm的自带Deployment功能进行代码上传

首先打开PyCharm的菜单栏的Tools –> Deployment –> Configure

在里面添加一个新的服务器,我添加的服务器命名为AliyunHK,输入好服务器的登陆信息

然后到Mappings页面,添加一下文件夹对应的路径配置,我这里是将代码放到了服务器的/opt/YojigenAPI目录下

最后到Excluded Paths页面,添加一个Local Path,忽略掉本地的venv虚拟环境文件夹

最后点击PyCharm的菜单栏的Tools –> Deployment –> Upload to AliyunHK,就可以将代码推送到服务器上了

环境配置

ssh登陆到服务器,来到项目目录下,先创建一个虚拟环境

virtualenv -p /usr/local/bin/python3 --no-site-packages venv

然后进入到虚拟环境里

source venv/bin/activate

之后恢复pip的包

pip install -r requirements.txt

注意这里安装了gunicorn,默认的Django项目是不会安装的

包都装完了,就可以退出虚拟环境了

deactivate

服务配置

先测试一下服务启动命令,10000是监听端口

venv/bin/gunicorn YojigenAPI.wsgi -b 0.0.0.0:10000

结果发现报错了,The SECRET_KEY setting must not be empty.,这个错误是项目配置文件找不到了,实际上是因为需要设置一下配置文件的环境变量才行,所以只要运行

venv/bin/gunicorn YojigenAPI.wsgi -b 0.0.0.0:10000 -e DJANGO_SETTINGS_MODULE="YojigenAPI.settings.prod"

之后再启动,如果没错那就没问题了

然而我又报错了,SQLite 3.8.3 or later is required (found 3.7.17).,这个问题是因为我用的是CentOS,系统自带的sqlite3版本太老,升级一下就可以了

./configure --prefix=/usr/local
make & make install

编译好了之后,执行一下这个,把/usr/local/lib添加到系统的链接库中

echo "/usr/local/lib" >> /etc/ld.so.conf
/sbin/ldconfig

之后再运行应该就可以正常启动了,然后配置更新一下数据库,以及转存一下静态文件

venv/bin/python manage.py migrate --settings=YojigenAPI.settings.prod
venv/bin/python manage.py createsuperuser --settings=YojigenAPI.settings.prod
venv/bin/python manage.py collectstatic --settings=YojigenAPI.settings.prod

接下来就是配置Supervisor守护进程了,因为我用了宝塔,宝塔里面有现成的Supervisor管理器,这里就直接使用宝塔的管理器了

填写进去启动信息,保存即可

最后就是大家都熟悉的Nginx配置反向代理了,反向代理到对应的域名上就可以了,不过这里有个坑,是关于静态文件的,宝塔默认的配置里包含了css和js的相关配置,要去给他删了才行

location /
{
	proxy_pass http://127.0.0.1:10000;
	proxy_set_header Host api.yojigen.tech;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header REMOTE-HOST $remote_addr;
	add_header X-Cache $upstream_cache_status;
}

location /static/ 
{
	alias /opt/YojigenAPI/static/;
}

到此配置全部完成,代码成功上线啦。

关于Pixiv第三方客户端无法登陆问题
现在不能文中所述的固定值了,必须要计算真实时间

昨日听闻Pixiv的API接口升级,大部分第三方的APP都无法登陆了,之后也是一直在找解决方案,最后在Mikusa的博客里面发现了这个 Iuuses ,并在里面找到了解决方案。

解决方案

Pixiv这次接口更新主要是在Header中加入了两个字段。 一个是X-Client-Time,内容为RFC3339格式的时间戳,类似

3000-01-01T00:00:00+00:00

另一个字段是X-Client-Hash,内容为X-Client-Time的内容加上

28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c

之后再进行MD5编码

众所周知MD5是校验编码,无法还原为原始数据,所以因此可以推测服务器没有办法判断当前的数据是否合法,因此只要写死进客户端里一个规则合法的值就可以了。

最后只要在请求的Header中添加

x-client-time: 3000-01-01T00:00:00+00:00
x-client-hash: 93771864335ef0c8e52db10be563eab3

就可以解决问题~

AndroidStudio推荐使用的.gitignore文件配置
现在推荐直接用IDE的.ignore插件了,非常简单好用
### Gradle template
.gradle
/build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Cache of project
.gradletasknamecache

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn.  Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

### Unity template
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Mm]emoryCaptures/

# Never ignore Asset meta data
!/[Aa]ssets/**/*.meta

# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*

# Autogenerated Jetbrains Rider plugin
[Aa]ssets/Plugins/Editor/JetBrains*

# Visual Studio cache directory
.vs/

# Gradle cache directory
.gradle/

# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db

# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta

# Unity3D generated file on crash reports
sysinfo.txt

# Builds
*.apk
*.unitypackage

# Crashlytics generated file
crashlytics-build.properties


### Android template
# Built application files
*.apk
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_summary.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/

.idea/.name
app/libs/
sdk/libs/
sdk/src/main/java/
sdk/src/main/res/