iPhone连接APPSTORE导致断网现象的分析

*近一个朋友买了个iphone ,其他都很好,但在家里连无线网时一旦打开APPSTORE就会导致整个家里的网无法连上互联网,此时路由器的管理界面也打不开,只有重启路由器才能重新联网。刚开始跟我说这个问题的时候我上网查了一下,估计是路由器问题,他自己也查了,我觉得换个路由器就能解决,也就没在意。后来他告诉我因为怕换了路由器还是解决不了所以一直没换,我才开始对这个问题产生兴趣。

出现此问题的现象是iphone连上无线网,连接手机的APPSTORE或者SIRI就会导致无法联网,此时无法进入路由器管理界面,重启路由器可解决。非手机问题,因为另一部iphone也出现此现象。同时,在电脑上自建无线网用手机连接就不会出现这种问题。网上有一些人也有这种问题,但基本上找不到解决办法。改过DNS,不行。由于自建网络没问题,那么很可能就是路由器本身的问题了。于是现在开始梳理一下大概的流程:

1.手机连上路由器网络,正常。
2.手机打开APPSTORE,系统向DNS发出解析请求,解析出需要连接的IP,但在这步出现问题了,路由器断网了。

由于更换过DNS,所以不是DNS的问题。如果是在连接DNS之前出的问题,那么就是系统将网址发送到路由器之后路由器就出现崩溃无法联网。这种情况有可能是系统发送的代码与路由器固件不兼容,路由器在接收到代码后出现了崩溃。另一种可能是路由器成功向DNS服务器发出了解析请求,DNS返回了正常的代码,路由器固件与此代码不兼容,出现崩溃。

由于只是连接APPSTORE和SIRI会出现这个问题,所以猜测前者的概率大一些,毕竟DNS返回的代码区别应该不大,但APPSTORE和SIRI发送的请求代码和其他软件应该是不一样的。

Android中实现滑动-实现(1)

在了解了前面介绍的基础知识之后,下面就来看看具体的滑动实现,接下来会介绍7种方法,主要是结合《Android群英传》中学习的知识展开。这七种方法分别是:

(1)View绘制时的layout方法

(2)View绘制时根据系统封装好的offSetLeftAndRight以及offSetTopAndBottom接口实现View的布局定位

(3)通过View的布局参数layoutParams来重置View的margin值实现布局重新定位

(4)通过View的scrollTo和scrollBy方法

(5)通过Scroller类实现

(6)通过属性动画实现

(7)通过ViewDragHelper实现

对于滑动的实现,我会分为四个部分讲解,*部分讲述(1)(2)(3)的实现方法,第二部分讲述(4)和(5)的实现,第三部分讲述属性动画方法实现,第四部分也是较复杂的部分讲述(7)的实现,下面我们就开始吧。

1,View滑动的本质
view滑动的本质就是view根据实时的当前位置以及上一次的位置进行不停的重新绘制工作,比如下图:

%title插图%num

我们只需要获取到偏移量之后就可以对view进行重新绘制,也即这里面的offX和offY,而获取方式主要有两种,这两种主要是针对*部分中介绍的两种坐标系计算出来的:

(1)视图坐标系

int x = (int) event.getX();
int y = (int) event.getY();
//方式一,通过视图坐标系计算偏移量
int offX = x – lastX;
int offY = y – lastY;
(2)Android坐标系

int rawX=(int)event.getRawX();
int rawY=(int)event.getRawY();
//方式二,通过Android坐标系计算偏移量
int offRawX=rawX-lastRawX;
int offRawY=rawY-lastRawY;
2,具体实现
//方式一,通过view的layout方法实现确定绘制的坐标
v.layout(offX+v.getLeft(),offY+v.getTop(),v.getRight()+offX,v.getBottom()+offY);
//v.layout(offRawX+v.getLeft(),offRawY+v.getTop(),v.getRight()+offRawX,v.getBottom()+offRawY);
//方式二,通过系统针对偏移量确定view坐标封装的API实现
//v.offsetLeftAndRight(offX);
//v.offsetTopAndBottom(offY);
//方式三,通过设置父布局view在父布局中参数设置实现,也即是通过修改移动目标view的margin值实现
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) v.getLayoutParams();
layoutParams.leftMargin=v.getLeft()+offX;
layoutParams.topMargin=v.getTop()+offY;
v.setLayoutParams(layoutParams);
3,示例代码
MainActivity代码:

package com.hfut.operationscroll;

import android.content.Context;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
* @author why
* @date 2018-8-19 9:15:28
*/
public class MainActivity extends AppCompatActivity {

int lastX = 0;
int lastY = 0;
// int lastRawX=0;
// int lastRawY=0;
private static final String TAG = “MainActivity”;
Button button;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.test_button);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();

// int rawX=(int)event.getRawX();
// int rawY=(int)event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
// lastRawX=rawX;
// lastRawY=rawY;
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
//方式一,通过视图坐标系计算偏移量
int offX = x – lastX;
int offY = y – lastY;
//方式二,通过Android坐标系计算偏移量
//int offRawX=rawX-lastRawX;
//int offRawY=rawY-lastRawY;

//方式一,通过view的layout方法实现确定绘制的坐标
//v.layout(offX+v.getLeft(),offY+v.getTop(),v.getRight()+offX,v.getBottom()+offY);
//v.layout(offRawX+v.getLeft(),offRawY+v.getTop(),v.getRight()+offRawX,v.getBottom()+offRawY);

//方式二,通过系统针对偏移量确定view坐标封装的API实现
//v.offsetLeftAndRight(offX);
//v.offsetTopAndBottom(offY);

//方式三,通过设置父布局view在父布局中参数设置实现,也即是通过修改移动目标view的margin值实现
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) v.getLayoutParams();
layoutParams.leftMargin = v.getLeft() + offX;
layoutParams.topMargin = v.getTop() + offY;
v.setLayoutParams(layoutParams);

//在使用Android坐标系计算偏移量注意重置初始值
//lastRawX=rawX;
//lastRawX=rawY;
break;
default:
break;
}
return true;
}
});
}

}

activity_main.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”.MainActivity”>
<Button
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”拖我移动” />

</LinearLayout>

4,注意事项
(1)在使用*对坐标(Android坐标系)的时候,一定要注意重置上一次的坐标信息

//在使用Android坐标系计算偏移量注意重置初始值
//lastRawX=rawX;
//lastRawX=rawY;
(2)在使用View布局在父布局中的参数设置margin值的时候需要注意两点:

a,参数的类型根据父布局的不同而不同,比如这里的Button父布局是一个LinearLayout:

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) v.getLayoutParams();
b,这种方式如果父布局中添加了一下属性影响到margin的值的时候,*终的效果也会不一样,比如你可以尝试把activity_main.xml文件改成如下试一试:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:gravity=”center”
tools:context=”.MainActivity”>
<Button
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”拖我移动” />

</LinearLayout>
注:欢迎扫码关注
————————————————

Android中实现滑动(上)—-基础知识

Android当中的滑动效果可以有效改善之前的点击,长按等UI体验,今天就来看一看其具体的实现,下面将会分为两个部分展开介绍,*部分介绍一下基础知识,第二部分介绍具体的几种实现方式。

1,Android中的坐标系
1.1 Android坐标系
如下图所示,Android中的坐标系是向右为X轴正方向,向下为Y轴正方向;其中(0,0)坐标对应于手机屏幕的左上角

%title插图%num

1.2 视图坐标系
如下图所示,其中(0,0)代表父布局的左上角(嵌套同理)

%title插图%num

2,触控事件
MotionEvent对于用户与界面的交互来说必不可少,当然随着语音识别等AI技术的成熟,这种依赖会降低,目前我们在做的机器人产品用户与机器人交互主要是通过ASR和TTS以及人脸识别等,界面更多的是展示功能。其中MotionEvent中定义了很多事件常量,主要如下:

MotionEvent.ACTION_DOWN
MotionEvent.ACTION_UP
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_CANCEL
MotionEvent.ACTION_OUTSIDE
MotionEvent.ACTION_POINTER_DOWN
MotionEvent.ACTION_POINTER_UP
MotionEvent.ACTION_BUTTON_PRESS
MotionEvent.ACTION_HOVER_ENTER
MotionEvent.ACTION_HOVER_EXIT
MotionEvent.ACTION_HOVER_MOVE
MotionEvent.ACTION_MASK
MotionEvent.ACTION_POINTER_INDEX_MASK
MotionEvent.ACTION_POINTER_INDEX_SHIFT
MotionEvent.ACTION_BUTTON_RELEAS
等,我们只需要在获取控件的时候设置onTouchEvent(EventMotion event)即可根据event收到的事件类型来做业务处理

3,获取坐标
3.1 通过View
getLeft() //获取View的左边到其父布局左边距离

getTop() //获取View的上边到其父布局上边距离

getRight() //获取View的右到其父布局左边距离

getBottom() //获取View的底边到其父布局上边边距离

3.2 通过MotionEvent
getX() //点击事件位置距离控件自身左边的距离

getY() //点击事件位置距离控件自身上边的距离

getRawX() //点击事件位置距离控件屏幕左边的距离

getRawY() //点击事件位置距离控件屏幕上边的距离

4,示例代码和解释
4.1 代码
MainActivity代码:

package com.hfut.operationscrollpre;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
* @author why
* @date 2018-8-18 15:15:13
*/
public class MainActivity extends AppCompatActivity {

private static final String TAG = “MainActivity”;
Button button;
LinearLayout linearLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.test_button);
linearLayout = findViewById(R.id.test_layout);

// ActionBar actionBar=getSupportActionBar();
// if(actionBar!=null){
// actionBar.hide();
// }

button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_DOWN”);
Log.d(“按钮:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“按钮:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“按钮:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_UP:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_UP”);
Log.d(“按钮:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“按钮:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“按钮:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_MOVE”);
break;
case MotionEvent.ACTION_CANCEL:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_CANCEL”);
break;
case MotionEvent.ACTION_OUTSIDE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_OUTSIDE”);
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_POINTER_DOWN”);
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_POINTER_UP”);
break;
case MotionEvent.ACTION_BUTTON_PRESS:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_BUTTON_PRESS”);
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_BUTTON_RELEASE”);
break;
default:
break;
}
return true;
}
});

linearLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_DOWN”);
Log.d(“父布局:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“父布局:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“父布局:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_UP:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_UP”);
Log.d(“父布局:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“父布局:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“父布局:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_MOVE”);
break;
case MotionEvent.ACTION_CANCEL:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_CANCEL”);
break;
case MotionEvent.ACTION_OUTSIDE:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_OUTSIDE”);
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_POINTER_DOWN”);
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_POINTER_UP”);
break;
default:
break;
}
return true;
}
});
}
}
activity_main.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”com.hfut.operationscrollpre.MainActivity”>

<LinearLayout
android:layout_marginLeft=”100px”
android:id=”@+id/test_layout”
android:orientation=”vertical”
android:gravity=”center”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:text=”我是测试按钮”
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>

</LinearLayout>
4.2 测试结果
(1)点击屏幕

%title插图%num

 

(2)滑动屏幕

%title插图%num

 

(3)点击按钮

%title插图%num

(4)滑动按钮

%title插图%num

分析:

getX() + getLeft()+父布局到屏幕左侧距离=getRawX()

getY()+getTop()+父布局到屏幕上边距离=getRawY()

从日志来看我们的父布局(id为test_layout)到屏幕上边距离是118px,实际上我们并没有设置marginTop属性值,这里我们千万别忘了其父布局上面还有一个存放ActionBar的FrameLayout所占据的空间,还有就是我们平时喜欢使用dp单位来表示margin属性值,而这里获取的坐标单位都是px,所以在测试的时候需要注意,有时候我们设置了margin值为100dp,显示的结果大小很可能不是100px,这之间有一个单位换算。可以使用:

public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
获取,首先获取像素密度,然后在换算成对应的像素值;假如我们把上面的设置改为:

<LinearLayout
android:layout_marginLeft=”100dp”
android:id=”@+id/test_layout”
android:orientation=”vertical”
android:gravity=”center”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:text=”我是测试按钮”
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
再次点击子布局,日志如下:

%title插图%num

再看看MainActivity中打印的日志:

Log.d(TAG, “onCreate: “+dp2px(getApplicationContext(),100));//代码

08-18 10:39:58.096 9114-9114/? D/MainActivity: onCreate: 133 //日志
————————————————

ios如何解除dns被劫持_iOS强制ATS后,DNS劫持问题如何解决?

之前苹果强制app上传AppStore必须支持ATS,截至日期是2017年01月01日,但是由于各种原因,导致deadline延期.具体什么时候苹果会强制ATS,官方暂时还没有给出明确答复.

支持Https后,一般情况下只会给域名添加证书.导致app所有的http请求都会走域名,这样就会有DNS劫持的风险.无论wifi网络下,还是移动网络根据域名都会去DNS服务解析成ip,然后进行访问.由于国内网络环境的原因,都会有DNS劫持的情况,一般会在访问网页的时候,在页面上嵌入一段js代码,甚至有些情况会出现DNS解析失败.尤其是用户达到一定规模,各种网络状况都会出现.

这里用移动设备举例,如果是wifi网络,一般用户是不会修改网络设置的DNS. 很有可能用户网络环境由于种种原因,会造成无法访问http服务.ATS之前的解决方案是可以直接使用ip地址,一般app都会有这样一个逻辑,从服务器获取DNS Config,这里面一般配置了domain,ip,protocol,port等属性,App的请求可以根据DNS Config进行动态调整.但是支持ATS后天,苹果设置必须支持https.这样一旦使用域名的http服务,都会有可能遇到DNS劫持的情况.

由于IP不一定能够长期保持,所以一般不会给ip地址配证书.一旦苹果强制支持ATS,那么就存在DNS劫持的风险.

dig命令查询DNS解析,下面是解析百度的域名

dig www.baidu.com

; <<>> DiG 9.8.3-P1 <<>> www.baidu.com

;; global options: +cmd

;; Got answer:

;; ->>HEADER<

;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 5, ADDITIONAL: 5

;; QUESTION SECTION:

;www.baidu.com.INA

;; ANSWER SECTION:

www.baidu.com.380INCNAMEwww.a.shifen.com.

www.a.shifen.com.134INA119.75.218.70

www.a.shifen.com.134INA119.75.217.109

;; AUTHORITY SECTION:

a.shifen.com.194INNSns3.a.shifen.com.

a.shifen.com.194INNSns5.a.shifen.com.

a.shifen.com.194INNSns4.a.shifen.com.

a.shifen.com.194INNSns2.a.shifen.com.

a.shifen.com.194INNSns1.a.shifen.com.

;; ADDITIONAL SECTION:

ns4.a.shifen.com.49INA115.239.210.176

ns2.a.shifen.com.580INA180.149.133.241

ns5.a.shifen.com.580INA119.75.222.17

ns1.a.shifen.com.580INA61.135.165.224

ns3.a.shifen.com.238INA61.135.162.215

;; Query time: 6 msec

;; SERVER: 172.17.16.3#53(172.17.16.3)

;; WHEN: Tue Feb 21 12:25:06 2017

;; MSG SIZE rcvd: 260

浏览器直接输入这两个ip:119.75.218.70,119.75.217.109,可以直接访问百度.如果前面加上https://119.75.218.70,进行访问,由于没有证书会显示不安全的链接.但是跳过之后还是能够正常访问.我用ios设备测试,底层调用https://ip可以正常访问.各种请求都没有问题.

如果正常网络环境下可以用所有的http服务尽量域名访问,每次启动app时可以向服务器获取dig出来的ip作为备用ip,一旦遇到DNS劫持的情况,虽然没有给ip配证书,但是底层可以使用ip正常访问.

django Paginator分页+Boostrap样式快速生成分页按钮

django Paginator分页+Boostrap样式快速生成分页按钮
django自带分页功能,方便我们快速调用,更多介绍见官方文档
1.导入
from django.core.paginator import Paginator
1
2.属性和方法
p = Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
#构造Paginator对象
#object_list:要分页的对象,可以是列表,元组,或django中的QuerySet等
#per_page:每页*多的文章数
#orphans:当*后一页的文章数小于或等于该值时,这些文章将被自动合并到前一页
#allow_empty_first_page:是否允许*页为空
#属性
p.count #文章总数
p.num_pages  #总分页数
p.page_range #页码范围,返回一个列表,从1开始,如[1,2,3,4]
#方法
page = p.page(number) #返回指定页码的page对象,若不存在则抛出错误
#page对象的属性和方法
Page.object_list  #包含当前页的所有对象列表
Page.number       #当前页的页码,从1开始
Page.has_next()   #是否有下一页,若有返回True
Page.has_previous()  #是否有上一页,若有返回True
Page.has_other_pages()  #是否有下一页或上一页,若有返回True
Page.next_page_number() #返回下一页的页码
Page.previous_page_number() #返回上一页的页码
Page.start_index()  #返回当前页的*个对象在所有对象列表中的序号
Page.end_index()  #返回当前页的*后一个对象在所有对象列表中的序号
3.例子
views.py
def news(request,type):
articles = Article.objects.all().filter(category__name = type).order_by(‘-time’)  #导入的Article模型
p = Paginator(articles,10)   #分页,10篇文章一页
if p.num_pages <= 1:  #如果文章不足一页
article_list = articles  #直接返回所有文章
data = ”  #不需要分页按钮
else:
page = int(request.GET.get(‘page’,1))  #获取请求的文章页码,默认为*页
article_list = p.page(page) #返回指定页码的页面
left = []  # 当前页左边连续的页码号,初始值为空
right = []  # 当前页右边连续的页码号,初始值为空
left_has_more = False  # 标示第 1 页页码后是否需要显示省略号
right_has_more = False  # 标示*后一页页码前是否需要显示省略号
first = False   # 标示是否需要显示第 1 页的页码号。
        # 因为如果当前页左边的连续页码号中已经含有第 1 页的页码号,此时就无需再显示第 1 页的页码号,
        # 其它情况下*页的页码是始终需要显示的。
        # 初始值为 False
last = False  # 标示是否需要显示*后一页的页码号。
total_pages = p.num_pages
page_range = p.page_range
if page == 1:  #如果请求第1页
right = page_range[page:page+2]  #获取右边连续号码页
if right[-1] < total_pages – 1:    # 如果*右边的页码号比*后一页的页码号减去 1 还要小,
            # 说明*右边的页码号和*后一页的页码号之间还有其它页码,因此需要显示省略号,通过 right_has_more 来指示。
right_has_more = True
if right[-1] < total_pages:   # 如果*右边的页码号比*后一页的页码号小,说明当前页右边的连续页码号中不包含*后一页的页码
            # 所以需要显示*后一页的页码号,通过 last 来指示
last = True
elif page == total_pages:  #如果请求*后一页
left = page_range[(page-3) if (page-3) > 0 else 0:page-1]  #获取左边连续号码页
if left[0] > 2:
left_has_more = True  #如果*左边的号码比2还要大,说明其与*页之间还有其他页码,因此需要显示省略号,通过 left_has_more 来指示
if left[0] > 1: #如果*左边的页码比1要大,则要显示*页,否则*页已经被包含在其中
first = True
else:  #如果请求的页码既不是*页也不是*后一页
left = page_range[(page-3) if (page-3) > 0 else 0:page-1]   #获取左边连续号码页
right = page_range[page:page+2] #获取右边连续号码页
if left[0] > 2:
left_has_more = True
if left[0] > 1:
first = True
if right[-1] < total_pages – 1:
right_has_more = True
if right[-1] < total_pages:
last = True
data = {    #将数据包含在data字典中
‘left’:left,
‘right’:right,
‘left_has_more’:left_has_more,
‘right_has_more’:right_has_more,
‘first’:first,
‘last’:last,
‘total_pages’:total_pages,
‘page’:page
}
return render(request,’news.html’,context={
‘article_list’:article_list,’data’:data
})
html
{% if data %}
<ul id=”pages” class=”pagination pagination-sm pagination-xs”>
{% if data.first %}
<li><a href=”?page=1″>1</a></li>
{% endif %}
{% if data.left %}
{% if data.left_has_more %}
<li><span>…</span></li>
{% endif %}
{% for i in data.left %}
<li><a href=”?page={{i}}”>{{i}}</a></li>
{% endfor %}
{% endif %}
<li class=”active”><a href=”?page={{data.page}}”>{{data.page}}</a></li>
{% if data.right %}
{% for i in data.right %}
<li><a href=”?page={{i}}”>{{i}}</a></li>
{% endfor %}
{% if data.right_has_more %}
<li><span>…</span></li>
{% endif %}
{% endif %}
{% if data.last %}
<li><a href=”?page={{data.total_pages}}”>{{data.total_pages}}</a></li>
{% endif %}
</ul>
{% endif %}

Centos 7环境下使用nginx和uwsgi部署多站(*简单的方法)

Centos 7环境下使用nginx和uwsgi部署多站(*简单的方法)
1.nginx
直接在.conf文件中添加多个配置,有几个网站就在http配置文件里面添加几个server
#*个网站
server {
       listen 80;
       server_name www.xxx1.com;  #此处输入你的域名
       charset utf-8;
       location /static {
               alias /home/zjw/website/website/zjw/static;
      }
       location / {
               include uwsgi_params;
               uwsgi_pass 127.0.0.1:8080;  #内部转发的端口要不同
       }
}
#第二个网站
server{
        listen 80;
        server_name www.xxx2.com;  #此处输入你的域名
        charset utf-8;
        location /static {
                alias /home/zjw/office201/zjw-web/web/static;
        }
        location / {
                include uwsgi_params;
                uwsgi_pass 127.0.0.1:9090;  #注意:内部转发的端口要不同
        }
}
注意server name一定要写正确,相关格式可以参考这篇文章server name,当所有server都不匹配时,nginx会采用*条配置。
2.uwsgi
对于每一个网站,都要分别对应一个uwsgi服务,需要各自的配置文件,例如:
站点1:
[uwsgi]
socket =127.0.0.1:8080   #对应nginx配置文件中的转发端口
master = true
processes = 4
vacuum = true
chdir = /home/zjw/website/website
module = website.wsgi
home = /home/zjw/website/v
chmod-socket = 666
chown-socket = root:nginx
enable-threads = true
pidfile = /tmp/pid
站点2:
[uwsgi]
socket =127.0.0.1:9090   #对应nginx配置文件中的转发端口
master = true
processes = 4
vacuum = true
chdir = /home/zjw/office201/zjw-web/web
module = web.wsgi
home = /home/zjw/office201/zjw-web/v
chmod-socket = 666
chown-socket = root:nginx
enable-threads = true
pidfile = /tmp/pid1

HTTP学习笔记6 首部字段类型 通用首部字段

HTTP学习笔记6 首部字段类型 通用首部字段
HTTP首部字段:给浏览器和服务器提供报文主体大小、语言、认证内容等诸多额外的信息
首部字段格式:字段名:字段值1 [,字段值2] [,字段值3] [….]
首部字段类型:
按实际用途划分:通用首部字段、请求首部字段、响应首部字段、实体首部字段
按是否缓存代理的行为划分:端到端首部(End to end Header)、逐跳首部(Hop by hop Header)
端到端首部:此类别的首部将转发给请求或响应的*终目标,经过代理时,也必须保存在由缓存生成的响应中,必须被转发。
逐跳首部:此类别的首部只对单次转发有效,经过缓存或代理时,将会失效。
在HTTP/1.1中的逐跳首部有8个,其余都为端到端首部,这8个为:Connection、Keep-Alive、Proxy-Authorization、Trailer、TE、Transfer-Encoding、Upgrade
(如果要使用逐跳首部,必须提供Connection字段,例如Connection:Keep-Alive)
通用首部字段:
① Cahe-Control:操作缓存
缓存请求指令:
no-cache:强制向服务器验证缓存有效期,无论缓存是否过期
no-store:不使用缓存
max-age=[秒]:接受缓存已保存的*大时间值,若缓存保存已超过该时间,则不会接受缓存(在Http/1.1中,同时存在max-age和Expires首部字段时,会忽略Expires,而在Http/1.0时,会忽略max-age)
max-stale(=[秒]):可接受缓存的*大保存时间,不管是否过期,时间可省略
min-fresh=[秒]:缓存的已保存时间加上该数值若未超过缓存的有效期,则可接受该缓存
no-transform:代理不可更改媒体类型
only-if-cached:只接受缓存资源,不会验证有效期,若无响应则返回504 网关超时状态码
缓存响应请求:
public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。
private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器
no-cache:缓存前需确定有效性
no-store:不使用缓存
no-transform:代理不可更改媒体类型
must-revalidate:若缓存过期后,必须再向源服务器验证有效期,会忽略max-stale指令
proxy-revalidate:与must-revalidate类似,但只限定于代理服务器
max-age=[秒]:将资源作为缓存保存的*长时间,在此期间不再确认有效期
s-maxage=[秒]:与max-age类似,但只适用于公共的缓存服务器(会忽略max-age以及Expires)
示例:Cache-Control:private,max-age=0,no-cache
② Connection:控制逐跳首部、管理持久连接
关闭持久连接:Connection:close
在旧版本http协议上维持持久连接:
Connection:keep-alive
Keep-Alive:timeout=10,max=500
1
2
③ Date:创建Http报文的日期和时间
④ Pragma:为兼容低版本Http协议的缓存字段,常与Cache-Control一起使用
Pragma:no-cache
Cache-Control:no-cache
1
2
⑤ Trailer:应用于Http/1.1分块传输编码时,说明报文主体后记录了哪些首部字段
Transfer-Encoding:chunked
Trailer:Expires
1
2
⑥ Transfer-Encoding:传输时采用的编码方式,Http/1.1仅对分块编码有效
⑦ Upgrade:使客户端或邻接服务器升级协议版本
Upgrade:TLS/1.0
Connection:upgrade
1
2
⑧ Via:追踪客户端与服务器之间的请求和响应报文的传输路径,常与Trace方法连用,详见本博客代理那一节笔记
⑨ Warning:告知用户一些关于缓存的警告信息
格式 warning:[警告码] [警告主机:端口号] [“警告内容”] (日期)
例如:
110 response is stale 代理返回的资源已过期
111 revalidation failed 验证资源失败
113 heuristic expiration 响应有效期大于24小时,试探性过期
214 transformation applied 媒体类型被代理改变

关于Python的随机数模块,你必须要掌握!

关于Python的随机数模块,你必须要掌握!
所谓七夕
昨天的文章这个七夕节,用Python为女友绘制一张爱心照片墙吧!收获了*近以来*高的浏览量,没枉费我熬到夜里3点赶出来的热点文章。有付出就总会有所回报,只是看这天来的早晚而已。七夕一个人看这网易的数据分析直播体验课程,偶尔刷刷朋友圈,看看各种秀…有人说,经得起寂寞,耐得住孤独,能安心踏实去做一件事,才有有所建树。努力吧更为朋友们!
常用模块
面对现在各种的python3天入门、21天速成,等等的教程与素材,让很多人对python的基础知识,掌握的很薄弱。包括我身边的朋友,已经开始Django、Flask的web开发了,甚至对文件遍历还不慎了解。昨天在做照片墙的时候,用到了random模块,大家可能觉得,这个模块有啥说的,无非就是随机数么,但随机的方式却有很多,今天就跟大家总结下random这个常用的模块
random函数总结
random作为python自带的模块,无需下载即可直接使用。**import random**导入该模块。
random
random.random()会生成一个[0,1)之间的随机数,如:0.21639729286525555。
randint
random.randint(start,end)随机生成一个范围内的整数。
random.randint(1,100) >>> 62
uniform
random.uniform(start,end)随机生成一个范围内的浮点数,起始与终止区间可以为小数
random.uniform(3.5,9.6) >>> 8.233366765359236
可迭代对象
python中万物皆对象,那么什么属于可迭代的对象呢?我们来举几个例子
list_a = [1, 2, 3]
dict_b = {“a”:1,“b”:2}
string_c = “abc”
char_d = ‘A’
int_e = 123
float_f =10.5
boolen_g = True
对于前两个,大家肯定知道是可迭代对象,但从第三个开始有些人就迷了…尤其**char_d = ‘A’**这个,很多人都会觉得是不可迭代的,但其实不然,python中没有所谓的char和string的区别,只有字符串所以string_c和char_d都是可迭代的对象,如何验证?
from collections.abc import Iterable
char_c = ‘A’
print(isinstance(char_c, Iterable))
>>> True
1
2
3
4
5
这里看到一点曾记否,我们引入Iterable使用的是**from collections import Iterable**什么时候出来了个abc?如果我们还是用老的方式导入,会给出提示:
DeprecationWarning: Using or importing the ABCs from ‘collections’ instead of from ‘collections.abc’ is deprecated, and in 3.8 it will stop working from collections import Iterable
所以有时候拥抱变化,也很重要!那么为什么突然插出一个Iterable的讲解呢?下面几个方法会用到…
choice & sample
刚才介绍可迭代对象就是为了讲解这两个random中使用*为普遍的函数。choice和sample之所以一起讲,是为了将二者对比记忆。
不管是random.choice还是random.sample,他们跟的必须是可迭代的对象。
choice我们可以理解为单选,而sample我们可以理解为自定义多选。举个栗子:
random.choice(‘abc’)
>>> ‘b’
random.choice([1,2,3,4,5])
>>> 2
random.sample(‘abc’,2)
>>> [‘b’, ‘c’]
random.sample([1,2,3,4,5])
>>> [3, 5, 4]
# 但我们不可以这样:
random.choice(5)
random.sample(10,1)
1
2
3
4
5
6
7
8
9
10
11
choice不容易出错,但sample大家需要注意:
sample既然是自定义多选,那么我们首先需要定义我们选择几个数值
sample在选择是,自定义的数值,不能大于可迭代对象的*大长度
sample选择后,返回列表类型,且列表为随机数。
shuffle
random.shuffle()这里需要注意,他只能针对list类型的数据,进行重新排序,这点一定要牢记,避免报错
list_a = [‘a’,’b’,’c’,’d’,’e’]
random.shuffle(list_a)
print(list_a)
>>> [‘b’, ‘e’, ‘c’, ‘a’, ‘d’]
1
2
3
4

红米笔记本 air 使用 Linux ,启动时内核会卡住 30 秒,求解决方案或思路

新买了台红米 air 笔记本,cpu 是 i7-10510Y,希望可以日常使用 Linux,尝试了多个发行版基本都能正常使用,唯一的问题就是启动时,当在 GRUB 选择系统页面选择系统后,画面会停留在 GURB 主题背景页面长达 30 秒,然后才能继续开机流程。

尝试了*新的 Ubuntu 系统,也是类似的问题,唯一的区别是 Ubuntu 下是选择系统后电脑黑屏 30 秒然后出现加载信息。

查看 dmesg 信息可以看到如下错误:

[ 0.244480] Simple Boot Flag at 0x44 set to 0x1 │
[ 0.244480] ACPI: bus type PCI registered │
[ 0.244480] acpiphp: ACPI Hot Plug PCI Controller Driver version: 0.5 │
[ 0.244480] PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0xe0000000-0xefffffff] (base 0xe0000000) │
[ 0.244480] PCI: MMCONFIG at [mem 0xe0000000-0xefffffff] reserved in E820 │
[ 0.244480] PCI: Using configuration type 1 for base access │
[ 0.244621] ENERGY_PERF_BIAS: Set to ‘normal’, was ‘performance’ │
[ 0.248307] Kprobes globally optimized │
[ 0.248314] HugeTLB registered 1.00 GiB page size, pre-allocated 0 pages │
[ 0.248314] HugeTLB registered 2.00 MiB page size, pre-allocated 0 pages │
[ 0.248314] ACPI: Added _OSI(Module Device) │
[ 0.248314] ACPI: Added _OSI(Processor Device) │
[ 0.248314] ACPI: Added _OSI(3.0 _SCP Extensions) │
[ 0.248314] ACPI: Added _OSI(Processor Aggregator Device) │
[ 0.248314] ACPI: Added _OSI(Linux-Dell-Video) │
[ 0.248314] ACPI: Added _OSI(Linux-Lenovo-NV-HDMI-Audio) │
[ 0.248314] ACPI: Added _OSI(Linux-HPI-Hybrid-Graphics) │
[ 0.358456] ACPI: 17 ACPI AML tables successfully acquired and loaded │
[ 0.362525] ACPI: EC: EC started │
[ 0.362527] ACPI: EC: interrupt blocked │

[ 30.376923] No Local Variables are initialized for Method [ECMD] │

[ 30.376927] Initialized Arguments for Method [ECMD]: (1 arguments defined for method invocation) │
[ 30.376928] Arg0: 0000000036f30172 Integer 000000000000001A │

[ 30.376940] ACPI Error: Aborting method \_SB.PCI0.LPCB.H_EC.ECMD due to previous error (AE_AML_LOOP_TIMEOUT) (20200925/psparse-531) │
[ 30.376960] fbcon: Taking over console │
[ 30.376972] ACPI Error: Aborting method \_TZ.FNCL due to previous error (AE_AML_LOOP_TIMEOUT) (20200925/psparse-531) │
[ 30.376986] ACPI Error: Aborting method \_TZ.FN00._OFF due to previous error (AE_AML_LOOP_TIMEOUT) (20200925/psparse-531) │
[ 30.376997] ACPI Error: Aborting method \_SB.PCI0.LPCB.H_EC._REG due to previous error (AE_AML_LOOP_TIMEOUT) (20200925/psparse-531) │
[ 30.377037] ACPI: EC: EC_CMD/EC_SC=0x66, EC_DATA=0x62 │
[ 30.377038] ACPI: EC: Boot ECDT EC used to handle transactions │
[ 30.379873] ACPI: [Firmware Bug]: BIOS _OSI(Linux) query ignored │
[ 30.426569] ACPI: Dynamic OEM Table Load: │
[ 30.426598] ACPI: SSDT 0xFFFF89BC00C45800 000507 (v02 PmRef Cpu0Ist 00003000 INTL 20160527) │
[ 30.429939] ACPI: \_PR_.PR00: _OSC native thermal LVT Acked │
[ 30.433359] ACPI: Dynamic OEM Table Load: │
[ 30.433378] ACPI: SSDT 0xFFFF89BC011D2C00 0003FF (v02 PmRef Cpu0Cst 00003001 INTL 20160527) │
[ 30.436652] ACPI: Dynamic OEM Table Load: │
[ 30.436670] ACPI: SSDT 0xFFFF89BC0148D6C0 0000BA (v02 PmRef Cpu0Hwp 00003000 INTL 20160527) │
[ 30.439724] ACPI: Dynamic OEM Table Load: │
[ 30.439742] ACPI: SSDT 0xFFFF89BC00C41800 000628 (v02 PmRef HwpLvt 00003000 INTL 20160527) │
[ 30.443600] ACPI: Dynamic OEM Table Load: │
[ 30.443622] ACPI: SSDT 0xFFFF89BC011C1000 000D14 (v02 PmRef ApIst 00003000 INTL 20160527) │
[ 30.448401] ACPI: Dynamic OEM Table Load: │
[ 30.448419] ACPI: SSDT 0xFFFF89BC011D0000 000317 (v02 PmRef ApHwp 00003000 INTL 20160527) │
[ 30.451771] ACPI: Dynamic OEM Table Load: │
[ 30.451788] ACPI: SSDT 0xFFFF89BC011D2800 00030A (v02 PmRef ApCst 00003000 INTL 20160527) │
[ 30.461796] ACPI: Interpreter enabled
通过传递 acpi=off 参数给内核可以跳过卡住的过程,但是开机后触摸板不可使用,且发热严重,根据日志错误信息查阅大量资料后,基本都是说升级 BIOS 以解决 ACPI 的错误,但是这款笔记本没有 BIOS 更新而且很有可能以后也不会有,所以求助各位大神,有没有什么解决这个问题的方法或者思路?

acpi osi error intl28 条回复 • 2021-07-01 13:29:07 +08:00
feelinglucky 1
feelinglucky 20 小时 1 分钟前
看起来是 ACPI 的问题,试试 BIOS 里面设置 XHCI Handoff 为 Enabled 看看?
debuggerx 2
debuggerx 19 小时 56 分钟前
@feelinglucky 笔记本 BIOS 里什么选项都没有,只有个安全启动开关和 usb 充电选项,也都切换试过了,没用 /(ㄒoㄒ)/~~
generic 3
generic 19 小时 31 分钟前
内核命令行参数 acpi_osi=Linux 试一下?
debuggerx 4
debuggerx 19 小时 20 分钟前
@generic 试过了的,没有效果。。。
kokutou 5
kokutou 19 小时 12 分钟前
试试 archlinux 带的是*新的内核.
iceecream 6
iceecream 19 小时 8 分钟前
只能等 bios 和 EC 更新了
scybhe 7
scybhe 18 小时 22 分钟前 via Android
试试禁用独显?来自 https://bbs.archlinux.org/viewtopic.php?id=237867
shayu*0001 8
shayu*0001 17 小时 7 分钟前
输入 systemd-analyze blame

看看哪个占用了*长时间
ihipop 9
ihipop 15 小时 16 分钟前 via Android
*新内核也不行的话,试试 acpi osi 改 Windows 呢?
germain 10
germain 14 小时 18 分钟前
grub (modprobe.blacklist) 里面 block 你的 nv 的 module 就行了。

xiadong1994 11
xiadong1994 8 小时 52 分钟前 via iPhone
https://unix.stackexchange.com/questions/592694/acpi-errors-preventing-boot-when-using-kernel-version-5
redeemer 12
redeemer 6 小时 30 分钟前 via iPhone
好像是 EC 的 interrupt blocked 占用了 30 秒。是不是内核没有适配你这个笔记本 EC 的驱动啊
imnpc 13
imnpc 5 小时 0 分钟前
看日志 应该是 BIOS 配置的时候没有考虑到 Linux 或者没有做*新匹配
正常出厂的时候会针对 windows linux 的一些不同做好匹配的
他这个笔记本可能只考虑支持 windows
debuggerx 14
debuggerx 3 小时 8 分钟前
@kokutou 回头试试编译和*新内核看看情况会不会好点吧
debuggerx 15
debuggerx 3 小时 6 分钟前
@iceecream 空等啥时候是个头啊 T_T 有啥反馈途径能联系到开发么 小米社区现在基本就是废的。。。
debuggerx 16
debuggerx 3 小时 5 分钟前
@shayu*0001 systemd-analyze blame 是看开启启动服务占用时长的 我是加载内核的时候就出问题咯
debuggerx 17
debuggerx 3 小时 4 分钟前
@ihipop 试了几个,也是没有效果,Windows 2009/2012/2015 等等。。
debuggerx 18
debuggerx 3 小时 3 分钟前
@germain 核显轻薄本,没有独显的……
debuggerx 19
debuggerx 3 小时 3 分钟前
@xiadong1994 这个我也搜到过,可是我的机器没独显的……
debuggerx 20
debuggerx 3 小时 1 分钟前
@redeemer 对的,我猜也是这样,现在就在想有没有啥改善的方法,只要能跳过这个恶心的 30 秒就行,其他使用起来感觉都还好。
debuggerx 21
debuggerx 2 小时 58 分钟前
@imnpc 翻车了 T_T 趁着 618 买了这台红米和宏碁的 swift3 pro,就想对比着选个跑 Linux 更完美的,结果 swift 那边直接装不上,折腾了好几天没解决,红米这边只是会开机卡一会儿,我就以为只是小问题好解决……结果现在过了 7 天无理由退货,才发现问题没有那么简单,只能硬着头皮解决了……
aneostart173 22
aneostart173 2 小时 46 分钟前
11 代 intel 大部分 linux 发行版都有问题,据说是 intel 新驱动的锅。
germain 23
germain 2 小时 31 分钟前
@debuggerx 不好意思没细看 log,看着差不多就回了。 你这能退货就赶紧退货吧。BOIS 的锅一般不是其它软件能解决的。

https://bugzilla.kernel.org/show_bug.cgi?id=109511
debuggerx 24
debuggerx 2 小时 20 分钟前
@germain 看#21,已经超过退货时间了 T_T 而且这台本子除了这个问题 其他方面我还都挺满意的……
这个 bug 处理链接我也看过了,可我还没找到一个能和红米笔记本的开发进行沟通的有效途径,目前的想法是看看通过编译自定义内核能不能绕过或者缓解这个问题。
germain 25
germain 2 小时 9 分钟前
@debuggerx 你可以尝试一下 Enable “EC read/write access through /sys/kernel/debug/ec”

CONFIG_ACPI_EC_DEBUGFS: 默认关闭 │
libook 26
libook 1 小时 20 分钟前
笔记本硬件有很多是专用硬件,有的不够开放,相应的会难以适配 Linux,这方面 Ubuntu 出了个认证项目(可以去 Ubuntu 官网查),通过 Ubuntu 认证的才会保证跑 Ubuntu 没问题,其他的设备基本都是看运气。

红米本还是个冷门本吧,这东西是销量越高相应的 Linux 适配资料就越多,前提是能 Hack,有的*度封闭的设备完全封禁了适配 Linux 的门路。

看有什么需求必须用 Linux,如果仅仅是想用 Linux 开发环境开发一些应用级别的项目,其实可以用 Win10+WSL2,这块越来越成熟了,我现在开发 Web 全栈项目 WSL2 是完全能胜任的。

不过如果开发系统级别的项目和硬件项目的话 WSL 可能无法满足需求,此时可以考虑虚拟机,只不过会比较耗电,但能解决所有硬件适配问题。
generic 27
generic 22 分钟前
@debuggerx 我红米 pro15 amd 版也有问题,比如特定内核版本上才能 suspend-resume 成功,比如合上屏幕不产生事件。bios 设置也是什么选项都没有。要 Linux 兼容性还是 dell 或者联想好一点。
generic 28
generic 21 分钟前
@debuggerx 硬核搞法就是自己去改 bios acpi 字节码: https://wiki.archlinux.org/title/DSDT

如何解释这段 JavaScript 代码的输出结果?

演示地址: https://jsbin.com/sepapegiga/edit?html,js,console,output

JavaScript href 演示 代码20 条回复 • 2021-07-01 12:16:02 +08:00
pcslide 1
pcslide 16 小时 40 分钟前
这里是个 array of function

这里的 rule 只是一个入参的代号,并不是一个变量
lujjjh 2
lujjjh 16 小时 34 分钟前
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
mxT52CRuqR6o5 3
mxT52CRuqR6o5 16 小时 24 分钟前 via Android ❤️ 1
又不是全局变量,为什么会被覆盖
farmer001 4
farmer001 16 小时 21 分钟前
因为每次函数执行都是不同的作用域(上下文?),所以并不会覆盖
KyrieJoshua 5
KyrieJoshua 16 小时 13 分钟前
内部函数可以访问外层函数的作用域里的变量,我是这么理解的;
定义的时候匿名函数的 rule 就是访问的外部函数里的变量;而且外部函数每次执行的时候都会重新声明一次 rule,所以不会互相覆盖;可以在匿名函数里打断点来查看调用栈;
如果使用 this.rule 来保存就会不一样了
JerryCha 6
JerryCha 16 小时 9 分钟前
似乎形成闭包了
ThomasTrainset 7
ThomasTrainset 16 小时 4 分钟前 via iPhone
基础不扎实
KrisWuSkrSkr 8
KrisWuSkrSkr 15 小时 47 分钟前
我的理解是闭包了,保存了当时的上下文 rule 。
tinkerer 9
tinkerer 14 小时 40 分钟前
2 楼正解。
Rocketer 10
Rocketer 14 小时 35 分钟前 via iPhone
你每次执行 add 方法都声明了一个新的 rule,所以他们各自引用的是不同的对象

Biwood 11
Biwood 14 小时 6 分钟前 via Android
没有立即执行函数和 return 操作很多人就不认识闭包了

按照闭包的解释,每次执行.add 操作的时候,在其内部会形成**一个独立的词法环境**,在这个词法环境中新创建的匿名函数(被 push 那个)会记住对环境中变量的引用,因为你在函数里内部执行了 console.log(rule),用到了当时环境中的 rule,所以这个 rule 变量会跟函数绑在一起,形成闭包

你第二次执行 add 操作的时候,形成的是新的运行时上下文,push 进去的函数也是新的,该函数捆绑的变量也是新的,也就是一个新的闭包,所以不会覆盖上一次用到的变量

你可以在 Chrome 的调试工具里用 console.dir()把*后一步 forEach 中的 fn 打印出来,可以看到闭包里面具体引用了哪些内容
muzuiget 12
muzuiget 12 小时 54 分钟前
闭包,建议重新理解。
myCupOfTea 13
myCupOfTea 5 小时 5 分钟前
这不就是闭包吗
meepo3927 14
meepo3927 5 小时 3 分钟前
形成闭包了,匿名函数访问的变量 Rule 不会释放。

每次执行 add 方法,都会存储一个新的 Rule 变量以及值,通常在方法结束之后,这个变量会被释放,

但是有闭包的情况,不会释放。
zhanlanhuizhang 15
zhanlanhuizhang 4 小时 39 分钟前
基本知识,没学好。
sandman511 16
sandman511 4 小时 23 分钟前
我后端,光看个标题就知道是闭包问题了(狗头
no1xsyzy 17
no1xsyzy 4 小时 18 分钟前
我还在想有什么可解释的,竟然是在说作用域?

请回炉从 SICP 重造
cenbiq 18
cenbiq 4 小时 7 分钟前
就是不会被覆盖,因为两次执行了 add 方法,var rule 发生了两次
lizhenda 19
lizhenda 4 小时 5 分钟前
闭包
libook 20
libook 1 小时 33 分钟前
function 其实有两种调用模式,一种是函数,另一种是对象方法;不同调用模式表现出不同的特性,*直接的区别就是 function 运行的上下文。

add 是声明在 RuleSystem 类(或者说是 RuleSystem 构造函数的原型)上的,当 new 出一个 ruleSystem 对象的时候,调用 ruleSystem.add 就是调用对象方法的模式,它的上下文是 ruleSystem 对象本身,所能直接操作 ruleSystem 的 rules 数组。

在 add 方法内部向 rules push 的 item 是个 funciton,这个 funciton 不在原型链上,调用的时候是普通的函数调用模式,所以它的上下文是它所在的作用域(跟写 C 语言差不多),用到 rule 的时候就会自然向外层一层一层搜索 rule 这个关键字,于是找到了 var rule=’Rule ‘+rule 。

当你每次调用 add 方法的时候,都会产生一个新的局部作用域,这个作用域里有个 rule 变量,你一共调用了 2 次 add 方法,所以创建了 2 个局部作用域,这两个作用域里的 rule 值不一样,一个是”Rule A”,另一个是”Rule B”;两次 push 也都是在上述两个作用域里分别执行的,push 到 rules 的 function 里的 rule 也都是引用各自作用域里离它*近的那个 rule 。

这时候代码等价于:

var rules=[];

var add=function(rule){
var rule=’Rule ‘+rule
rules.push(function(){
console.log(rule)
})
}

两者区别仅仅是 rules 放在对象里还是直接放在上层作用域里,其余都是完全等价的。

如果你希望 rules 数组里的所有函数中的 console.log(rule)都输出*后产生的 rule 值,你应该把 add 方法内部的 rule 变量换成一个公共变量,就是每次调用 add 方法不会重新创建的那种,比如直接放在上层作用域里,或者用 this.rule 。