Coder Social home page Coder Social logo

blog's Introduction

hello world

blog's People

Contributors

cisen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

android入门

AndroidManifest.xml

  1. AndroidManifest.xml

gradle

  • 配置本地gradle,在grable/wrapper/gradle-wrapper.properties里面设置
distributionUrl=file:/Users/cisen/gradle-4.8.1/gradle-4.8.1-all.zip

adb

  • 安装adb命令
  1. 在~/.bash_profile文件中输入export PATH=${PATH}:~/Library/Android/sdk/platform-tools/
  2. source ~/.bash_profile,然后输入adb测试
  • 部分命令
    获取安装程序列表
adb shell pm list packages

卸载

adb uninstall com.ircloud.yfj

react/lib/update的使用

$splice

官方例子

const collection = [1, 2, {a: [12, 17, 15]}];
const newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
// => [1, 2, {a: [12, 13, 14, 15]}]

$splice:[[开始index, 删除长度, 后面都是插入数据], [可以多个action]],就是在一个数组里面应用多次splice

AVFoundation

实例

  1. 一个基础能用版,需10.0以上和权限协议统一
    在plist添加
<key>NSCameraUsageDescription</key>
    <string>App需要您的同意,才能访问相机</string>

在swift文件里:

import UIKit
import AVFoundation

class Camera: UIViewController,AVCaptureMetadataOutputObjectsDelegate,AVCapturePhotoCaptureDelegate {
    
    @IBOutlet weak var previewView: UIView!
//    @IBOutlet weak var captureButton: UIButton!
    @IBOutlet weak var messageLabel: UILabel!
    
    var captureSession: AVCaptureSession?
    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
    
    var capturePhotoOutput: AVCapturePhotoOutput?
    var qrCodeFrameView: UIView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let previewView = UIView(frame: CGRect(x: 20, y: 20, width: 50, height: 50))
        previewView.backgroundColor = UIColor.green
        self.view.addSubview(previewView)
        
//        captureButton.layer.cornerRadius = captureButton.frame.size.width / 2
//        captureButton.clipsToBounds = true
        
        // Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter
        guard let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) else {
            fatalError("No video device found")
        }
        
        do {
            // Get an instance of the AVCaptureDeviceInput class using the previous deivce object
            let input = try AVCaptureDeviceInput(device: captureDevice)
            
            // Initialize the captureSession object
            captureSession = AVCaptureSession()
            
            // Set the input devcie on the capture session
            captureSession?.addInput(input)
            
            // Get an instance of ACCapturePhotoOutput class
            capturePhotoOutput = AVCapturePhotoOutput()
            capturePhotoOutput?.isHighResolutionCaptureEnabled = true
            
            // Set the output on the capture session
            captureSession?.addOutput(capturePhotoOutput!)
            
            // Initialize a AVCaptureMetadataOutput object and set it as the input device
            let captureMetadataOutput = AVCaptureMetadataOutput()
            captureSession?.addOutput(captureMetadataOutput)
            
            // Set delegate and use the default dispatch queue to execute the call back
            captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
            
            //Initialise the video preview layer and add it as a sublayer to the viewPreview view's layer
            videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
            videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
            videoPreviewLayer?.frame = view.layer.bounds
            previewView.layer.addSublayer(videoPreviewLayer!)
            
            //start video capture
            captureSession?.startRunning()
            
//            messageLabel.isHidden = true
            
            //Initialize QR Code Frame to highlight the QR code
            qrCodeFrameView = UIView()
            
            if let qrCodeFrameView = qrCodeFrameView {
                qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
                qrCodeFrameView.layer.borderWidth = 2
                view.addSubview(qrCodeFrameView)
                view.bringSubview(toFront: qrCodeFrameView)
            }
        } catch {
            //If any error occurs, simply print it out
            print(error)
            return
        }
        
    }
    
    override func viewDidLayoutSubviews() {
        videoPreviewLayer?.frame = view.bounds
        if let previewLayer = videoPreviewLayer ,(previewLayer.connection?.isVideoOrientationSupported)! {
            previewLayer.connection?.videoOrientation = UIApplication.shared.statusBarOrientation.videoOrientation ?? .portrait
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func onTapTakePhoto(_ sender: Any) {
        // Make sure capturePhotoOutput is valid
        guard let capturePhotoOutput = self.capturePhotoOutput else { return }

        // Get an instance of AVCapturePhotoSettings class
        let photoSettings = AVCapturePhotoSettings()

        // Set photo settings for our need
        photoSettings.isAutoStillImageStabilizationEnabled = true
        photoSettings.isHighResolutionPhotoEnabled = true
        photoSettings.flashMode = .auto

        // Call capturePhoto method by passing our photo settings and a delegate implementing AVCapturePhotoCaptureDelegate
        capturePhotoOutput.capturePhoto(with: photoSettings, delegate: self)
    }
}

extension ViewController : AVCapturePhotoCaptureDelegate {
    func photoOutput(_ captureOutput: AVCapturePhotoOutput,
                     didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?,
                     previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
                     resolvedSettings: AVCaptureResolvedPhotoSettings,
                     bracketSettings: AVCaptureBracketedStillImageSettings?,
                     error: Error?) {
        // Make sure we get some photo sample buffer
        guard error == nil,
            let photoSampleBuffer = photoSampleBuffer else {
                print("Error capturing photo: \(String(describing: error))")
                return
        }
        
        // Convert photo same buffer to a jpeg image data by using AVCapturePhotoOutput
        guard let imageData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) else {
            return
        }
        
        // Initialise an UIImage with our image data
        let capturedImage = UIImage.init(data: imageData , scale: 1.0)
        if let image = capturedImage {
            // Save our captured image to photos album
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }
    }
}

extension ViewController : AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ captureOutput: AVCaptureMetadataOutput,
                        didOutput metadataObjects: [AVMetadataObject],
                        from connection: AVCaptureConnection) {
        // Check if the metadataObjects array is contains at least one object.
//        if metadataObjects.count == 0 {
//            qrCodeFrameView?.frame = CGRect.zero
//            messageLabel.isHidden = true
//            return
//        }
//
//        // Get the metadata object.
//        let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
//
//        if metadataObj.type == AVMetadataObject.ObjectType.qr {
//            // If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
//            let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
//            qrCodeFrameView?.frame = barCodeObject!.bounds
//
//            if metadataObj.stringValue != nil {
//                messageLabel.isHidden = false
//                messageLabel.text = metadataObj.stringValue
//            }
//        }
    }
}

extension UIInterfaceOrientation {
    var videoOrientation: AVCaptureVideoOrientation? {
        switch self {
        case .portraitUpsideDown: return .portraitUpsideDown
        case .landscapeRight: return .landscapeRight
        case .landscapeLeft: return .landscapeLeft
        case .portrait: return .portrait
        default: return nil
        }
    }
}

TypedArray ArrayBuffer DataView

https://www.jishux.com/p/0aab4578d1b2d5f4

TypedArray

数据类型 字节长度(Byte) 含义 对应的C语言类型
Int8 1 8位带符号整数 signed char
Uint8 1 8位不带符号整数 unsigned char
Uint8C 1 8位不带符号整数(自动过滤溢出) unsigned char
Int16 2 16位带符号整数 short
Uint16 2 16位不带符号整数 unsigned short
Int32 4 32位带符号整数 int
Uint32 4 32位不带符号的整数 unsigned int
Float32 4 32位浮点数 float
Float64 8 64位浮点数 double

说明

  1. array和typeArray
    Javascript的数组的强大以及全能,给我们带来了便捷性。但一般而言:
    全能的东西能在各种环境下使用,但却不一定适用于各种环境。
    而TypedArray正是为了解决Javascript中数组“干太多事”而出现的。
    起源
    TypedArray是一种通用的固定长度缓冲区类型,允许读取缓冲区中的二进制数据。
    其在WEBGL规范中被引入用于解决Javascript处理二进制数据的问题。
    TypedArray已经被大部分现代浏览器支持。
TypedArray 数组
数组成员均为同一个类型 可以是任意类型
数组成员是连续的,不会有空位 非连续
数组的成员默认值均为0 默认为空
视图,不存储数据 存储数据

TypedArray是用来存储二进制数据的抽象数据结构,如果把他当做父类理解,那他的子类有:

Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();

语法如下:

new TypedArray(); // new in ES2017
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);

Int16Array不能用push方法,但可以中括号赋值,比如 int16[2]=60;console.info(int16)

和Array的互转

1) Array转Int16Array 

var int16 = new Int16Array([1,2,3]);console.info(int16)

2) Int16Array 转Array

Array.from(new Int16Array([1,2,3]))

和buffer的互转

var byteArray=new Uint8Array(arrayBuffer)

ArrayBuffer

定义

官方
ArrayBuffer is a data type that is used to represent a generic, fixed-length binary data buffer. You can't directly manipulate the contents of an ArrayBuffer; instead, you create an ArrayBufferView object which represents the buffer in a specific format, and use that to read and write the contents of the buffer.
表示二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据。 无法直接读取或写入 ArrayBuffer,但可根据需要将其传递到类型化数组或 DataView 对象 来解释原始缓冲区。

他是一个二进制数据的原始缓冲区,虽然 JavaScript 是弱类型语言,但是他本身是对数据的类型和大小都有限制的,我们需要通过某种数据结构将缓冲区的内容有序的读取出来(写进去),如TypedArray或DataView 对象来操作。

使用

类型化数组(TypedArray)类型表示可编制索引和操纵的 ArrayBuffer 对象 的各种视图。 所有数组类型的长度均固定。

名称 大小(以字节为单位) 描述
Int8Array 1 8 位二补码有符号整数
Uint8Array 1 8 位无符号整数
Int16Array 2 16 位二补码有符号整数
Uint16Array 2 16 位无符号整数
Int32Array 4 32 位二补码有符号整数
Uint32Array 4 32 位无符号整数
Float32Array 4 32  IEEE 浮点数
Float64Array 8 64  IEEE 浮点数

DataView 是一个可以从ArrayBuffer对象中读写多种数值类型的底层接口,在读写时不用考虑平台字节序问题。

// create an ArrayBuffer with a size in bytes
var buffer = new ArrayBuffer(16);
// Create a couple of views
var view1 = new DataView(buffer);
var view2 = new DataView(buffer,12,4); //from byte 12 for the next 4 bytes
view1.setInt8(12, 42); // put 42 in slot 8
console.log(view2.getInt8(0));
// expected output: 42
```js
DataView似乎就是为了让ArrayBuffer可用,证明其存在价值:
1 )  DataView包装ArrayBuffer,使ArrayBuffer可以读写数据
2) 写数据需要用set方法,比如setInt8(12,42),而读数据用get方法,比如getInt8(0),仍然觉得没有数组好用
3) Array.from(dv) 会为空数组,这个最垃圾,简直是大坑,还是遍历获取吧
```js
let dv = new DataView(new ArrayBuffer(16));
dv.setInt8(0,10);
Array.from(dv);

转换

  1. 与字符串转化
// ArrayBuffer转为字符串,参数为ArrayBuffer对象
function ab2str(buf) {
   return String.fromCharCode.apply(null, new Uint16Array(buf));
}
// 字符串转为ArrayBuffer对象,参数为字符串
function str2ab(str) {
    var buf = new ArrayBuffer(str.length*2); // 每个字符占用2个字节
    var bufView = new Uint16Array(buf);
    for (var i=0, strLen=str.length; i<strLen; i++) {
         bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

应用

ArrayBuffer 的应用特别广泛,无论是 WebSocket、WebAudio 还是 Ajax等等,前端方面只要是处理大数据或者想提高数据处理性能,那一定是少不了 ArrayBuffer 。
很多浏览器操作的API,用到了二进制数组操作二进制数据,下面是其中的几个。
File API
XMLHttpRequest
Fetch API
Canvas
WebSockets

cocoapods

安装

sudo gem install cocoapods

使用

pod install

更新Podspec索引文件

pod setup作用:将所有第三方的Podspec索引文件更新到本地的~/.cocoapods/repos目录下
pod安装成功之后一个首先的操作就是执行命令(不是必须的):
pod setup
所有的第三方开源库的Podspec文件都托管在https://github.com/CocoaPods/Specs
我们需要把这个Podspec文件保存到本地,这样才能让我们使用命令pod search 开源库搜索一个开源库,怎样才能把github上的Podspec文件保存本地呢?那就是 pod setup

执行pod setup时,CocoaPods 会将第三方的podspec索引文件更新到本地的~/.cocoapods/repos目录下

如果没有执行过 pod setup,那用户根目录下~找不到.cocoapods/repos目录的,没有创建这个目录。

如果执行 pod setup,并且命令没有执行成功,那么会创建~/.cocoapods/repos目录,只不过目录是空的。

如果执行 pod setup,并且命令执行成功,说明把github上的Podsepc文件更新到本地,那么会创建~/.cocoapods/repos目录,并且repos目录里有一个master目录,这个master目录保存的就是github上所有第三方开源库的Podspec索引文件。

但是第一次执行pod setup时,这个github的Podspec索引文件比较大,有 300M 左右(以后会越来越大的),所以第一次更新时非常慢.要耐心等待......可以进去目录~/.cocoapods/repos使用命令du -sh *来查看下载文件的大小了

搜索一下三方库

终端输入:pod search AFNetworking

更换源

cd ~/.cocoapods/repos
pod repo remove master
git clone https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git master
最后进入自己的工程,在自己工程的podFile第一行加上:
source ‘https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git’

卸载CocoaPods

sudo gem uninstall cocoapods

其他

pod install后,关闭所有的Xcode窗口,再次打开工程目录会看到多了一个后缀名为.xcworkspace文件以后打开工程就双击这个文件打开了,而不再是打开.xcodeproj文件。 进入工程后引入头文件不再是#import "AFNetworking.h",而是#import <AFNetworking.h>

react 16新接口

unstable_deferredUpdates

  1. 实现异步setState的接口。有时候一个setState的触发的render特别耗时,会阻塞反馈,导致用户有卡顿感,用这个就能实现在非操作阶段setState
  2. 具体实现是deferredUpdates赋值expirationContext,并计算computeAsyncExpiration,在enqueueSetState的时候进入异步更新

unstable_batchedUpdates

flushSync

unstable_createRoot

##unstable_flushControlled

问题

  1. 如何控制异步setState的expirationTime

libusb

安装

下载源码,然后

sh ./autogen.sh
make install

configure // 配置环境
make
make install

mac上完整卸载删除.简单粗暴无脑:androidstudio删除方案

如果你是mac ,你删除as ,删不干净也正常,你会发现安装的时候,前面的东西也在.配置文件在,会导致你以前的错误不想要的东西都在.

废话不多说,复制粘贴就是干!!!!~~~~~~~~

第一步: 复制粘贴就是干!!!!~~~~~~~~ 复制到命令行里面,直接回车就OK

rm -Rf /Applications/Android\ Studio.app
rm -Rf ~/Library/Preferences/AndroidStudio*
rm ~/Library/Preferences/com.google.android.studio.plist
rm -Rf ~/Library/Application\ Support/AndroidStudio*
rm -Rf ~/Library/Logs/AndroidStudio*
rm -Rf ~/Library/Caches/AndroidStudio*

第二步: 复制粘贴就是干!!!!~~~~~~~~ 复制到命令行里面,直接回车就OK

rm -Rf ~/AndroidStudioProjects

第三步: 复制粘贴就是干!!!!~~~~~~~~ 复制到命令行里面,直接回车就OK

rm -Rf ~/.gradle

第四步: 复制粘贴就是干!!!!~~~~~~~~ 复制到命令行里面,直接回车就OK

rm -Rf ~/.android

第五步: 复制粘贴就是干!!!!~~~~~~~~ 复制到命令行里面,直接回车就OK

rm -Rf ~/Library/Android*

做完这五步,关闭你的终端,全部完毕,然后安装你的AS,一点问题都没有.

pinus

备份

目录说明

mster

主要负责启动各种逻辑服务器,生成和执行各种ssh命令,监控各种逻辑服务器的运行情况,添加和删除
start函数里面有个tarter.runServers(self.app);启动各种服务器

server

components

主要实现服务器对各种扩展功能的支持和管理

  • proxy。由于每台服务器可能要连接N台逻辑服务器,所以需要使用proxy来分发请求到具体服务器。pomelo-rpc就是提供同时连接多台服务器的管理和建设一台server。server的数据返回不需要区分哪台服务器,只要通过回调执行就好
  • session是用来管理用户登录的

common

  • manager
  • remote
  • service

connectors

主要负责硬件之间的链接,事件侦听和链接方式的切换

filters

负责对消息的过滤

modules

问题

未解决

  • 服务器初始化过程要加载什么配置,如何加载的
  • 如何启动多个服务器,侦听多个端口。通过util/appUtil.js下面的loadServers函数,app.loadConfigBaseApp(Constants.RESERVED.SERVERS, Constants.FILEPATH.SERVER)。加载完后会设置全局变量'servers',值就是servers的json对象,通过servers变量就能获得所有服务器列表。启动主要是在master/starter.js里面的runServers函数,通过sshrun执行一段命令将server跑起来
  • 分布式之间如何沟通?通过rpc,rpc服务器之间的连接通过socket连接,主要实现四pomelo-rpc模块
  • session是在何时被创建的?被谁如何管理的?connector组件start的时候__session__会被绑定到 this.session = this.app.components.session;组件的启动是在application.js的Application.start 函数,通过appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {启动组件。如何创建和获得session的呢?通过components/connector.js的bindEvents函数获取和创建(获取不到就创建)session
  • components/connectors.js和connectors/sioconnectors.js有何不同?各有什么用?
    components/connectors.js负责connectors的的分配,session的分配,也就是这个是连接管理器
    connectors/sioconnectors.js负责底层socket的连接管理和一些编码解码,这个是连接器
  • sioconnector在哪里如何启动的?这个是在components/connector.js里面require进来的,也是在构造函数里面this.connector = getConnector(app, opts);加入,然后在afterStart里面启动的。
  • 如何区分加载的是那种服务器,gate,connector,chat?这个区分主要是在starter.runServers这个函数,第一次是启动all,然后子启动就根据app.type获取对应服务器id,然后使用child_process再次启动app.js并附带上所有的参数如
[
	'--inspect=32123',
    'gamer-server/app.js',
    'env=develop',
    'id=connectort-server-1',
    'host=127.0.0.1',
    'port=4050',
    'clientPort=3050',
    'fronttend=true',
    'args= --inspect=32123',
    'serverType=connector'
]
  • BackendSession是怎么创建和删除的?

  • service和component如何区分?service通常是一块业务逻辑,可以通过N各业务组件实现,比如session service就没有session component, service的函数通常包含业务参数,或者包含业务函数。component通常是实现某单一功能,不包含业务逻辑

  • 每台服务器之间的区别是什么?比如说是不是每个服务器都会有components

  • session 和 sessionService是数据结构是什么?
    session:

this.id = sid;          // r
        this.frontendId = frontendId; // r
        this.uid = null;        // r
        this.settings = {};

        // private
        this.__socket__ = socket;
        this.__sessionService__ = service;
        this.__state__ = ST_INITED;

SessionService:

 opts = opts || {};
        this.singleSession = opts.singleSession;
        this.sessions = {};     // sid -> session
        this.uidMap = {};       // uid -> sessions
this.sessions[session.id] = session;
  • 前端session和后端session有什么关系?后端session的建立以来前端rpc调用传来的sid和uid等,但是后端session的修改并不会影响前端session。后端只是拿前端rpc调用的信息建立session。

  • 如何给同一个channel的用户发送消息?channel是属于特定某台后端服务器的,一个用户要加入channel,那他所在的connector会发起对这台channel服务器的rpc调用,这台channel服务器会创建这位客户的session(包括sid和uid),并把他加入到channel中,并保持与客户connector的链接。当channel中某位用户广播消息,channel服务器直接从session列表中读取客户和connector信息,然后直接返回就可以了。就是说客户加入某个channel,那个channel服务器就会跟客户的connector一直保持通讯。

  • monitor是在哪里启动的?monitor随着component一起启动的+

  • sid、uid、frontendid、rid各有什么区别?sid不是socketid,是serverid比如:‘mqttconnector-server-1’,uid是userid,frontendid是前端id,rid是room_id房间号。

  • socket是什么?mqtt的socket是connectors目录下面的对应的socketjs,有mqtt、ws的,connector的socket结构是:

// 这里的id是在mqttconnector里面传过来的,事实上是一个连接数,随着连接的数量自增
this.id = id;
// 这里的是mqtt_connection返回的socket
        this.socket = socket;
        this.remoteAddress = {
            ip: socket.stream.remoteAddress,
            port: socket.stream.remotePort
        };
        this.adaptor = adaptor;

        let self = this;

        socket.on('close', this.emit.bind(this, 'disconnect'));
        socket.on('error', this.emit.bind(this, 'disconnect'));
        socket.on('disconnect', this.emit.bind(this, 'disconnect'));

        socket.on('pingreq', function (packet: any) {
            socket.pingresp();
        });

        socket.on('subscribe', this.adaptor.onSubscribe.bind(this.adaptor, this));

        socket.on('publish', this.adaptor.onPublish.bind(this.adaptor, this));

        this.state = ST_INITED;
  • socket、session的区别与关系?socket属于连接的,socket的翻译是插座,是一个长期的连接。session是属于用户的,服务器保存的用户的一份缓存,翻译是会议,是一个短期会话的各个人的基本信息。关系是:sessions[sid] = session,session.socket = MQTTSocket 是connector下面的mqttsocket而不是原生的mqtt_connection。

  • handler和remote的加载和调用原理?

  1. remote函数的调用底层在pinus-rpc\lib\rpc-server\dispatcher.ts
  2. handler是在pinus\lib\common\service\handlerService.ts里面加载的
  3. handerService是在pinus\packages\pinus\lib\server\server.ts里面的start的时候使用initHandler初始化handler,然后在接受消息触发事件后,执行完beforeFilter后开始找到对应的handler并执行
  • handle和remote的触发流程
    先是handler
  1. 在components/connector通过socket.on('message'监听所有的message事件
  2. 使用decode解释客户端传过来的msg,如果消息有加密的可以调用解密库解密
  3. 解密完调用server/server.js的globalHandle,执行完parseRoute之后
  4. 调用handlerService并根据路由找到存储的handler执行
    然后是remote
  5. 在pinus\lib\components\remote.ts里面读取config的服务器配置,并调用pinus-rpc创建createServer(opts);
  • 为什么不能在connector里面调用chat的handler,只能remote?
    服务器有两层,一层是front服务器,它侦听config的clientPort,connector被触发msg事件就会走handler流程
    另外一层是rpc服务器,它调用pinus-rpc的server,利用config的port生成RPC侦听服务器,走remote流程
  1. 在rpc-client里面的generateProxy只load了remote的方法,没有loadhandler的
  2. 如果前端发给connector的路由是chat.chatHandler.send,会在pinus\lib\server\server.ts里面的globalHandle的dispatch里面判断
// 如果与本服务器类型不一样,则调用doForward
if (self.app.getServerType() !== routeRecord.serverType) {
                doForward(self.app, msg, session, routeRecord, function (err, resp) {
                    response(true, self, err, routeRecord, msg, session, resp, cb);
                });
            } else {
// 如果一样则调用本服务器的handle
                doHandle(self, msg, session, routeRecord, function (err, resp) {
                    response(true, self, err, routeRecord, msg, session, resp, cb);
                });
            }

而在doForward函数里直接就是app.sysrpc[routeRecord.serverType].msgRemote.forwardMessage(调用sys的rpc。所以客户端通过路由访问后端服务器,在connector那里就写死是调用sysrpc来调用后端的remote。
4. 在调用this.app.rpc事实上已经被定向到self.client.proxies.user,而user只存储了remote的方法,所以要调用chat.handler要使用this.app.sysrpc.megRemote.forwardMessage(sysrpc只有这个),然后将socket和带chathandler的msg作为参数传进去就可以调用了

  • handler和remote的区别
  1. remote会生成一个RPC服务器,handler不会
  2. handler是暴露给用户使用的,比如浏览器端使用发送消息:chat.chatHandler.send(这里其实还是由connector去发送消息chat服务器),remote是给后端服务器之间使用的

已解决

  • gate如何分配connector,connector如何分配业务服务器?同过dispatch的crc32算法

  • route如何捕获路由和执行根据uid分配业务服务器?通过lib/components/proxy.js下面的genRouteFun获取到对应路由的分配函数

  • 为什么需要远程调试?因为调试新的进程只能通过远程调试,config/servers.json上配置的args可以添加子进程的访问接口

  • 客户每次发送消息后端在哪里侦听接收?在lib/components/connnector.js的bindEvents函数里面,里面调用socket.on('message', ()=> {})侦听所有信息、

  • component的加载和运行原理是什么?所拥有的权限是什么?每个component都有个name属性,pomelo初始化的时候通过fs.readdirSync函数将components目录下面的所有组件都加载,然后通过组件的name属性挂载到pomelo.components下面, 通过Pomelo.components.__defineGetter__(name, _load);函数使用component下面设置的name挂载到pomelo的components下面,然后就可以通过this.app.components.__session__这种方法调用组件的方法

  • 为什么需要frontendSession和BackendSession?session是一个人的登录信息,connectors维护session是为了保持用户的登录状态,backendSession是为了在同一个channel的用户客户相互之间发送信息

  • 服务器是如何上线的?app每次添加server都会被master侦听到(Constants.KEYWORDS.MASTER_WATCHER),master那里有一份服务器的subscribe列表,它会向所有的listener广播增加了一台服务器,chat服务器的monitor监听到了master的广播触发app.addServers,addServers通过app.event发送events.ADD_SERVERS信号,proxy组件在初始化的时候就通过app.event侦听app.event的添加服务器信号,然后往rpc的proxies里面塞服务器类型和方法并存起来。也就是每台服务器都会维护一份serverTypeMaps。

  • monitor(监控器)是什么?是不是每台服务器都有?monitor是作为一个component为每台服务器启动的时候都加载,也就是每台服务器都通过monitor监听master的信息和发送心跳信息。monitor底层是通过mqtt链接配置好的master服务器信息链接到master,订阅master的信息。

  • console是什么?console是pinus的命令行服务器,就是你可以通过cmd查询和操作服务器,具体

  • 为什么filter会有before和after,作用是什么?before是将粗口屏蔽,比如将“fuct”改成“”。after是对用户信息进行组装,比如“fuck”组装成“用户1 在12:00:00发表:”。

  • backendsessionservice是如何启动的?也是作为一个component 设置'name = 'backendSession';'启动的

  • 在client publish后,pinus是如何接受消息并调用game-server\app\servers\mqttconnector\handler\publish.ts的publish函数的?

  1. 首先,所有connector都是继承自EventEmitter模块,然后启动component/connector模块的时候,会监听EventEmitter的时间
  2. publish是on('message', 在pinus\packages\pinus\lib\components\connector.ts,触发handleMessage(主要是使用filter校验msg),然后开始触发beforeFilter,在beforeFilter的回调里面触发
    self.handlerService.handle函数,这个函数通过handler[method](msg, session)执行用户自己写的publish函数(game-server\app\servers\mqttconnector\handler\publish.ts)
  3. 执行完之后继续dispatch,dohandle,response,afterFilter。校验完后在pinus\packages\pinus\lib\components\connector.ts handleMessage里面的globalHandle的回调函数里面调用send,吧消息插入scheduleservice里面。
  • session是由谁创建和维护的?connector组件调用sessionservice创建的,在getSession函数。session实例也是由connector去维护的,app只是连接了session组件并没有维护session实例。注意一个服务器只有一个connector,一个connector维护一个sessionService,sessionService将每个用户的session根据sid存在数组里面let session = this.sessions[sid];

  • pinus修改使用的组件的方法?
    在你的应用的app.js里面设置:

app.configure( env: 'all', fn: function () [
  // console. Log( '~!! curserverval: ', app. get(RESERVED. CURRENT_ .SERVER));
  app. set(RESERVED.ERROR_ HANDLER, errorHandler);
  app. set(RESERVED.GLOBAL_ ERROR_ HANDLER, gLobaLErrorHandLer) ;
  app. set(RESERVED.RPC_ERROR_HANDLER, rpcErrorHandLer) ;
  app. load(new NestComponent(app));
  pinus.components.dictionary = DictionaryComponent;
  pinus.components.connector = MyConnectorComponent;
  // RPC 启用TCP协议
  app. set( setting: ' proxyConfig', val: [
    mailboxFactory: createTcpMailBox,
    //bufferMsg: true
    // rpc 超时时间
    timeout: 20 * 1000,
  • 配置里面的port和clientPort有什么关系和区别?
    port是rpc端口,clientPort是对外的端口

  • mqttconnection和mqtt.js并没有提供服务器广播功能,pinus是如何实现将publish的消息发给所有subscribe的人的?

  1. mqtt发生连接后,rpc的acceptor会将连接的socket借助socket.id存在类里面
  2. 对外的connector会把socket存到components/connectors的session里面,主要在bindEvents的 let session = this.getSession(socket);这一句。getSession会顺便创建session,并存于继承自sessionService的sessionComponent
  • 如何实现批量publish?
    在channelService,实际上还是通过遍历。在一个chanal的人的sid(socketid)会被存到groups里面,然后一个for循环遍历groups,发送出去

  • 如何确定某个chanel绑定在哪个后端服务器?
    所有服务器都有channelService,具体定那个channel在哪台服务器要自己写路由定,先在app里面设置好app.route('chat', routeUtil.chat);
    路由函数可以这么写

export function chat(session: Session, msg: any, app: Application, cb: (err: Error , serverId ?: string) => void) {
    let chatServers = app.getServersByType('chat');

    if(!chatServers || chatServers.length === 0) {
        cb(new Error('can not find chat servers.'));
        return;
    }

    let res = dispatch(session.get('rid'), chatServers);

    cb(null, res.id);
}
  • 同一个channel的所有人都在同一个服务器?
    根据指定某类服务器(chat)使用不同的路由算法,比如app.route('chat', routeUtil.chat);,这样分配channel的就根据rid来了,不根据uid来了,所有的user都可以放在一起了

debug

vscode

{
  "version": "0.2.0",
  "configurations": [
      {
          "type": "node",
          "request": "attach",
          "name": "附到connector",
          "address": "127.0.0.1",
          "port": 10001,
          "protocol":"inspector",
          "localRoot": "${workspaceFolder}/game-server/dist",
          "remoteRoot": "${workspaceFolder}/game-server/dist"
      },
      {
          "type": "node",
          "request": "attach",
          "name": "附到gate",
          "address": "127.0.0.1",
          "port": 10003,
          "localRoot": "${workspaceFolder}/game-server/dist",
          "remoteRoot": "${workspaceFolder}/game-server/dist",
      },
      {
          "type": "node",
          "request": "attach",
          "name": "附到chat",
          "address": "127.0.0.1",
          "port": 10002,
          "localRoot": "${workspaceFolder}/game-server/dist",
          "remoteRoot": "${workspaceFolder}/game-server/dist"
      },
      {
          "type": "node",
          "request": "launch",
          "name": "web-server",
          "cwd":"${workspaceFolder}/web-server",
          "program": "${workspaceFolder}/web-server/app.js"
      },
      {
          "type": "node",
          "request": "launch",
          "name": "game-server",
          "args": ["env=development"],
          "cwd":"${workspaceFolder}/game-server/dist",
          "program": "${workspaceFolder}/game-server/dist/app.js"
      }
  ]
}

mqtt

app.set('connectorConfig', {
        connector: pinus_1.pinus.connectors.mqttconnector,
        publishRoute: 'connector.handler.entryHandler',
        subscribeRoute: 'connector.handler.entryHandler',
    });

其他

  1. 如果设置了 proto:true , 就会在 proto。json里找对应的消息。有则走proto,没有则走json。
  2. 支持https
    connectorConfig 加
ssl: {
		type: 'wss',
   		key: fs.readFileSync('./key/xxx.key'),  
		cert: fs.readFileSync('./key/xxx.pem')
	},

问答

  1. handler和remote的区别,handler是开放给用户调用的,remote是内部RPC调用的

moment的一些用法

时间范围查询

// 本周
beginDate = moment().startOf('week').format('YYYY-MM-DD');
endDate = moment().endOf('week').format('YYYY-MM-DD');
// 上周
beginDate = moment().subtract(1, 'weeks').startOf('week').format('YYYY-MM-DD');
endDate = moment().subtract(1, 'weeks').endOf('week').format('YYYY-MM-DD');
// 本月
beginDate = moment().format("YYYY-MM")+'-01';
endDate = moment().format('YYYY-MM-DD');
// 上月
beginDate = moment().add(-1, 'months').format("YYYY-MM")+'-01';
endDate = moment(beginDate).add(1, 'months').add(-1, 'days').format('YYYY-MM-DD');

时间差计算

  1. 判断一年
let duration = moment.duration(endDate - beginDate);
    if (duration.asYears() > 1) {
      notification.error({message: '', description: '查询期间不能超过一年,请重新选择查询日期', duration: 5});
      return false;
    }

react-dnd

术语解析

Backend

HTML5 DnD API兼容性不怎么样,并且不适用于移动端,所以干脆把DnD相关具体DOM事件抽离出去,单独作为一层,即Backend:

Under the hood, all the backends do is translate the DOM events into the internal Redux actions that React DnD can process.

Item和Type

Item是对元素/组件的抽象理解,拖放的对象不是DOM元素或React组件,而是特定数据模型(Item):

An item is a plain JavaScript object describing what’s being dragged.

进行这种抽象同样是为了解耦:

Describing the dragged data as a plain object helps you keep the components decoupled and unaware of each other.

Type与Item的关系类似于Class与Class Instance,Type作为类型标识符用来表示同类Item:

A type is a string (or a symbol) uniquely identifying a whole class of items in your application.

Type作为萝卜(drag source)和坑(drop target)的匹配依据,相当于经典DnD库的group name

collect

collect 是一个函数,默认有两个参数:connect 和 monitor。collect函数将返回一个对象,这个对象会注入到组件的 props 中,也就是说,我们可以通过 this.props 获取collect返回的所有属性。

参数 connect

source组件 collect 中 connect是 DragSourceConnector的实例,它内置了两个方法:dragSource() 和 dragPreview()。dragSource()返回一个方法,将source组件传入这个方法,可以将 source DOM 和 React DnD backend 连接起来;dragPreview() 返回一个方法,你可以传入节点,作为拖拽预览时的角色。
target组件 collect 中 connect是 DropTargetConnector的实例,内置的方法 dropTarget() 对应 dragSource(),返回可以将 drop target 和 React DnD backend 连接起来的方法。

参数 monitor

monitor 用于查询当前的拖拽状态,其对应实例内置了很多方法。

source组件 collect 中 monitor是 DragSourceMonitor的实例。
target组件 collect 中 monitor是 DropTargetMonitor的实例。

// DragSourceMonitor
monitor.canDrag()        // 是否能被拖拽
monitor.isDragging()      // 是否正在拖拽
monitor.getItemType()     // 拖拽组件type
monitor.getItem()         // 当前拖拽的item
monitor.getDropResult()   // 查询drop结果
monitor.didDrop()         // source是否已经drop在target
monitor.getInitialClientOffset()   // 拖拽组件初始拖拽时offset
monitor.getInitialSourceClientOffset()
monitor.getClientOffset() // 拖拽组件当前offset
monitor.getDifferenceFromInitialOffset() // 当前拖拽offset和初始拖拽offset的差别
monitor.getSourceClientOffset()

// DropTargetMonitor
monitor.canDrop()         // 是否可被放置
monitor.isOver(options)   // source是否在target上方
monitor.getItemType()     // 拖拽组件type
monitor.getItem()         // 当前拖拽的item
monitor.getDropResult()   // 查询drop结果
monitor.didDrop()         // source是否已经drop在target
monitor.getInitialClientOffset()   // 拖拽组件初始拖拽时offset
monitor.getInitialSourceClientOffset()
monitor.getClientOffset() // 拖拽组件当前offset
monitor.getDifferenceFromInitialOffset() // 当前拖拽offset和初始拖拽offset的差别
monitor.getSourceClientOffset()

Monitor是拖放状态的集合,比如拖放操作是否正在进行,是的话萝卜是哪个坑是哪个:

React DnD exposes this state to your components via a few tiny wrappers over the internal state storage called the monitors.

例如:

monitor.isDragging()
monitor.isOver()
monitor.canDrop()
monitor.getItem()

以props注入的方式暴露DnD内部状态,类似于Redux的mapStateToProps:

export default DragSource(Types.CARD, cardSource, (connector, monitor) => ({
  // You can ask the monitor about the current drag state:
  isDragging: monitor.isDragging()
}))(Card);

P.S.事实上,React DnD就是基于Redux实现的,见下文核心实现部分

Connector

Connector用来建立DOM抽象(React)与DnD Backend需要的具体DOM元素之间的联系:

The connectors let you assign one of the predefined roles (a drag source, a drag preview, or a drop target) to the DOM nodes

用法很有意思:

render() {
  const { highlighted, hovered, connectDropTarget } = this.props;

  // 1.声明DnD Role对应的DOM元素
  return connectDropTarget(
    <div className={classSet({
      'Cell': true,
      'Cell--highlighted': highlighted,
      'Cell--hovered': hovered
    })}>
      {this.props.children}
    </div>
  );
}

// 2.从connector取出connect方法,并注入props
export default DragSource(Types.CARD, cardSource, (connector, monitor) => ({
  // Call this function inside render()
  // to let React DnD handle the drag events:
  connectDropTarget: connector.dropTarget()
}))(Card);

建立联系的部分connectDropTarget(

)看起来相当优雅,猜测实际作用应该相当于:

render() {
  const { connectToRole } = this.props;
  return <div ref={(node) => connectToRole(node)}></div>
}

猜对了:

Internally it works by attaching a callback ref to the React element you gave it.

Drag Source与Drop Target

上面提到过这两个东西,可以称之为DnD Role,表示在DnD中所饰角色,除了drag source和drop target外,还有一个叫drag preview,一般可以看作另一种状态的drag source
DnD Role是React DnD中的基本抽象单元:

They really tie the types, the items, the side effects, and the collecting functions together with your components.

是该角色相关描述及动作的集合,包括Type,DnD Event Handler(例如drop target通常需要处理hover、drop等事件)等

API

顶级API

想要灵活使用,就先知道几个核心API

  • DragSource 用于包装你需要拖动的组件,使组件能够被拖拽(make it draggable)
  • DropTarget 用于包装接收拖拽元素的组件,使组件能够放置(dropped on it)
  • DragDropContex 用于包装拖拽根组件,DragSource 和 DropTarget 都需要包裹在DragDropContex内
  • DragDropContextProvider 与 DragDropContex 类似,用 DragDropContextProvider 元素包裹拖拽根组件。
  • DragLayer 浏览器DnD默认会根据被拖动的元素创建drag preview(一般像个半透明截图),就是鼠标拖动时右边的的图,DragLayer是用来包含preview的组件的

Connecting to DOM

DragSourceConnector

  • dragPreview() => (elementOrNode, options?)

android fragment

https://cloud.tencent.com/developer/article/1005538
  
Fragment是Android honeycomb 3.0新增的概念,Fragment名为碎片不过却和Activity十分相似。
  Fragment是用来描述一些行为或一部分用户界面在一个Activity中,
(1)你可以合并多个fragment在一个单独的activity中建立多个UI面板,
(2)同时重用fragment在多个activity中。
  你可以认为fragment作为一个activity中的一节模块 ,fragment有自己的生命周期,接收自己的输入事件,你可以添加或移除从运行中的activity。
  从中可以看出:一个fragment必须总是嵌入在一个activity中,同时fragment的生命周期 受 activity而影响。当activity 暂停,那么所有在这个activity的fragments将被destroy释放。

Fragment核心的类有:

Fragment:Fragment的基类,任何创建的Fragment都需要继承该类。
FragmentManager:管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl。
FragmentTransaction:对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord。
Nested Fragment(Fragment内部嵌套Fragment的能力)是Android 4.2提出的,support-fragment库可以兼容到1.6。通过getChildFragmentManager()能够获得管理子Fragment的FragmentManager,在子Fragment中可以通过getParentFragment()获得父Fragment。

activity and fragment

activity:
activity
fragment:
fragment
activity && fragment
af

onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
onCreate():Fragment被创建时调用。
onCreateView():创建Fragment的布局。
onActivityCreated():当Activity完成onCreate()时调用。
onStart():当Fragment可见时调用。
onResume():当Fragment可见且可交互时调用。
onPause():当Fragment不可交互但可见时调用。
onStop():当Fragment不可见时调用。
onDestroyView():当Fragment的UI从视图结构中移除时调用。
onDestroy():销毁Fragment时调用。
onDetach():当Fragment和Activity解除关联时调用。

我们这里举个例子来理解Fragment生命周期方法。功能如下:共有两个Fragment:F1和F2,F1在初始化时就加入Activity,点击F1中的按钮调用replace替换为F2。

当F1在Activity的onCreate()中被添加时,日志如下:

BasicActivity: [onCreate] BEGIN
BasicActivity: [onCreate] END
BasicActivity: [onStart] BEGIN
Fragment1: [onAttach] BEGIN
Fragment1: [onAttach] END
BasicActivity: [onAttachFragment] BEGIN
BasicActivity: [onAttachFragment] END
Fragment1: [onCreate] BEGIN
Fragment1: [onCreate] END
Fragment1: [onCreateView]
Fragment1: [onViewCreated] BEGIN
Fragment1: [onViewCreated] END
Fragment1: [onActivityCreated] BEGIN
Fragment1: [onActivityCreated] END
Fragment1: [onStart] BEGIN
Fragment1: [onStart] END
BasicActivity: [onStart] END
BasicActivity: [onPostCreate] BEGIN
BasicActivity: [onPostCreate] END
BasicActivity: [onResume] BEGIN
BasicActivity: [onResume] END
BasicActivity: [onPostResume] BEGIN
Fragment1: [onResume] BEGIN
Fragment1: [onResume] END
BasicActivity: [onPostResume] END
BasicActivity: [onAttachedToWindow] BEGIN
BasicActivity: [onAttachedToWindow] END
可以看出:

Fragment的onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()都是在Activity的onStart()中调用的。
Fragment的onResume()在Activity的onResume()之后调用。
接下去分两种情况,分别是不加addToBackStack()和加addToBackStack()。

1、当点击F1的按钮,调用replace()替换为F2,且不加addToBackStack()时,日志如下:

Fragment2: [onAttach] BEGIN
Fragment2: [onAttach] END
BasicActivity: [onAttachFragment] BEGIN
BasicActivity: [onAttachFragment] END
Fragment2: [onCreate] BEGIN
Fragment2: [onCreate] END
Fragment1: [onPause] BEGIN
Fragment1: [onPause] END
Fragment1: [onStop] BEGIN
Fragment1: [onStop] END
Fragment1: [onDestroyView] BEGIN
Fragment1: [onDestroyView] END
Fragment1: [onDestroy] BEGIN
Fragment1: [onDestroy] END
Fragment1: [onDetach] BEGIN
Fragment1: [onDetach] END
Fragment2: [onCreateView]
Fragment2: [onViewCreated] BEGIN
Fragment2: [onViewCreated] END
Fragment2: [onActivityCreated] BEGIN
Fragment2: [onActivityCreated] END
Fragment2: [onStart] BEGIN
Fragment2: [onStart] END
Fragment2: [onResume] BEGIN
Fragment2: [onResume] END
可以看到,F1最后调用了onDestroy()和onDetach()。

2、当点击F1的按钮,调用replace()替换为F2,且加addToBackStack()时,日志如下:

Fragment2: [onAttach] BEGIN
Fragment2: [onAttach] END
BasicActivity: [onAttachFragment] BEGIN
BasicActivity: [onAttachFragment] END
Fragment2: [onCreate] BEGIN
Fragment2: [onCreate] END
Fragment1: [onPause] BEGIN
Fragment1: [onPause] END
Fragment1: [onStop] BEGIN
Fragment1: [onStop] END
Fragment1: [onDestroyView] BEGIN
Fragment1: [onDestroyView] END
Fragment2: [onCreateView]
Fragment2: [onViewCreated] BEGIN
Fragment2: [onViewCreated] END
Fragment2: [onActivityCreated] BEGIN
Fragment2: [onActivityCreated] END
Fragment2: [onStart] BEGIN
Fragment2: [onStart] END
Fragment2: [onResume] BEGIN
Fragment2: [onResume] END
可以看到,F1被替换时,最后只调到了onDestroyView(),并没有调用onDestroy()和onDetach()。当用户点返回按钮回退事务时,F1会调onCreateView()->onStart()->onResume(),因此在Fragment事务中加不加addToBackStack()会影响Fragment的生命周期。

FragmentTransaction有一些基本方法,下面给出调用这些方法时,Fragment生命周期的变化:

add(): onAttach()->…->onResume()。
remove(): onPause()->…->onDetach()。
replace(): 相当于旧Fragment调用remove(),新Fragment调用add()。
show(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。
hide(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。
detach(): onPause()->onStop()->onDestroyView()。UI从布局中移除,但是仍然被FragmentManager管理。
attach(): onCreateView()->onStart()->onResume()。

通讯

Fragment向Activity传递数据

首先,在Fragment中定义接口,并让Activity实现该接口(具体实现省略):

public interface OnFragmentInteractionListener {
    void onItemClick(String str);  //将str从Fragment传递给Activity
}

在Fragment的onAttach()中,将参数Context强转为OnFragmentInteractionListener对象:

public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

并在Fragment合适的地方调用mListener.onItemClick("hello")将”hello”从Fragment传递给Activity。

FABridge

由于通过接口的方式从Fragment向Activity进行数据传递比较麻烦,需要在Fragment中定义interface,并让Activity实现该interface,FABridge通过注解的形式免去了这些定义。

在build.gradle中添加依赖:

annotationProcessor 'com.zhy.fabridge:fabridge-compiler:1.0.0'
compile 'com.zhy.fabridge:fabridge-api:1.0.0'

首先定义方法ID,这里为FAB_ITEM_CLICK,接着在Activity中定义接口:

@FCallbackId(id = FAB_ITEM_CLICK)
public void onItemClick(String str) {  //方法名任意
    Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}

最后,在Fragment中,通过以下形式调用”ID=FAB_ITEM_CLICK”的方法(该方法可能在Activity中,也可能在任何类中):

Fabridge.call(mActivity,FAB_ITEM_CLICK,"data");  //调用ID对应的方法,"data"为参数值

Activity向Fragment传递数据

Activity向Fragment传递数据比较简单,获取Fragment对象,并调用Fragment的方法即可,比如要将一个字符串传递给Fragment,则在Fragment中定义方法:

public void setString(String str) {
     this.str = str;
}

并在Activity中调用fragment.setString("hello")即可。

Fragment之间通信

由于Fragment之间是没有任何依赖关系的,因此如果要进行Fragment之间的通信,建议通过Activity作为中介,不要Fragment之间直接通信。

DialogFragment

DialogFragment是Android 3.0提出的,代替了Dialog,用于实现对话框。他的优点是:即使旋转屏幕,也能保留对话框状态。

如果要自定义对话框样式,只需要继承DialogFragment,并重写onCreateView(),该方法返回对话框UI。这里我们举个例子,实现进度条样式的圆角对话框。

public class ProgressDialogFragment extends DialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); //消除Title区域
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));  //将背景变为透明
        setCancelable(false);  //点击外部不可取消
        View root = inflater.inflate(R.layout.fragment_progress_dialog, container);
        return root;
    }

    public static ProgressDialogFragment newInstance() {
        return new ProgressDialogFragment();
    }
}

进度条动画我们使用Lottie实现,Lottie动画从这里找到。使用非常方便,只需要下载JSON动画文件,然后在XML中写入:

<com.airbnb.lottie.LottieAnimationView
    android:layout_width="wrap_content"  //大小根据JSON文件确定
    android:layout_height="wrap_content"
    app:lottie_fileName="loader_ring.json"   //JSON文件
    app:lottie_loop="true"    //循环播放
    app:lottie_autoPlay="true" />  //自动播放

然后通过下面代码显示对话框:

ProgressDialogFragment fragment = ProgressDialogFragment.newInstance();
fragment.show(getSupportFragmentManager(), "tag");
//fragment.dismiss();
为了实现圆角,除了在onCreateView()中把背景设为透明,还需要对UI加入背景:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff"/>
    <corners
        android:radius="20dp"/>
</shape>

匿名函数

作用域

在javascript中,是没有块级作用域这种说法的,以上代码的这种方式就是模仿了块级作用域(通常成为私有作用域),语法如下所示:

     (function(){
         //这里是块级作用域
     })();

关于括号

 function(){
         
     }();

上面的代码是错误的,因为Javascript将function关键字当作一个函数声明的开始,而函数声明后面不能加圆括号,如果你不显示告诉编译器,它会默认生成一个缺少名字的function,并且抛出一个语法错误,因为function声明需要一个名字。有趣的是,即便你为上面那个错误的代码加上一个名字,他也会提示语法错误,只不过和上面的原因不一样。在一个表达式后面加上括号(),该表达式会立即执行,但是在一个语句后面加上括号(),是完全不一样的意思,他的只是分组操作符(此处摘自汤姆大叔的博客)。

// 下面这个function在语法上是没问题的,但是依然只是一个语句
// 加上括号()以后依然会报错,因为分组操作符需要包含表达式
 
function foo(){ /* code */ }(); // SyntaxError: Unexpected token )
 
// 但是如果你在括弧()里传入一个表达式,将不会有异常抛出
// 但是foo函数依然不会执行
function foo(){ /* code */ }( 1 );
 
// 因为它完全等价于下面这个代码,一个function声明后面,又声明了一个毫无关系的表达式: 
function foo(){ /* code */ }
 
( 1 );

所以上面代码要是想要实现,就必须要实现赋值,如a = function(){}(),"a="这个告诉了编译器这个是一个函数表达式,而不是函数的声明。因为函数表达式后面可以跟圆括号。所以下面两段代码是等价的。

        var aa = function(x){
            alert(x);
        }(5);//5

(function(x){alert(x);})(5);//5
有上面对于函数和匿名函数的了解,我们引申出来了一个概念,即自执行函数,让我们更加深入的了解为什么。a = function(){}()这个表示可以让编译器认为这个是一个函数表达式而不是一个函数的声明。

自执行函数

我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
自执行函数,即定义和调用合为一体。下面我们来看下一下自执行函数的一些表达方式,下面一些专业的讲法摘自汤姆大叔的博客:

// 下面2个括弧()都会立即执行
 
(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的
 
// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了
// 不过,请注意下一章节的内容解释
 
var i = function () { return 10; } ();
true && function () { /* code */ } ();
0, function () { /* code */ } ();
 
// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号
 
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
 
// 还有一个情况,使用new关键字,也可以用,但我不确定它的效率
// http://twitter.com/kuvos/status/18209252090847232
 
new function () { /* code */ }
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()

上面所说的括弧是消除歧义的,其实压根就没必要,因为括弧本来内部本来期望的就是函数表达式,但是我们依然用它,主要是为了方便开发人员阅读,当你让这些已经自动执行的表达式赋值给一个变量的时候,我们看到开头有括弧(,很快就能明白,而不需要将代码拉到最后看看到底有没有加括弧。
即要是想要这样function(){}()来实现自执行,可以用一些操作符在function的前面来消除歧义。

function(x){
    alert(x);
}(5);//报错,function name expected
 
var aa = function(x){
    alert(x);
}(1);//1
 
true && function(x){
    alert(x);
}(2);//2
 
0, function(x){
    alert(x);
}(3);//3
 
!function(x){
    alert(x);
}(4);//4
 
~function(x){
    alert(x);
}(5);//5
 
-function(x){
    alert(x);
}(6);//6
 
+function(x){
    alert(x);
}(7);//7
 
 new function (){
     alert(8);//8
 }
 
  new function (x){
    alert(x);
}(9);//9
很多情况下,可以利用自执行函数和闭包来保存某个特殊状态中的值,具体想看下方讲解。

问题

  1. 为什么通常匿名函数都需要传入this?
    因为引用的环境不定,有可能是浏览器,有可能是node,引入this才能找到正确的全局

swift3升级4遇到的坑

https://blog.csdn.net/shmilycoder/article/details/77948797
升级Swift4.0
并不是所有库都能做到及时支持Swift4.0,更何况是在现在连Xcode9也还是beta的状态
所以我们仅能做到将自己的业务代码(主工程代码)部分升级到Swift4.0,然后同时保留各种pod库在Swift3.2版本。
没办法,谁叫Swift4.0也还无法做到API兼容呢(但愿能在Swift5之前实现吧)。
至于我说的同时使用两个版本的Swift,这是没问题的,Xcode9支持在项目中同时使用Swift3.2和Swift4.0。

一. 修改Swift版本

  1. 如下图指定主工程的Swift版本为4.0

Xcode图示.png
2. 修改pod库
在Podfile文件的最下方加入如下代码,指定pod库的Swift版本为3.2(这样会使得所有的第三方pod库的Swift版本都为3.2)

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.2'
    end 
  end
end

二. 主工程中的代码修改

  1. 列举一下Swift3.2到Swift4.0的改变(只是我项目中遇到的):
    1). Swift4.0中对于扩展的属性(包括实例属性、static属性、class属性),都只能使用get方法,不可使用set方法

2). Swift4.0中不再允许复写扩展中的方法(包括实例方法、static方法、class方法)

比如:自定义的协议方法在extension中实现,若某个类遵循了该协议,其子类便不能重写该协议方法
解决的方法是: 在每个需要该协议的类里面都重新遵循该协议,实现协议方法
个人想到的办法,不知道有没有其他解决办法可以提供一下
3). swift3使用#selector指定的方法,只有当方法权限为private时需要加@objc修饰符,现在Swift4.0全都要加@objc修饰符

4). 自定义的protocol协议中,有optional修饰的非必须实现的方法,需要用@objc修饰

5). 字体方面的一些重命名

NSFontAttributeName --- .font 
//或者NSAttributedStringKey.font
 
NSForegroundColorAttributeName --- .foregroundColor
//NSAttributedStringKey.foregroundColor
 
NSStrikethroughStyleAttributeName --- .strikethroughStyle
//NSAttributedStringKey.strikethroughStyle
 
//字符串类型的,添加rawValue
NSAttributedStringKey.font.rawValue
 
//等等等等..........
 
//大部分类似以下,涉及富文本的方法均已改为了NSAttributedStringKey类型
addAttributes(_ attrs: [NSAttributedStringKey : Any] = [:], range: NSRange)

三. 项目中遇到的一些的报错问题

3-1. "Closure cannot implicitly capture a mutating self parameter"错误

在struct中,如果我们在闭包中使用self,就会得到Closure cannot implicitly capture a mutating self parameter的错误提示。比如:

struct RecordModel {
    /// 定义一个闭包
    var action: (() -> ())?
    var height = 10
    
    self.action = { 
        self.height = 20 
        //Closure cannot implicitly capture a mutating self parameter报错
    }
}

++并且由于RecordModel的类型是struct,我们也没发在action闭包里添加截获列表。那么是不是就必须使用class了?答案是否定的。有两种方式可以解决这个问题。++
方案一:为closure增加一个inout类型的参数

struct RecordModel {
   /// 定义一个闭包
   var action: ((_ inSelf: inout RecordModel) -> ())?
   var height = 10
   
   self.action = { (inSelf) in
       inSelf.height = 20 
   }
}

根据inout类型的说明,我们知道,实际上这相当于增加了一个隐藏的临时变量,self被复制,然后在closure(闭包)中使用,完成后,再复制回self。也就是说,这个方法有额外的内存开销。如果是struct较大的情形,这么做并不划算。
方案二:使用UnsafeMutablePointer
==这次采用直接指针的方式对于struct来进行操作,采用指针的好处是self不会被多次复制,性能较高。缺点是你需要自行确定你的代码的安全。==

struct RecordModel {
   /// 定义一个闭包
   var action: (() -> ())?
   var height = 10
   
   let selfPointer = UnsafeMutablePointer(&self)
   self.action = { 
       selfPointer.pointee.height = 20 
       
   }
}

结论
==Closure cannot implicitly capture a mutating self parameter错误的原因是在进出closure(闭包)之后,self的一致性没办法得到保证,所以编译器默认不允许在struct的closure(闭包)中使用self。如果我们确定这么做是安全的,就可以通过上面的两种方式解决这个问题。其中,方法二的性能更好一些。==

注意
这里可以记一下指针和swift变量之间的关系:
UnsafePointer对应let
UnsafeMutablePointer对应var
AutoreleasingUnsafeMutablePointer对应unowned UnsafeMutablePointer,用于inout的参数类型
UnsafeRawPointer对应let Any,raw系列都是对应相应的Any类型
UnsafeBufferPointer是non-owning的类型(unowned),用于collection的elements, buffer系列均如此

3-2. Declarations from extensions cannot be overridden yet 错误

==这个错误大致是因为,协议方法是在extension里面的,不能被重写==

解决办法:(仅供参考,如有更好的建议还望多多指教)

小编想到的解决办法就是在每一个需要此协议的类里面,重新遵循代理,实现该协议方法

3-3. "Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift"

==报错原因: 在于已经废弃的initialize方法,示例如下==

方法交叉(Method Swizzling)
有时为了方便,也有可能是解决某些框架内的 bug,或者别无他法时,需要修改一个已经存在类的方法的行为。方法交叉可以让你交换两个方法的实现,相当于是用你写的方法来重载原有方法,并且还能够是原有方法的行为保持不变。

extension UIViewController {
    public override class func initialize() {//此处报错
        
    //此处省略100行代码
        
    }
}

initialize该方法已经被Swift4.0废弃
在Swift3.0还勉强可以使用,但是会有警告;但是在4.0已经被完全废弃
==替代方法:==

在 app delegate 中实现方法交叉
像上面通过类扩展进行方法交叉,而是简单地在 app delegate 的 application(_:didFinishLaunchingWithOptions:) 方法调用时调用该方法
extension UIViewController {
public override class func initializeOnceMethod() {

//此处省略100行代码
    
}

}

//在AppDelegate的方法中调用:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    //此处省略100行代码
    UIViewController.initializeOnceMethod()
 
}

3-4. 'dispatch_once' is unavailable in Swift: Use lazily initialized globals instead

报错原因: dispatch_once在Swift4.0也已经被废弃

extension UITableView {
    struct once{
        static var onceTaken:Int = 0
    }
    dispatch_once(&once.onceTaken) { () -> Void in
    //在这里dispatch_once就会报错
        //此处省略1000000行代码    
    }
}

解决方法: 通过给DispatchQueue添加扩展实现

extension DispatchQueue {
    private static var _onceTracker = [String]()
    public class func once(token: String, block: () -> ()) {
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
        }
        if _onceTracker.contains(token) {
            return
        }
        _onceTracker.append(token)
        block()
    }
    
    func async(block: @escaping ()->()) {
        self.async(execute: block)
    }
    
    func after(time: DispatchTime, block: @escaping ()->()) {
        self.asyncAfter(deadline: time, execute: block)
    }
}

使用字符串token作为once的ID,执行once的时候加了一个锁,避免多线程下的token判断不准确的问题。
使用的时候可以传token

 DispatchQueue.once(token: "tableViewOnce") {
     print( "Do This Once!" )  
 }

或者使用UUID也可以:

private let _onceToken = NSUUID().uuidString
  
DispatchQueue.once(token: _onceToken) {  
    print( "Do This Once!" )  
}

四、swift3.2升级到swift4.0 扫码不走回调方法

xcode升级到9.0 swift改到swift4.0之后扫码一直不走回调 ,研究了好长时间,发现苹果把扫码的代理方法的参数变了之前的方法

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!)

这是之前swift3.2的代理方法,swift4.0之后不会走这两个代理方法,原因是现在代理方法不一样了

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)

swift4.0的两个代理方法对比之前的3.2方法,可以发现现在方法的参数变了

将之前的两个方法的参数改好,扫码就可以正常用了

css

组合

  1. 两行打点
overflow:hidden; 
text-overflow:ellipsis;
display:-webkit-box; //将对象作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; //从上到下垂直排列子元素(设置伸缩盒子的子元素排列方式)
-webkit-line-clamp:2; //这个属性不是css的规范属性,需要组合上面两个属性,表示显示的行数。
  1. 一行打点
overflow:hidden; //超出的文本隐藏
text-overflow:ellipsis; //溢出用省略号显示
white-space:nowrap; //溢出不换行

PT与PX和dpi

字体大小的设置单位,常用的有2种:px、pt。这两个有什么区别呢?

先搞清基本概念:px就是表示pixel,像素,是屏幕上显示数据的最基本的点;

pt就是point,是印刷行业常用单位,等于1/72英寸。

这样很明白,px是一个点,它不是自然界的长度单位,谁能说出一个“点”有多长多大么?可以画的很小,也可以很大。如果点很小,那画面就清晰,我们称它为“分辨率高”,反之,就是“分辨率低”。所以,“点”的大小是会“变”的,也称为“相对长度”。

pt全称为point,但中文不叫“点”,查金山词霸可以看到,确切的说法是一个专用的印刷单位“磅”,大小为1/72英寸。所以它是一个自然界标准的长度单位,也称为“绝对长度”。

因此就有这样的说法,pixel是相对大小,而point是绝对大小。

分清“屏幕效果”和“打印效果”:

在浏览网页过程中,所有的“大”“小”概念,都是基于“屏幕”这个“界面”上。“屏幕”上的各种信息,包括文字、图片、表格等等,都会随屏幕的分辨率变化而变化,一个100px宽度大小的图片,在800×600分辨率下,要占屏幕宽度的1/8,但在1024×768下,则只占约1/10。所以如果在定义字体大小时,使用px作为单位,那一旦用户改变显示器分辨率从800到1024,用户实际看到的文字就要变“小”(自然长度单位),甚至会看不清,影响浏览。

那是不是用pt做单位就没这样的问题呢?错!问题同样出现。刚才的例子已经很清楚的说明,在不同分辨率下,无论是px还是pt,都会改变大小。以现在的电脑屏幕情况,还没有一种单位可以保证,在不同分辨率下,一个文字大小可以“固定不变”。因为这很难以实现也不是很有必要:全球电脑用户以亿来数,屏幕从14寸到40寸甚至更高都有,屏幕大小不同,分辨率也不同,要保证一个字体在所有用户面前大小一样,实在是MISSION IMPOSSIBLE。另外,电脑有其自身的调节性。

那在页面设计中到底是用px还是pt呢?

我认为,这个并没有什么原则性差异,就看自己处于什么角度思考了。

Mac机怎么情况不清楚,在Windows里,默认的显示设置中,把文字定义为96DPI(PPI,微软都将DPI和PPI混为一体,我们也就无须较真了)。这样的定义,说明了:1px=1/96英寸。联系pt的概念,1pt=1/72英寸,可以得出,在这样的设置中,1px=0.75pt,常见的宋体9pt=12px。在显示器分辨率不变的基础上(比如现在常用的1024×768),1px大小也就固定不变,改变显示设置,调整为144DPI,可以得出,1px=0.5pt,常见的宋体9pt=18px。原先用12px来组成的一个文字,现在需要18px来组成,px多了,文字就“大”了,更易阅读了。

所以,px和pt的使用区别,只有当用户改变默认的96DPI下才会产生:使用px定义文字,无论用户怎么设置,都不会改变大小;使用pt定义文字,当用户设置超过96DPI的值,数值越大,字体就越大。

  (附公式:px = pt * DPI / 72) 对了,刚才还提到改变浏览器中文字大小的选项,也可以改变网页的文字大小。但在这种情况下,使用px和pt都是无效的,因为这2个都是有实际“pixel”数值的单位,比如9pt是12px,大小固定。这里要引用新的单位:em,其实就是%。因为当网页中的字体没有给出实际的px或pt定义的话,会有一个默认值:12pt即16px,对应浏览器中“字体大小”中的“中等”,以这个为标准,变大或缩小。(只适用于IE,在FF中,即便定义px或pt也都可以变大变小)

所以,从这个概念上看,em才是真正的“相对单位”(百分比嘛,当然是相对),而px和pt都是绝对单位(都有固定值)。

在网页设计中,面向用户的屏幕的基本单位是px,因此使用px作为单位是最简单也最容易理解的,而pt也不过是通过了Windows的设置乘上了一个比率转变成px再显示,算是绕了个圈子。参考大部分大型网站,包括Adobe和Microsoft,都是使用px作为单位,而且在HTML中,默认的单位就是px,是不是也暗示着px是网页设计的“内定单位”?

  但在Word或Photoshop中,使用pt就相当方便。因为使用Word和Photoshop的主要目的都不是为了屏幕浏览,而是输出打印。当打印到实体时,pt作为一个自然长度单位就方便实用了:比如Word中普通的文档都用“宋体 9pt”,标题用“黑体 16pt”等等,无论电脑怎么设置,打印出来永远就是这么大。又或者在Photoshop中,设置一个图片中的某个艺术效果的字体是72pt大小,然后分别将这张图片设为300DPI和72DPI,再打印出来,就可以看出,这2个字体大小完全一样,只是“清晰度”不同,300DPI更清晰。这是毫无疑问的结果。

  最后整理一下所有出现过的单位:

  px:pixel,像素,屏幕上显示的最小单位,用于网页设计,直观方便;

  pt:point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用;

  em:即%,在CSS中,1em=100%,是一个比率,结合CSS继承关系使用,具有灵活性。

  PPI(DPI):pixel(dot)per inch,每英寸的像素(点)数,是一个率,表示了“清晰度”,“精度”

PX和PT转换的公式:

以前在文章中介绍过PX和PT的转换规则,其实很简单,pt=px乘以3/4。

比如12px×3/4=9pt大小。

PX和em转换的公式:

对于PX转em的方法也类似,就是em=16乘以px,也就是说1.5em=1.5×16=24px。

设计中常用PX/EM/PT/百分比转换表格

Pixels
EMs
Percent
Points

6px
0.375em
37.50%
5pt

7px
0.438em
43.80%
5.5pt

8px
0.5em
50%
6pt

9px
0.563em
56.30%
7pt

10px
0.625em
62.50%
8pt

11px
0.688em
68.80%
8pt

12px
0.75em
75%
9pt

13px
0.813em
81.30%
10pt

14px
0.875em
87.50%
11pt

15px
0.938em
93.80%
11pt

16px
1em
100%
12pt

17px
1.063em
106.30%
13pt

18px
1.125em
112.50%
14pt

19px
1.188em
118.80%
14pt

20px
1.25em
125%
15pt

21px
1.313em
131.30%
16pt

22px
1.375em
137.50%
17pt

23px
1.438em
143.80%
17pt

24px
1.5em
150%
18pt

字号 磅数 毫米

八号 5磅 1.84

七号 5.5磅 2.12

小六号 6.5磅 2.45

六号 7.5磅 2.81

小五号 9磅 3.15

五号 10.5磅 3.70

小四号 12磅 4.25

四号 14磅 4.93

小三号 15磅 5.23

三号 16磅 5.55

小二号 18磅 6.37

二号 22磅 7.80

小一号 24磅 8.42

一号 26磅 9.66

小初号 36磅 11.1

初号 42磅 12.7

(附公式:px = pt * DPI / 72)

codova学习记录

文件

  1. .gradle 文件夹,此文件夹是用来保存gradle的依赖信息。
  2. 所有的 build 文件夹,build文件夹是用来保存编译后的文件目录。
  3. 所有的 .iml 文件,是用来保存开发工具信息。
  4. local.properties 文件,是用来保存项目依赖信息

gradle

  1. build.gradle详解

RN缺点

  1. 不能完全兼容css,很多css属性用不了(RN实现了一套Native样式),如:position: absolute
  2. 有一套RN专用组件,只能用来写native,H5还需另外写
  3. RN组件出问题很难调试(虽然很低概率),可能在复杂交互有些无力
  4. 动画或游戏性能低

ios越狱

注意

  1. 所有的越狱软件基本都是基于盘古越狱
  2. 越狱完会安装cydia
  3. 越狱应用有pp助手,itools,爱思(推荐)
  4. 越狱完后重启又会变成不越狱状态,要按照爱思的地址再处理一下
  5. 越狱完后还是不能马上看到系统根目录,要安装apple file conduit "2"
  6. 目前只支持9.3一下的越狱。越狱失败只能刷11.几的系统并且不能再降级和越狱

babel源码解读

原理

babel是一个转译器,感觉相对于编译器compiler,叫转译器transpiler更准确,因为它只是把同种语言的高版本规则翻译成低版本规则,而不像编译器那样,输出的是另一种更低级的语言代码。
但是和编译器类似,babel的转译过程也分为三个阶段:parsingtransforminggenerating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:

ES6代码输入 ==》 babylon进行解析 ==》 得到AST
==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树
==》 用babel-generator通过AST树生成ES5代码

解析(parse,生产ast),转换(transform,es6的AST转为ES5的AST),生成(generate,生成的AST转化为代码)

此外,还要注意很重要的一点就是,babel只是转译新标准引入的语法,比如ES6的箭头函数转译成ES5的函数;而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的API等(如Proxy、Set等),这些babel是不会转译的。需要用户自行引入polyfill来解决

plugins(自己手写的插件)

插件应用于babel的转译过程,尤其是第二个阶段transforming,如果这个阶段不使用任何插件,那么babel会原样输出代码。
我们主要关注transforming阶段使用的插件,因为transform插件会自动使用对应的词法插件,所以parsing阶段的插件不需要配置。

presets(babel现有的插件集,一般官方提供)

如果要自行配置转译过程中使用的各类插件,那太痛苦了,所以babel官方帮我们做了一些预设的插件集,称之为preset,这样我们只需要使用对应的preset就可以了。以JS标准为例,babel提供了如下的一些preset:

  • es2015
  • es2016
  • es2017
  • env
    es20xx的preset只转译该年份批准的标准,而env则代指最新的标准,包括了latest和es20xx各年份
    另外,还有 stage-0到stage-4的标准成形之前的各个阶段,这些都是实验版的preset,建议不要使用。

polyfill

polyfill是一个针对ES2015+环境的shim,实现上来说babel-polyfill包只是简单的把core-js和regenerator runtime包装了下,这两个包才是真正的实现代码所在(后文会详细介绍core-js)。
使用babel-polyfill会把ES2015+环境整体引入到你的代码环境中,让你的代码可以直接使用新标准所引入的新原生对象,新API等,一般来说单独的应用和页面都可以这样使用。

使用方法

先安装包: npm install --save babel-polyfill
要确保在入口处导入polyfill,因为polyfill代码需要在所有其他代码前先被调用
代码方式: import "babel-polyfill"
webpack配置: module.exports = { entry: ["babel-polyfill", "./app/js"] };

如果只是需要引入部分新原生对象或API,那么可以按需引入,而不必导入全部的环境,具体见下文的### core-js
runtime
polyfill和runtime的区别
直接使用babel-polyfill对于应用或页面等环境在你控制之中的情况来说,并没有什么问题。但是对于在library中使用polyfill,就变得不可行了。因为library是供外部使用的,但外部的环境并不在library的可控范围,而polyfill是会污染原来的全局环境的(因为新的原生对象、API这些都直接由polyfill引入到全局环境)。这样就很容易会发生冲突,所以这个时候,babel-runtime就可以派上用场了。

transform-runtime和babel-runtime
babel-plugin-transform-runtime插件依赖babel-runtime,babel-runtime是真正提供runtime环境的包;也就是说transform-runtime插件是把js代码中使用到的新原生对象和静态方法转换成对runtime实现包的引用,举个例子如下:

// 输入的ES6代码
var sym = Symbol();
// 通过transform-runtime转换后的ES5+runtime代码 
var _symbol = require("babel-runtime/core-js/symbol");
var sym = (0, _symbol.default)();

从上面这个例子可见,原本代码中使用的ES6新原生对象Symbol被transform-runtimec插件转换成了babel-runtime的实现,既保持了Symbol的功能,同时又没有像polyfill那样污染全局环境(因为最终生成的代码中,并没有对Symbol的引用)
另外,这里我们也可以隐约发现,babel-runtime其实也不是真正的实现代码所在,真正的代码实现是在core-js中,后面我们再说

  • 一个是转化的包(插件),一个是充满polyfill的包。transform是使用包的工具
  • babel-runtime和 babel-plugin-transform-runtime的区别是,相当一前者是手动挡而后者是自动挡,每当要转译一个api时都要手动加上require('babel-runtime'),而babel-plugin-transform-runtime会由工具自动添加,主要的功能是为api提供沙箱的垫片方案,不会污染全局的api,因此适合用在第三方的开发产品中。
  • runtime相对于polyfile是不会污染全局空间,但还是需要手动去用,而transform-runtime是自动识别新的对象并转化,不需要自己去手动转。
  • 在大多数情况下,你需要安装babel-plugin-transform-runtime作为开发版本的依赖(设置--save-dev)。并且babel-runtime作为生产版本依赖(设置 --save)。区分dev和product,github上很多项目的常用做法吧。babel运行时会自动查找.babelrc文件然后读取理念的设置。如果你的项目需要dev和pro两个环境不同的设置,那么常用做法是设置两个不同的.babelrc文件。然后设置全局变量值到process.env[projectName]='dev' or'pro'。当项目运行build命令时,process.env[projectName]的值设置成pro然后去读取响应模式的.babelrc配置文件,接下来的编译就这个方式生效

transform-runtime插件的功能

把代码中的使用到的ES6引入的新原生对象和静态方法用babel-runtime/core-js导出的对象和方法替代
当使用generators或async函数时,用babel-runtime/regenerator导出的函数取代(类似polyfill分成regenerator和core-js两个部分)
把Babel生成的辅助函数改为用babel-runtime/helpers导出的函数来替代(babel默认会在每个文件顶部放置所需要的辅助函数,如果文件多的话,这些辅助函数就在每个文件中都重复了,通过引用babel-runtime/helpers就可以统一起来,减少代码体积)

上述三点就是transform-runtime插件所做的事情,由此也可见,babel-runtime就是一个提供了regenerator、core-js和helpers的运行时库。
建议不要直接使用babel-runtime,因为transform-runtime依赖babel-runtime,大部分情况下都可以用transform-runtime达成目的。
此外,transform-runtime在.babelrc里配置的时候,还可以设置helpers、polyfill、regenerator这三个开关,以自行决定runtime是否要引入对应的功能。
最后补充一点:由于runtime不会污染全局空间,所以实例方法是无法工作的(因为这必须在原型链上添加这个方法,这是和polyfill最大的不同) ,比如:

var arr = ['a', 'b', 'c'];
arr.fill(7);  // 实例方法不行
Array.prototype.fill.apply(arr, 7);  // 用原型链来调用也是不行

runtime转换器插件主要做了三件事:

* 当你使用generators/async方法、函数时自动调用babel-runtime/regenerator
* 当你使用ES6 的Map或者内置的东西时自动调用babel-runtime/core-js
* 移除内联babel helpers并替换使用babel-runtime/helpers来替换

transform-runtime优点

* 不会污染全局变量
* 多次使用只会打包一次
* 依赖统一按需引入,无重复引入,无多余引入

transform-runtime缺点

* 不支持实例化的方法Array.includes(x) 就不能转化
* 如果使用的API用的次数不是很多,那么transform-runtime 引入polyfill的包会比不是transform-runtime 时大

通过core-js实现按需引入polyfill或runtime

core-js包才上述的polyfill、runtime的核心,因为polyfill和runtime其实都只是对core-js和regenerator的再封装,方便使用而已。
但是polyfill和runtime都是整体引入的,不能做细粒度的调整,如果我们的代码只是用到了小部分ES6而导致需要使用polyfill和runtime的话,会造成代码体积不必要的增大(runtime的影响较小)。所以,按需引入的需求就自然而然产生了,这个时候就得依靠core-js来实现了。

core-js的组织结构

首先,core-js有三种使用方式:

  • 默认方式:require('core-js')
  • 这种方式包括全部特性,标准的和非标准的
  • 库的形式: var core = require('core-js/library')
    这种方式也包括全部特性,只是它不会污染全局名字空间
  • 只是shim: require('core-js/shim')或var shim = require('core-js/library/shim')
    这种方式只包括标准特性(就是只有polyfill功能,没有扩展的特性)

core-js的结构是高度模块化的,它把每个特性都组织到一个小模块里,然后再把这些小模块组合成一个大特性,层层组织。比如:
core-js/es6(core-js/library/es6)就包含了全部的ES6特性,而core-js/es6/array(core-js/library/es6/array)则只包含ES6的Array特性,而core-js/fn/array/from(core-js/library/fn/array/from)则只有Array.from这个实现。
实现按需使用,就是自己选择使用到的特性,然后导入即可。具体的每个特性和对应的路径可以直接查看core-js的github

core-js的按需使用

1、类似polyfill,直接把特性添加到全局环境,这种方式体验最完整

require('core-js/fn/set');
require('core-js/fn/array/from');
require('core-js/fn/array/find-index');

Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
[1, 2, NaN, 3, 4].findIndex(isNaN);   // => 2

2、类似runtime一样,以库的形式来使用特性,这种方式不会污染全局名字空间,但是不能使用实例方法

var Set       = require('core-js/library/fn/set');
var from      = require('core-js/library/fn/array/from');
var findIndex = require('core-js/library/fn/array/find-index');

from(new Set([1, 2, 3, 2, 1]));      // => [1, 2, 3]
findIndex([1, 2, NaN, 3, 4], isNaN); // => 2

3、因为第二种库的形式不能使用prototype方法,所以第三种方式使用了一个小技巧,通过::这个符号而不是.来调用实例方式,从而达到曲线救国的目的。这种方式的使用,路径中都会带有/virtual/

import {fill, findIndex} from 'core-js/library/fn/array/virtual';

Array(10)::fill(0).map((a, b) => b * b)::findIndex(it => it && !(it % 8)); // => 4

// 对比下polyfill的实现 
// Array(10).fill(0).map((a, b) => b * b).findIndex(it => it && !(it % 8));

引用

Babel是如何读懂JS代码的
the-super-tiny-compiler

常用linux命令

  1. 删除文件夹 rm -rf xxx
  2. 查看node进程
    ps aux | grep node
  3. 查看当前命令所在的路径
    pwd
  4. 查看安装的程序所在的路径
    which node

Reactivecocoa

https://www.jianshu.com/p/c35ef5a4ab63

一.UITextField输入文本监听

比如有这样一个

lazy var accountTextField : UITextField = {
        let textField = UITextField(frame: CGRect(x:30, y:200, width:200, height:30))
        //设置边框样式为圆角矩形
        textField.borderStyle = UITextBorderStyle.roundedRect
        return textField
    }()

1.假如你的界面有一个UITextField文本框,你需要对其输入内容进行监听。

accountTextField.reactive.continuousTextValues.observeValues { (text) in
        
                    print(text ?? "")
        
                }

2.假如你不想知道UITextField的文本内容,而是想知道文本的长度,那么使用map函数进行信号内容的修改,然后再对map后的信号进行观察.map函数可以对信号的内容进行转换,他的返回值可以是任何你想要的类型.

 accountTextField.reactive.continuousTextValues.map { (text) -> Int in
            
            return text!.characters.count
            
        }.observeValues { (count) in
            
            print(count)
            
        }

3.也许你只是在某个条件下,才想监听UITextfield文本的内容。比如:当文本的内容长度大于3的时候,你才想监听文本的内容。这时可以使用filter函数进行过滤操作。filter函数只能返回Bool类型。只有当filter函数返回true的时候,信号继续传递,我们才能监听到文本的内容;当filter函数返回False的时候,信号会被拦截。

accountTextField.reactive.continuousTextValues.filter { (text) -> Bool in
        
                    return text!.characters.count > 3
        
                }.observeValues { (text) in
       
                    print(text ?? "")
        
                }

二.UIButton点击事件监听.

 loginBtn.reactive.controlEvents(.touchUpInside).observeValues { (btn) in
            
            print("btn click")
            
        }

三.Combine Siganl (组合信号).

最经典的案例:登录界面的实现 一个账号文本框 一个密码文本框 当两个文本框内容的长度都大于3的时候 ,login按钮才可点击。

//首先创建账号文本框的signal ,然后使用map函数对信号内容进行转换。结果转换为Bool类型
let accountSignal = accountTextField.reactive.continuousTextValues.map { (text) -> Bool in
            
            return text!.characters.count > 3
        }
//密码文本框同理
let pwdSignal = pwdTextField.reactive.continuousTextValues.map { (text) -> Bool in
            
            return text!.characters.count > 3
        
        }
//使用combineLatest函数将两个信号组合为一个信号,然后利用map函数对信号进行转换。使用<~将信号的结果绑定到loginBtn.
loginBtn.reactive.isEnabled <~ accountSignal.combineLatest(with: pwdSignal).map({ $0 && $1
        })

如果你对$不熟悉,可以这样写

 let accountSignal = accountTextField.reactive.continuousTextValues.map { (text) -> Bool in
            
            return text!.characters.count > 3
        }
        
 let pwdSignal = pwdTextField.reactive.continuousTextValues.map { (text) -> Bool in
            
            return text!.characters.count > 3
        
        }
        
 loginBtn.reactive.isEnabled <~ accountSignal.combineLatest(with: pwdSignal).map({ (accountValid, pwdValid) -> Bool in
            return accountValid && pwdValid
        })

四.KVO VS MutableProperty

如果你想对某个属性的value进行observe.那么你可以使用KVO ,当然Reactivecocoa里的MutableProperty也可以满足你的需求,而且它比KVO用起来更加方便。

//<>里面可以是任意类型,它代表属性的类型。
// 这里定义一个int的racvalue并监听它,这个的意思是它能监听所有属性的改变
let racValue = MutableProperty<Int>(1)
        
racValue.producer.startWithValues { (make) in
           print(make)
   }
       
 racValue.value = 10

五.方法调用拦截

当你想获取到某个方法被调用的事件,比如UIViewController的ViewwillAppear事件。

 self.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:))).observeValues { () in
            
            print("viewWillAppear被调用了")
            
        }

六.监听对象的生命周期

比如你想在某个对象被销毁以后,做一些事情。那么你可以对这个对象的生命周期进行监听,也就是当对象销毁的时候,你获得对象销毁的信号,然后观察这个信号。当然你也可以重写deinit函数,当对象被销毁的时候,会调用deinit函数。也就是Objective-C中的dealloc方法。

//按钮点击 push到secondVC 
 loginBtn.reactive.controlEvents(.touchUpInside).observeValues { (btn) in
            
            let secondVC = SecondViewController()
            //当在secondVC pop的时候,secondVC会被销毁
            secondVC.reactive.lifetime.ended.observeCompleted {
                
                print("secondVC 被销毁")
                
            }
            
            self.navigationController?.pushViewController(secondVC, animated: true)
            
        }

promise

技术说明

prototype

  1. prototype的函数要实例化才能访问
  2. new方法会先于构建函数的执行,先new完创建好对象,绑定this,然后构建函数就能使用this了
  3. 类内的this是可以通过this.访问prototype原型属性的如this.bb
  4. new的优先级和.(成员访问)的优先级是一样的,具体见:优先级。new aa().bb()由于new在左边所以先执行new。但是无参new的优先级比.(成员访问)底,所以new aa.bb()是优先执行完bb()再new
function aa (){
  this.bb // 这里可以直接访问到bb 
}
aa.prototype.bb = () => {}
aa.bb // undefine
var cc = new aa();
cc.bb // function
// 这句会先执行new aa(),然后会调new出来的对象的bb属性,这个比较神奇
var dd = new aa().bb()

new原理

  1. 所有对象都可以通过__proto__遍历原型访问到它或它的父的属性
  2. new执行将this绑定到一个新的对象并执行构建函数,并将__proto__属性指定到类的prototype,最后返回那个新对象
    实现一个new
function new(F){
    var obj = {'__proto__': F.prototype};  /*第一步*/
    return function() {
        F.apply(obj, arguments);           /*第二步*/
        return obj;                        /*第三步*/
    }
}
// 或者
new Animal("cat") = {
    // 所有对象都继承自基础对象,所以原型链会找到基础对象
    var obj = {};
    obj.__proto__ = Animal.prototype;
    var result = Animal.call(obj,"cat");
    return typeof result === 'object'? result : obj;
}

实现

  1. then的执行会有一次深层递归,先执行完最顶层的then,在执行根的then
  2. promise像一坐闭包山,一层一层的,每一层都是then,层与层的区分考this绑定
  3. 主要有两层局部变量,一个是promise顶层,一个是resolve层。实际上就是执行一个函数,在函数执行的不同阶段修改promise闭包不同的局部变量。
  4. 主要有三步:1. 构建promise函数,2. 执行resolve函数,3. 检查有没有then,有则继续执行
  5. getThen主要是判断是否有深层嵌套promise的情况。因为一般只有在promise()就完成,然后利用new的对象执行then,比如:
// 不会再resovle里面传入一个promise作为参数,如果传入则需要使用getThen判断并深入嵌套
let myFirstPromise = new Promise((resolve, reject) => {
  setTimeout(function(){
    resolve("Success!"); // Yay! Everything went well!
  }, 250);
});
myFirstPromise.then((successMessage) => {
});
  1. 实现then传值的原理是,先执行异步函数得到值,然后判断是否有then有则传值到then继续执行,只是嵌套的情况。promise().then().then()的递归传值原理是,在执行then的时候,this还指向上一个promise,上一个promise有个this.data属性指向上一个promise的结果。然后通过bind,将this绑到下一个promise,并传值下去。
;(function(scope) {
    var PENDING = 'pending';
    var RESOLVED = 'resolved';
    var REJECTED = 'rejected';
    var UNDEFINED = void 0;

    function CallbackItem(promise, onResolved, onRejected) {
        this.promise = promise;
        this.onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
            return v };
        this.onRejected = typeof onRejected === 'function' ? onRejected : function(v) {
            throw v };
    }
    CallbackItem.prototype.resolve = function(value) {
        executeCallbackAsync.bind(this.promise)(this.onResolved, value);
    }
    CallbackItem.prototype.reject = function(value) {
        executeCallbackAsync.bind(this.promise)(this.onRejected, value);
    }
    // 如果有obj有then属性函数,则返回一个then函数,this绑定到obj,接受使用then时的参数
    function getThen(obj) {
        var then = obj && obj.then;
        if (obj && typeof obj === 'object' && typeof then === 'function') {
            return function appyThen() {
                // this绑定到boj并执行then方法
                then.apply(obj, arguments);
            };
        }
    }
    // 如果resolve(res)的res还是一个promise,则先执行完自己的resolve,再继续执行res的then方法,this绑定到res
    // 这里可以将res的所有then方法递归完
    function executeCallback(type, x) {
        var isResolve = type === 'resolve',
            thenable;
        // 如果客户使用resolve(res)时,res是一个函数或者对象,x就是res,则去判断
        // res是否有then属性函数,有则返回一个绑定res作为this的函数
        if (isResolve && (typeof x === 'object' || typeof x === 'function')) {
            try {
                // 如果x有then方法,则返回一个then函数,this绑定到x,参数是使用thenable函数时thenable函数的参数
                // thenable是一个函数
                thenable = getThen(x);
            } catch (e) {
                return executeCallback.bind(this)('reject', e);
            }
        }
        if (isResolve && thenable) {
            // 递归执行resolve(res)的res.then方法,并绑定this到第一个res上
            // 一开始this指向最外层的promise,所以执行完resolve后就能执行then了
            // 这里执行this所在的then
            executeResolver.bind(this)(thenable);
        } else {
            this.state = isResolve ? RESOLVED : REJECTED;
            this.data = x;
            this.callbackQueue.forEach(v => v[type](x));
        }
        return this;
    }
    //
    function executeResolver(resolver) {
        var called = false,
        // 把this存起来,让执行onSuccess还能访问到这个promise对象的属性
            _this = this;
        // 执行reject,没有返回
        function onError(y) {
            if (called) {
                return; }
            called = true;
            executeCallback.bind(_this)('reject', y);
        }
        // 执行resolve函数, 这里的执行不知道是什么时候的执行,因为是异步的
        // 这个函数是由用户去触发执行的,就是那个resolve(res);
        function onSuccess(r) {
            if (called) {
                return; }
            called = true;
            // 一开始this指向最外层的promise,所以执行完resolve后就能执行then了
            executeCallback.bind(_this)('resolve', r);
        }
        try {
            // 执行成功的函数是由封装的函数作为一个参数提供给用户使用的
            resolver(onSuccess, onError);
        } catch (e) {
            onError(e);
        }
    }

    function executeCallbackAsync(callback, value) {
        var _this = this;
        setTimeout(function() {
            var res;
            try {
                res = callback(value);
            } catch (e) {
                return executeCallback.bind(_this)('reject', e);
            }

            if (res !== _this) {
                return executeCallback.bind(_this)('resolve', res);
            } else {
                return executeCallback.bind(_this)('reject', new TypeError('Cannot resolve promise with itself'));
            }
        }, 4)
    }
    // 构建promise对象,并递归将resolver的resolve(res)的res的then函数递归执行完
    // 所以多层promise是优先执行最深层的then,再执行最外层的then
    // 具体如:
    /* var aa = new Promise().then();
    var bb = new Promise((resolve, reject) => {
      resolve(aa)
    }).then();
    var cc = new Promise((resolve, reject) => {
      resolve(bb)
    }).then();
    */
    // 会优先执行完aa的then,再执行完bb的then,最后再执行完cc的then
    function Promise(resolver) {
        if (resolver && typeof resolver !== 'function') {
            throw new Error('Promise resolver is not a function') }
        this.state = PENDING;
        this.data = UNDEFINED;
        this.callbackQueue = [];

        if (resolver) executeResolver.call(this, resolver);
    }
    Promise.prototype.then = function(onResolved, onRejected) {
        if (typeof onResolved !== 'function' && this.state === RESOLVED ||
            typeof onRejected !== 'function' && this.state === REJECTED) {
            return this;
        }
        // then要重新生成一个promise,但又不能马上执行,因为要判断这个promise执行完了没。promise是异步的
        var promise = new this.constructor();

        if (this.state !== PENDING) {
            var callback = this.state === RESOLVED ? onResolved : onRejected;
            // 通过bind绑定this到新的promise,这样完成了promise的循环。注意这个this.data,是上一个promise执行的结果
            executeCallbackAsync.bind(promise)(callback, this.data);
        } else {
            this.callbackQueue.push(new CallbackItem(promise, onResolved, onRejected))
        }
        // 执行完所有的resolve后,返回旧的promise,这个promise里面保存有上一个then执行完的值。在执行到下一个then前,会判断是否有then,有则将结果返回给then并执行
        return promise;
    }
    Promise.prototype['catch'] = function(onRejected) {
        return this.then(null, onRejected);
    }

    Promise.prototype.wait = function(ms) {
        var P = this.constructor;
        return this.then(function(v) {
            return new P(function(resolve, reject) {
                setTimeout(function() { resolve(v); }, ~~ms)
            })
        }, function(r) {
            return new P(function(resolve, reject) {
                setTimeout(function() { reject(r); }, ~~ms)
            })
        })
    }
    Promise.prototype.always = function(fn) {
        return this.then(function(v) {
            return fn(v), v;
        }, function(r) {
            throw fn(r), r;
        })
    }
    Promise.prototype.done = function(onResolved, onRejected) {
        this.then(onResolved, onRejected).catch(function(error) {
            setTimeout(function() {
                throw error;
            }, 0);
        });
    }

    Promise.resolve = function(value) {
        if (value instanceof this) return value;
        return executeCallback.bind(new this())('resolve', value);
    }
    Promise.reject = function(value) {
        if (value instanceof this) return value;
        return executeCallback.bind(new this())('reject', value);
    }
    Promise.all = function(iterable) {
        var _this = this;
        return new this(function(resolve, reject) {
            if (!iterable || !Array.isArray(iterable)) return reject(new TypeError('must be an array'));
            var len = iterable.length;
            if (!len) return resolve([]);

            var res = Array(len),
                counter = 0,
                called = false;

            iterable.forEach(function(v, i) {
                (function(i) {
                    _this.resolve(v).then(function(value) {
                        res[i] = value;
                        if (++counter === len && !called) {
                            called = true;
                            return resolve(res)
                        }
                    }, function(err) {
                        if (!called) {
                            called = true;
                            return reject(err);
                        }
                    })
                })(i)
            })
        })
    }
    Promise.race = function(iterable) {
        var _this = this;
        return new this(function(resolve, reject) {
            if (!iterable || !Array.isArray(iterable)) return reject(new TypeError('must be an array'));
            var len = iterable.length;
            if (!len) return resolve([]);

            var called = false;
            iterable.forEach(function(v, i) {
                _this.resolve(v).then(function(res) {
                    if (!called) {
                        called = true;
                        return resolve(res);
                    }
                }, function(err) {
                    if (!called) {
                        called = true;
                        return reject(err);
                    }
                })
            })
        })
    }
    Promise.stop = function() { return new this(); }
    Promise.deferred = Promise.defer = function() {
        var dfd = {};
        dfd.promise = new Promise(function(resolve, reject) {
            dfd.resolve = resolve;
            dfd.reject = reject;
        })
        return dfd
    }
    Promise.timeout = function(promise, ms) {
        return this.race([promise, this.reject(new TimeoutError('Operation timed out after ' + ms + ' ms')).wait(ms)]);
    }
    Promise.sequence = function(tasks) {
        return tasks.reduce(function(prev, next) {
            return prev.then(next).then(function(res) {
                return res });
        }, this.resolve());
    }


    function TimeoutError(message) {
        this.message = message || '';
        this.stack = (new Error()).stack;
    }
    TimeoutError.prototype = Object.create(Error.prototype);
    TimeoutError.prototype.constructor = TimeoutError;
    if(!scope.TimeoutError) scope.TimeoutError = TimeoutError;

    try {
        module.exports = Promise;
    } catch (e) {
        if (scope.Promise && !scope.MPromise) scope.MPromise = Promise;
    }
    return Promise;
})(this)

引用:
深入理解 Promise (上)
深入理解 Promise (中)
深入理解 Promise (下)

cordova in swift

注意:

  1. CDVViewController只能作为self.window?.rootViewController?
  2. 使用pod添加好后,还要添加bridge-header.h
#ifndef bridge_header_h
#define bridge_header_h
#import <Cordova/CDVViewController.h>

#endif /* bridge_header_h */

另外在Build Setting里面搜索bridge,找到Objective-C Bridging Header添加bridge-header.h文件

例子

import UIKit
import ReactiveCocoa
import Cordova

class CordovaSubView: CDVViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.addSubViews()
        self.webView.backgroundColor = UIColor.red;
        let path = Bundle.main.path(forResource: "www/login", ofType:"html")
        let urlStr = URL.init(fileURLWithPath: path!)
        print(urlStr)
//        self.webViewEngine.load(URLRequest(url: URL(string: "http://m.jd.com")!))
        self.webViewEngine.load(URLRequest(url: urlStr))
    }
    
    private func addSubViews() {
//        let vc = CDVViewController()
//        vc.view.frame = UIScreen.main.bounds
        // UIWindow(frame: UIScreen.main.bounds)
//        self.view.addSubview(vc.view)
        //创建UIScrollView
        let scrollV=UIScrollView(frame: CGRect(x:100,y:100,width:50,height:50))
        SpeechModal.init().beginSpeech()
        scrollV.backgroundColor=UIColor.red
        self.view.addSubview(scrollV)
    }
}

在appdelegate.swift文件添加self.window?.rootViewController = CordovaSubView()语句即能运行

cordova-plugin-qrscanner支持条形码

iOS

platforms/ios/{{YOUR_APP_NAME}}/Plugins/cordova-plugin-qrscanner/QRScanner.swift
metaOutput!.metadataObjectTypes = [AVMetadataObjectTypeQRCode] 

by

metaOutput!.metadataObjectTypes = [AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeCode128Code] 

And

if found.type == AVMetadataObjectTypeQRCode || && found.stringValue != nil {

by

if (found.type == AVMetadataObjectTypeQRCode || found.type == AVMetadataObjectTypeEAN13Code) {{ OR WHATEVER CODES YOU NEED }} && found.stringValue != nil {

Android

just add it in the formatList array of
platforms/android/src/com/bitpay/cordova/qrscanner/QRScanner.java

ArrayList<BarcodeFormat> formatList = new ArrayList<BarcodeFormat>(); 
                formatList.add(BarcodeFormat.EAN_13); 
                formatList.add(BarcodeFormat.CODE_128); 

react-dnd开发实录

公共组件对外接口格式

block

{
    width: 0,
    height: 0,
    top: 0,
    left,
    isFlex: false,
    enableResize: false,
    enableMove: true
}

注意

const SourceEmptyBlock = Block.genDNDBlock(DropTargetParams);
let draggedInst = [];
    if (storeData.inst && storeData.inst.length) {
      storeData.inst.map((item, index) => {
        const Child = getComponentByType(item.type)
        // 这里注意,key是不会传下去的
        draggedInst.push(<Child
          {...item}
          store={store}
          key={item.key}
          enableBubble={false}
          child={item.child}
          storeData={storeData} />
        )
      })
    }
    return (
      <div className={`${prefixCls}`}>
        <SourceEmptyBlock
          defaultStyle={{height: '100%', width: '100%'}}
          store={store}
          enableBubble={false}
          storeData={storeData}>
          {draggedInst}
        </SourceEmptyBlock>
      </div>
    )
  1. 数值的运算函数不存入json,因为json转字符串会被删掉

工单

  1. hover时模块边框要亮
  2. 根据x轴和y轴的可拖拽性显示圈和方形
  3. 鼠标移入可修改的文本框,鼠标要变成竖线
  4. 缩放的点要显示特殊的鼠标样式

解决难题

  1. 下一页的渲染基于上一页的渲染结果。成功将123*...n的计算量转换成N*2
  2. 要求不弹出渲染预览页。这里会有严重的异步控制问题,具体渲染顺序是:
1.  先渲染一页,计算每一页条数并存储起来(在最后一条data didmount后触发计算完成)
2. 计算完条数后再一次全部渲染成为打印页(在最后一页 didmount后触发startprint)
3. 打印页渲染完成后触发打印功能

执行环境、作用域、this

作用域

前面的话

  大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找。再加上this机制的干扰,使得变量查找极易出错。这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识。本文是深入理解javascript作用域系列第二篇——词法作用域和动态作用域

词法作用域

  第一篇介绍过,编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成词法单元。这个概念是理解词法作用域的基础

  简单地说,词法作用域就是定义在词法阶段的作用域,是由写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变

关系

  无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定

function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log( a, b, c );
    }
    bar(b * 3);
}
foo( 2 ); // 2 4 12

  在这个例子中有三个逐级嵌套的作用域。为了帮助理解,可以将它们想象成几个逐级包含的气泡
作用域

  作用域气泡由其对应的作用域块代码写在哪里决定,它们是逐级包含的

  气泡1包含着整个全局作用域,其中只有一个标识符:foo

  气泡2包含着foo所创建的作用域,其中有三个标识符:a、bar和b

  气泡3包含着bar所创建的作用域,其中只有一个标识符:c

查找

  作用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置

  在代码片段中,引擎执行console.log(...)声明,并查找a、b和c三个变量的引用。它首先从最内部的作用域,也就是bar(...)函数的作用域开始查找。引擎无法在这里找到a,因此会去上一级到所嵌套的foo(...)的作用域中继续查找。在这里找到了a,因此引擎使用了这个引用。对b来讲也一样。而对c来说,引擎在bar(...)中找到了它

  [注意]词法作用域查找只会查找一级标识符,如果代码引用了foo.bar.baz,词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则分别接管对bar和baz属性的访问

foo = {
    bar:{
        baz: 1
    }
};
console.log(foo.bar.baz);//1

遮蔽

  作用域查找从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见第一个匹配的标识符为止

  在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”,内部的标识符“遮蔽”了外部的标识符

var a = 0;
function test(){
    var a = 1;
    console.log(a);//1
}
test();

  全局变量会自动为全局对象的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引用来对其进行访问

var a = 0;
function test(){
    var a = 1;
    console.log(window.a);//0
}
test();

  通过这种技术可以访问那些被同名变量所遮蔽的全局变量。但非全局的变量如果被遮蔽了,无论如何都无法被访问到

动态作用域

  javascript使用的是词法作用域,它最重要的特征是它的定义过程发生在代码的书写阶段

  那为什么要介绍动态作用域呢?实际上动态作用域是javascript另一个重要机制this的表亲。作用域混乱多数是因为词法作用域和this机制相混淆,傻傻分不清楚

  动态作用域并不关心函数和作用域是如何声明以及在任何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套

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

  【1】如果处于词法作用域,也就是现在的javascript环境。变量a首先在foo()函数中查找,没有找到。于是顺着作用域链到全局作用域中查找,找到并赋值为2。所以控制台输出2

  【2】如果处于动态作用域,同样地,变量a首先在foo()中查找,没有找到。这里会顺着调用栈在调用foo()函数的地方,也就是bar()函数中查找,找到并赋值为3。所以控制台输出3

  两种作用域的区别,简而言之,词法作用域是在定义时确定的,而动态作用域是在运行时确定的

js大数加、减、乘(整数)运算

只实现加法

function add (a, b) {
    let lenA = a.length,
        lenB = b.length,
        len = lenA > lenB ? lenA : lenB;

    // 先补齐位数一致
    if(lenA > lenB) {
        for(let i = 0; i < lenA - lenB; i++) {
            b = '0' + b;
        }
    } else {
        for(let i = 0; i < lenB - lenA; i++) {
            a = '0' + a;
        }
    }

    let arrA = a.split('').reverse(),
        arrB = b.split('').reverse(),
        arr = [],
        carryAdd = 0;

    for(let i = 0; i < len; i++) {
        let temp = Number(arrA[i]) + Number(arrB[i]) + carryAdd;
        arr[i] = temp > 9 ? temp - 10 : temp;
        carryAdd = temp >= 10 ? 1 : 0;
    }

    if(carryAdd === 1) {
        arr[len] = 1;
    }

    return arr.reverse().join('');
    
}
	//加法
	function jia(a, b) {
		//把a,b放进zong数组
		var zong = [String(a), String(b)];
		//创建fen数组
		var fen = [];
		//把a,b较大的放在前面
		zong = getMax(zong[0], zong[1]);
		//把zong数组里面的元素分成单个数字
		zong[0] = zong[0].split('');
		zong[1] = zong[1].split('');
		//创建加0变量
		var jialing;
		//判断两个参数是否相同长度
		if(!(zong[0].length == zong[1].length)) {
			//创建0
			jialing = new Array(zong[0].length-zong[1].length+1).join('0');
			//把0放进zong[1]前面
			zong[1] = jialing.split('').concat(zong[1]);
		}
		//创建补充上一位的数字
		var next = 0;
		//从个位数起对应单个计算
		for(var i=(zong[0].length-1); i>=0; i--) {
			//求和
			var he = Number(zong[0][i]) + Number(zong[1][i]) + next;
			//把求和的个位数先放进数组
			fen.unshift(he%10);
			//把求和的十位数放进补充上一位的数字,留在下一次循环使用
			next = Math.floor(he/10);
			//判断最后一次如果求和的结果为两位数则把求和的十位数加在最前面
			if(i == 0 && !(next==0)) {
				fen.unshift(next);										
			}						
		}
		//把最后的结果转化成字符串
		var result = fen.join('');
		//返回字符串
		return result;
	}
 
	//减法
	function jian(a, b) {
		var zong = [String(a), String(b)];
		var fen = [];
		zong = getMax(zong[0], zong[1]);
		if(zong.length == 3) {
			alert("金币不足");
			return false;
		}
		zong[0] = zong[0].split('');
		zong[1] = zong[1].split('');
		var jialing;
		if(!(zong[0].length == zong[1].length)) {
			jialing = new Array(zong[0].length-zong[1].length+1).join('0');
			zong[1] = jialing.split('').concat(zong[1]);
		}
		var next = 0;
		for(var i=(zong[0].length-1); i>=0; i--) {
			var cha = Number(zong[0][i]) - Number(zong[1][i]) - next;
			next = 0;
			if(cha<0) {
				cha = cha + 10;
				next = 1;
			}
			fen.unshift(cha%10);					
		}
		var result = fen.join('');
		if(result[0] == 0) {
			result = shanchuling(result);
		}
		return result;	
	}
 
	//乘法
	function cheng(a, b) {
		var zong = [String(a), String(b)];
		var fen = [];
		zong = getMax(zong[0], zong[1]);
 
		zong[0] = zong[0].split('');
		zong[1] = zong[1].split('');
		//获取b的长度,处理乘法分配率的乘法
		for(var j=(zong[1].length-1); j>=0; j--) {
			var next = 0;
			var fentemp = []; 
			var jialing = '';
			//获取a的长度处理乘法
			for(var i=(zong[0].length-1); i>=0; i--) {
				var ji = Number(zong[0][i]) * Number(zong[1][j]) + next;
				fentemp.unshift(ji%10);
				next = Math.floor(ji/10);
				if(i == 0 && !(next==0)) {
					fentemp.unshift(next);										
				}
			}
			//后面添加0
			jialing = new Array((zong[1].length-(j+1))+1).join('0');
			fentemp.push(jialing);			
			fen[j] = fentemp.join('');				
		}
		//处理乘法后的求和
		var cishu = fen.length;
		for(var k=1; k<cishu; k++) {
			var hebing = jia(fen[0], fen[1]);
			fen.splice(0,2,hebing);
		}
		
		var result = fen.join('');
		if(result[0] == 0) {
			result = shanchuling(result);
		}	
		return result;
	}
 
	//获取最大值
	function getMax(a, b) {
		var result = [a, b];
		//如果a长度小于b长度
		if(a.length<b.length)
		{
			//b放前面
			result[0] = b;
			result[1] = a;
			//返回result长度为3,为了减法的不够减而准备
			result[2] = 'not';
			//返回最终数组
			return result;
		}
		//如果a长度等于b长度
		if(a.length == b.length) {
			//循环对比a,b里面的单个元素
			for(var i=0; i<a.length; i++) {
				if(result[0][i]>result[1][i]) {
					result[0] = a;
					result[1] = b;
					return result;
				}
				if(result[0][i]<result[1][i]) {
					result[0] = b;
					result[1] = a;
					result[2] = 'not';
					return result;					
				}
				//假如全部相等,当最后一个元素,以上条件都不执行,则执行默认的返回结果
				if(i == a.length-1) {
					return result;
				}				
			}
		}
		if(a.length>b.length) {
			return result;				
		}
	}
 
	//删除字符串前面多余的0
	function shanchuling(result) {
		//首先判断是否全部都是0,是的话直接返回一个0
		if(result == 0) {
			result = 0;
			//返回最终字符串
			return result;
		}
		//把字符串分割成数组
		result = result.split('');	
		//获取数组长度
		var hebing = result.length;
		for(var j=0; j<hebing; j++) {
			//判断数组首位是否为0
			if(result[0] == 0) {
				//把数组首位删掉
				result.splice(0,1);
			}
			else {
				//删除完了就跳出循环
				break;
			}
		}
		//返回最终字符串
		return result;		
	}
	//用法
	//console.log(jia("123","123"));
	//console.log(jian("123","123"));
	//console.log(cheng("123","123"));

koa源码解读

总结

  • https://github.com/cisen/sourcecode-koa-01
  • express源码:https://github.com/cisen/sourcecode-express-01
  • 中间件实现是通过外部全局变量+递归+promise实现的
  • 当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。
  • koa实现的功能
    • 洋葱模型的中间件
    • context,request和respond对象的封装
    • 说到底就是给请求加上中间件
  • 中间件说明
  • 还有一个很关键的东西,使用ctx来代理操作request和respond,具体实现是
    • 先写好req和res的getter和setter,比如设置body的时候需要把content-type也设置好
    • 然后在ctx对象使用delegate函数代理res的操作
    • 这样就可以在ctx对象上操作res的body设置了,比如ctx.response.body = ''
  • 中间件文件使用严格模式,然后使用JS引擎的尾调用优化
// require("babel-register");
const http = require('http');
const https = require('https');
const Koa = require('koa');
const nexttest = require("./nexttest");
const app = new Koa();


// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now();
  console.log('------1---------');
  await next();
  console.log('------2---------');

  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// logger
app.use(async (ctx, next) => {
  const start = Date.now();
  console.log('------3--------');

  await next();
  console.log('------4---------');

  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

app.use(async (ctx, next) => {
  console.log('------5--------');

  await next();
  console.log('------6---------');

});

// response
app.use(async ctx => {
  console.log('------7---------');

  ctx.body = ctx.response.status;
});

http.createServer(app.callback()).listen(3000);
// https.createServer(app.callback()).listen(3000);

输出:

------1---------
------3--------
------5--------
------7---------
------6---------
------4---------
GET / - 2
------2---------
------1---------
------3--------
------5--------
------7---------
------6---------
------4---------
GET / - 0
------2---------
------1---------
------3--------
------5--------
------7---------
------6---------
------4---------
GET /favicon.ico - 0
------2---------

vscode 调试

VSCode tasks.json中的各种替换变量的意思 ${workspaceFolder} ${file} ${fileBasename} ${fileDirname}等

When authoring tasks configurations, it is often useful to have a set of predefined common variables. VS Code supports variable substitution inside strings in the tasks.json file and has the following predefined variables:

  • ${workspaceFolder} the path of the workspace folder that contains the tasks.json file
  • ${workspaceRootFolderName} the name of the folder opened in VS Code without any slashes (/)
  • ${file} the current opened file
  • ${relativeFile} the current opened file relative to the workspace folder containing the file
  • ${fileBasename} the current opened file's basename
  • ${fileBasenameNoExtension} the current opened file's basename without the extension
  • ${fileDirname} the current opened file's dirname
  • ${fileExtname} the current opened file's extension
  • ${cwd} the task runner's current working directory on startup
  • ${lineNumber} the current selected line number in the active file
    You can also reference environment variables through ${env:Name} (for example, ${env:PATH}). Be sure to match the environment variable name's casing, for example ${env:Path} on Windows.

Below is an example of a custom task configuration that passes the current opened file to the TypeScript compiler.

{
    "taskName": "TypeScript compile",
    "type": "shell",
    "command": "tsc ${file}",
    "problemMatcher": [
        "$tsc"
    ]
}

部分翻译:(来自互联网)

${workspaceRoot} 当前打开的文件夹的绝对路径+文件夹的名字
${workspaceRootFolderName} 当前打开的文件夹的名字
${file}当前打开正在编辑的文件名,包括绝对路径,文件名,文件后缀名
${relativeFile}从当前打开的文件夹到当前打开的文件的路径

如 当前打开的是test文件夹,当前的打开的是main.c,并有test / first / second / main.c

那么此变量代表的是 first / second / main.c

${fileBasename} 当前打开的文件名+后缀名,不包括路径

${fileBasenameNoExtension} 当前打开的文件的文件名,不包括路径和后缀名

${fileDirname} 当前打开的文件所在的绝对路径,不包括文件名

${fileExtname} 当前打开的文件的后缀名

${cwd} the task runner's current working directory on startup

不知道怎么描述,这是原文解释,

跟 cmd 里面的 cwd 是一样的

${lineNumber} 当前打开的文件,光标所在的行数

android res文件夹

目录 资源类型
animator/ 用于定义属性动画的 XML 文件
anim/ 定义渐变动画的 XML 文件
color/ 用于定义颜色状态列表
drawable/ 位图文件(.png、.9.png、.jpg、.gif)、状态列表、形状...
mipmap/ 适用于不同启动器图标密度的 mipmap/ 文件夹管理启动器图标的详细信息。
layout/ 用于定义用户界面布局。
menu/ 用于定义应用菜单(如选项菜单、上下文菜单或子菜单)的 。
raw/ 要以原始形式保存的任意文件(2.3版本要求1M)。
values/ 包含字符串、整型数和颜色等简单值的
xml/ 可以在运行时通过调用 Resources.getXML() 读取的任意 XML 文件

https://blog.csdn.net/li_huorong/article/details/51684622
https://blog.csdn.net/wjrong_1/article/details/20918759

继承研究

总结

  1. 原型链的实现是因为new的时候创建的对象会有一个__proto__指针指向实例化函数的prototype对象。继承的实现也是通过Cat.prototype = new Animal(); new的时候创建一个对象和它的__proto__指针,所以继承的原理就是new的时候创建的对象的prototype_or_outer_reference_cp指针会指向上一个prototype对象,而js的继承是通过Cat.prototype = new Animal();实现的
    0.1 new Animal()会执行constructor生成一个包括对Animal prototype方法引用的新对象和__proto__指针,将生成的对象赋值给新的prototype就完成了prototype的继承,添加__proto__指针就完成了溯源
  2. extends会继承父constructor的this的属性,使用super之后还是会继承父的this的属性
  3. 所谓继承,只是继承原型方法prototype(通过cat.prototype=new Animate()),属性this.name(通过执行constructor函数),__proto__要指向继承源的prototype。
  4. 实例化对象是没有prototype对象的,只有函数有。实例化对象可以通过instanceof获取实例化的类
  5. 每一个引用数据都有一个__proto__属性,函数只有一个,类有两个(一个是函数本身,一个是prototype对象)。类的__ptoto__指向Function.prototype,类的prototype对象的__proto__指向继承的类的prototype
  6. new的所有对象都会有一个__proto__指针指向父的prototype
  7. 类的constructor是Function
// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};
function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log('1', cat instanceof Animal); //true 
console.log('2', Cat instanceof Animal); // false
console.log('3', cat instanceof Cat); //true
console.log('4', cat.__proto__ === Cat.prototype) // true
console.log('5', cat.__proto__ === Animal.prototype) // false
console.log('6', cat.__proto__.__proto__ === Animal.prototype) // true
console.log('7', Cat.__proto__ === Animal.prototype) // false
console.log('7.1', Cat.prototype.__proto__ === Animal.prototype) // true
console.log('8', Cat.__proto__ === Function.prototype) // true
console.log('9.0', cat.constructor, Cat.constructor, Cat.prototype.constructor) // Animal/Function/Animal,因为Cat的constructor美修复
console.log('9', cat.constructor === Cat) // false
console.log('9.1', cat.constructor === Animal) // true
console.log('10', Cat.prototype.constructor === Cat) // false 因为Cat的constructor美修复
console.log('11', Cat.prototype.constructor === Animal) // true 因为Cat的constructor美修复
console.log('12', Cat.constructor === Cat) // false
  1. 注意ES5标准13.2.2If Type(result) is Object then return result.如果new 构造函数返回的是一个对象,则获取到的是该对象,而不是new的类对象(包括数组)。具体实现再ecma_op_function_construct函数的/* 9. */‘

继承的实现方式

es6的extends

会继承父constructor的this的属性

原型链继承

核心: 将父类的实例作为子类的原型

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};
function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例

  2. 父类新增原型方法/原型属性,子类都能访问到

  3. 简单,易于实现
    缺点:

  4. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

  5. 无法实现多继承

  6. 来自原型对象的引用属性是所有实例共享的(详细请看附录代码: 示例1)

  7. 创建子类实例时,无法向父类构造函数传参
    推荐指数:★★(3、4两大致命缺陷)

构造继承

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题

  2. 创建子类实例时,可以向父类传递参数

  3. 可以实现多继承(call多个父类对象)
    缺点:

  4. 实例并不是父类的实例,只是子类的实例

  5. 只能继承父类的实例属性和方法,不能继承原型属性/方法

  6. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
    推荐指数:★★(缺点3)

实例继承

核心:为父类实例添加新特性,作为子类实例返回

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
```js
特点:

不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:

实例是父类的实例,不是子类的实例
不支持多继承
推荐指数:★★

### 拷贝继承
```js
function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

支持多继承
缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
    推荐指数:★(缺点1)

组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

// 感谢 @学无止境c 的提醒,组合继承也是需要修复构造函数指向的。

Cat.prototype.constructor = Cat;

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
Reflect.getPrototypeOf(Cat.prototype);
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法

  2. 既是子类的实例,也是父类的实例

  3. 不存在引用属性共享问题

  4. 可传参

  5. 函数可复用
    缺点:

  6. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
    推荐指数:★★★★(仅仅多消耗了一点内存)

寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性(父类的this.属性),这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
  Cat.prototype.constructor = Cat;
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

感谢 @Bluedrink 提醒,该实现没有修复constructor。

Cat.prototype.constructor = Cat; // 需要修复下构造函数
特点:

堪称完美
缺点:

实现较为复杂
推荐指数:★★★★(实现复杂,扣掉一颗星)

附录代码:
示例一:

function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
  //实例引用属性
  this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var tom = new Cat('Tom');
var kissy = new Cat('Kissy');

console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []

tom.name = 'Tom-New Name';
tom.features.push('eat');

//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']

原因分析:

关键点:属性查找过程

执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。

react源码对该继承方式的封装(个人注释可能有误)

/**
 * Helper to reduce boilerplate when creating subclasses.
 帮助者在创建子类时减少样板。
 总结:其实就是将SyntheticEvent和class的prototype合并赋给class,更新Interface,然后再赋予批量创建的方法augmentClass?
 *
 * @param {function} Class
 * @param {?object} Interface
 */
 // 用于生成子类
 // class一般就是SyntheticInputEvent之类的
 // 用于给Class增加this(有可能指向SyntheticUIEvent)的prototype,并且不会增加this构造函数里面的this的一些属性
 // Class和this的关系不是相同的,比如this是SyntheticUIEvent, Class是SyntheticFocusEvent
SyntheticEvent.augmentClass = function(Class, Interface) {
  // this指向SyntheticUIEvent的proxy
  const Super = this;
  // 存在的意义是使Class合并this时不会将this构造函数的this属性合并下来
  const E = function() {};
  // 继承SyntheticUIEvent类的原型方法
  // Super.prototype就是SyntheticUIEvent.prototype,E.constructor指向空函数,并不会指向SyntheticUIEvent
  // 但是E.prototype.constructor会指向Super.prototype.constructor
  E.prototype = Super.prototype;
  // prototype.constructor会指向SyntheticUIEvent
  const prototype = new E();
 // prototype继承SyntheticUIEvent的prototype方法后,再包含Class的原型属性,并作为Class的原型对象
 // Class.prototype一般是空的
  Object.assign(prototype, Class.prototype);
  // prototype集合了SyntheticUIEvent和class的prototype
  // 这样会使Class的constructor指向SyntheticUIEvent,下面修正
  Class.prototype = prototype;
  // Class构造函数使用时预先将SyntheticEvent作为普通函数使用生成实例
  // 再使用SyntheticEvent.augmentClass修改Class类的原型对象
  // 指定前Class.prototype.constructor指向SyntheticEvent,指定后指向Class
  // 详见https://stackoverflow.com/questions/4012998/what-it-the-significance-of-the-javascript-constructor-property/4013295#4013141,
  // https://www.cnblogs.com/SheilaSun/p/4397918.html
  Class.prototype.constructor = Class;

  Class.Interface = Object.assign({}, Super.Interface, Interface);
  // 使Class具有SyntheticUIEvent构造函数形式的生成子类的功能
  Class.augmentClass = Super.augmentClass;
  addEventPoolingTo(Class);
};

ios开发随记

报错

  1. no such file or directorybug,在Build Phases下面有个copy www directory,勾上run scriptonly installing,不要勾上show environment variables in build log就可以了
  2. 按照证书后,要在build Settings->Signing->Debug->Code Signing Identify(选择identities in keychain的key)/any iOS SDK(选择identities in keychain的key), Provisioning Profile选择dinghuoFor_test
  3. Gerneral-> Identity-> Bundle Identifier的YDH123后面加上.TEST

基础语法

  1. @,关键字也是@开头的,但是平常用@可以是指定一块内存对象,如@'helloword'就是指定一个内存存放数组。
  2. @selector
    selector可以叫做选择器,其实指的就是对象的方法,也可以理解为C语言里面的函数指针,在面向对象里面的对应概念。@selector(xxxx)的作用是找到名字为xxxx的方法。一般用于
    [a performSelector:@selector(b)];就是说去调用a对象的b方法,和[a b];的意思一样,但是这样更加动态一些。@selector(xxxx)返回的类型是SEL,看方法说明的时候如果参数类型是SEL,那么就是要接受@selector(xxxx)返回的值的那种了。使用已经创建好的Selector。你可以通过 performSelector : 来调用某个方法。
// 创建一个run方法选择器 
SEL aSelector = @selector(run);
// 通过 performSelector: 来调用对象的 run 方法 
[aDog performSelector:aSelector]; 
[anStudent performSelector:aSelector];

https://www.jianshu.com/p/672c0d4f435a

函数

  • 函数命名
- (void) doIt:(NSString *) actorName movieName: (NSString*) value timesSeen: (int)times { 
  NSLog(@"%@ is my favorite actor in the movie %@, I saw it %i times.",actorName, value, times); 
}

1、'-'表示这个函数是实例函数(类似非静态函数),'+'表示这个函数是类函数(类似静态函数)
2、(void)表示这个函数没有返回值。
3、函数名是'doIt: movieName: timesSeen:',而不是'doIt',movieName是第二个参数名,timesScreen是第三个参数名
4、参数用空格隔开
5、参数类型写在括号中
6、参数分内部参数和外部参数,如电影名称,内部参数是:value,外部参数是:movieName
7、这个函数的调用方法是:

[somebody dolt:"周星驰" movieName:"大话西游" timesScreen:"333"]

  1. protocal就相当于java中的interface; 声明一些协议,相当于声明一个基础类,大家都要的方法合集
  2. 而interface和implementation共同代表一个类,两者的组合相当于java中的class,即oc中的类必须包括两部分,interface部分(对外声明类)和implementation部分(对内实现类的所有方法),这才是oc中的一个类的完整声明;然后OC中将成员变量和成员方法的声明部分放置在interface部分中,包括继承关系,protocal实现关系,都在interface里面的头部进行声明,然后将实现部分放置在implementation部分中,相当于是将类拆分成声明和实现两部分,这两部分缺一不可,所以在OC中,不妨不要将interface叫做接口,直接叫做类声明部分来得容易理解多了,简而言之,oc中interface是类的一个部分,和implementation共同组成一个完整的类。
  • 继承
  1. a继承b
    @interface A : B
  2. 中括号用法
    [A b]表示调用A类或A对象中的b函数或属性
  • 实例化
  1. “类名 *p=[类名 new]”如:Person *p=[Person new]; 这里的初始化相当于:Person *p=[[Person alloc] init]; Person *p1=[person alloc];
  2. 调用类方法+alloc分配存储空间,返回未经初始化的对象
    Person *p1=[person alloc];
  3. 调用对象方法-init进行初始化,返回对象本身
    Person *p2=[p1 init];
  4. 以上两个过程整合为一句:
    Person *p=[[Person alloc] init];

##启动流程

  • 代码中UIApplicationMain函数创建了一个UIApplication对象,每个app都有且只有一个UIApplication对象,作用是维护运行循环,而且运行循环会一直循环下去。
    UIApplicationMain还会创建类的对象,将其设置为UIApplication的delegate。在应用启动运行循环并开始接受事件前,UIApplication会想委托对象发送applocation:didFinishLaunchingWithOptions:,我们可以在这个方法里完成需要的初始设定。
  • 程序启动的完整过程
    1.main函数
    2.UIApplicationMain
  • 创建UIApplication对象
  • 创建UIApplication的delegate对象
    3.delegate对象开始处理(监听)系统事件(没有storyboard)
  • 程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法
  • 在application:didFinishLaunchingWithOptions:中创建UIWindow
  • 创建和设置UIWindow的rootViewController
  • 显示窗口
    3.根据Info.plist获得最主要storyboard的文件名,加载最主要的storyboard(有storyboard)
  • 创建UIWindow
  • 创建和设置UIWindow的rootViewController
  • 显示窗口

Foundation框架

  1. Foundation框架是Mac\iOS中其他框架的基础,内部包含了开发中常用的数据类型如:结构体、枚举和一些类
    如果我们想使用Foundation框架中的功能,只要包含Foundation框架中的主头文件即可。
    #import <Foundation/Foundation.h>
    该框架提供了非常好用的类,都以NS开头,比如
    NSString:字符串
    NSArray:数组
    NSDictionary:字典
    NSDatte:日期
    NSData:数据
    NSNumber:数字

添加第三方

  • 添加头文件路径和链接库文件
    在PROJECT->build Setting->搜索linking
  1. 添加头文件依次找到
    Header Search Paths: 添加#include <>的路径
    User Header Search Paths: 添加#include “”路径
  2. 添加库文件
    Library Search Paths: 添加库所在目录
    Other Linker Flags: 比如要链接的库是libboost_regex.a,那么此处应该添加-lboost_regex即可。

添加第三方项目

问题

  1. UIKit的api如何查找使用方法?
  2. 引入的Cordova如何查找API

immer

用法

produce

  1. 修改原有的数据this.state,生成全新的state,draft就是this.state
this.setState(
      produce(this.state, draft => {
        draft.user.age += 1;
      })
    );

由于setState接受一个函数,所有又可以这么写

this.setState(
      produce(draft => {
        draft.user.age += 1;
      })
    );

produce可深层比较,甚至深层嵌套的数组,只修改其中一个元素,整个数组都不===,但是里面的其他元素都是===的,如

state = {
    user: {
      name: "Michel",
      age: 33,
      dd: {
        ff: 33
      },
      tt: {
        cc: 33
      }
    }
  };
let newOne = produce(this.state, draft => {
      draft.user.tt.cc += 1;
    });
newOne === this.state // false
newOne.user === this.state.user // false
newOne.user.dd === this.state.user.dd // true
newOne.user.tt === this.state.user.tt // false

Currying

produce还可以作为一个回调函数接受上一个函数传递下来的参数作为参数,比如下面的draft和index分别对象map的item和index

// mapper will be of signature (state, index) => state
const mapper = produce((draft, index) => {
    draft.index = index
})

// example usage
console.dir([{}, {}, {}].map(mapper))
//[{index: 0}, {index: 1}, {index: 2}])

This mechanism can also nicely be leveraged to further simplify our example reducer:

import produce from 'immer'

const byId = produce((draft, action) => {
  switch (action.type) {
    case RECEIVE_PRODUCTS:
      action.products.forEach(product => {
        draft[product.id] = product
      })
      return
    })
  }
})

Note that state is now factored out (the created reducer will accept a state, and invoke the bound producer with it).

If you want to initialize an uninitialized state using this construction, you can do so by passing the initial state as second argument to produce:

如果你想初始化没有初始化的state,你可以传递produce第二个参数

import produce from "immer"

const byId = produce(
    (draft, action) => {
        switch (action.type) {
            case RECEIVE_PRODUCTS:
                action.products.forEach(product => {
                    draft[product.id] = product
                })
                return
        }
    },
    {
        1: {id: 1, name: "product-1"}
    }
)

applyPatches

applyPatches主要用于撤销/重做
在处理producer的过程中,Immer可以记录所有reducer改变生成的patches,这能让你使用一份fork数据实现撤销重做功能。执行patches需要使用applyPatches函数
例子:

import produce, {applyPatches} from "immer"

let state = {
    name: "Micheal",
    age: 32
}

let fork = state
// 用户的所有改变
let changes = []
// 用户所有改变的反向改变
let inverseChanges = []

fork = produce(
    fork,
    draft => {
        draft.age = 33
    },
// produce的第三个参数是一个包括patches和inversePatches的回调函数
    (patches, inversePatches) => {
        changes.push(...patches)
        inverseChanges.push(...inversePatches)
    }
)

// 在同时,我们的原始数据改变了,比如收到后端数据并改变原始数据
state = produce(state, draft => {
    draft.name = "Michel"
})

// When the wizard finishes (successfully) we can replay the changes that were in the fork onto the *new* state!
// 当改变完成,我们可以用新数据再次调用这个改变以生成一份新的数据
state = applyPatches(state, changes)

// state now contains the changes from both code paths!
// state现在包括后端的改变和使用历史变更的改变
expect(state).toEqual({
    name: "Michel", // changed by the server 后端改变
    age: 33         // changed by the wizard 历史变更改变
})

// Finally, even after finishing the wizard, the user might change his mind and undo his changes...
// 最后,就算完成了所有变更,用户仍可以撤销他们的变更
state = applyPatches(state, inverseChanges)
expect(state).toEqual({
    name: "Michel", // Not reverted 没有撤销
    age: 32         // Reverted 撤销了
})

Map和Set

Map和Set都需要将旧map copy到新map中,但是这里有个问题,比如一个map中的width改变了,在不可变判断的时候,判断不出height是否有改变,它是这个map的所有属性都改变了

const state = {
    title: "hello",
    tokenSet: new Set()
}

const nextState = produce(state, draft => {
    draft.title = draft.title.toUpperCase() // let immer do it's job
    // don't use the operations onSet, as that mutates the instance!
    // draft.tokenSet.add("c1342")

    // instead: clone the set (once!)
    const newSet = new Set(draft.tokenSet)
    // mutate the clone (just in this producer)
    newSet.add("c1342")
    // update the draft with the new set
    draft.tokenSet = newSet
})

map:

const state = {
    users: new Map(["michel", { name: "miche" }])
}

const nextState = produce(state, draft => {
    const newUsers = new Map(draft.users)
    // mutate the new map and set a _new_ user object
    // but leverage produce again to base the new user object on the original one
    newUsers.set("michel", produce(draft.users.get("michel"), draft => {
        draft.name = "michel"
    }))
    draft.users = newUsers
})

其他

  1. 可以使用中间变量在produce里代替draft的某一个属性去改变其值,效果是一样的
let newState = produce(this.state, draft => {
      let user = draft.user;
      user.age += 1;
     // 跟 draft.user.age += 1 效果一样
    });
    if (newState === this.state) {
      console.log("newState === state");
    } else {
      console.log("!==");
    }
  1. 深层嵌套里面子不能有字段引用父
var a1 = {
    b1: {
        return: a1
    }
}

这样会导致撤销重做死循环爆内存

纪要

推荐使用 stage2
TC39
ES3 IE8
ES4 太激进
ES5 IE10 太保守
ES6 太多

装饰器发布什么时候?装饰器起码完全没发布

object.observe是唯一一个在stage2被撤销的,原因是双向绑定不是很重要了,性能差,接口可代替(proxy)

SIMD被从state3降到1,被webassembly代替

::this.onclick是唯一一个被实现了但是还留着stage1,可能被废弃

stage1被取消的promise.oncancel, var p = new Promise(resolve, reject), p.oncancel。cancelation取代了promise.oncancel

es标准: spec

decorator停在state2,因为被重新实现了

HTML渲染流程

总体

解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树

生成DOM Tree

DOM Tree:浏览器将HTML解析成树形的数据结构。

生成 CSS Rule Tree

CSS Rule Tree:浏览器将CSS解析成树形的数据结构。

合并生成Render Tree

Render Tree: DOM和CSSOM合并后生成Render Tree。

layout

layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。

reflow

reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
reflow几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着
下面情况会导致reflow发生
1:改变窗口大小
2:改变文字大小
3:内容的改变,如用户在输入框中敲字
4:激活伪类,如:hover
5:操作class属性
6:脚本操作DOM
7:计算offsetWidth和offsetHeight
8:设置style属性

repaint

repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。如果只是改变某个元素的背景色、文 字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint(重绘)。repaint 的速度明显快于 reflow

reflow和repaint的区别

  1. 使用display:none;的方式隐藏一个结点会导致repaint与reflow,使用visibility:hidden;进行dom隐藏仅仅导致repaint(没有结构性变化,仅仅看不见而已)
  2. 在性能优先的前提下,reflow的性能消耗要比repaint的大。

注意

(1)display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。

(2)display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

(3)有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

Gecko 里把格式化好的可视元素称做“帧树”(Frame tree)。每个元素就是一个帧(frame)。 webkit 则使用”渲染树”这个术语,渲染树由”渲染对象”组成。webkit 里使用”layout”表示元素的布局,Gecko则称为”reflow”。Webkit使用”Attachment”来连接DOM节点与可视化信息以构建渲染树。一个非语义上的小差别是Gecko在HTML与DOM树之间有一个附加的层 ,称作”content sink”,是创建DOM对象的工厂。

  尽管Webkit与Gecko使用略微不同的术语,这个过程还是基本相同的,如下:

  1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

  2. 将CSS解析成 CSS Rule Tree 。

  3. 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像Header或display:none的东西就没必要放在渲染树中了。

  4. 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为layout,顾名思义就是计算出每个节点在屏幕中的位置。

  5. 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

  注意:上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

引用

浏览器渲染原理及流程
浏览器的渲染原理简介
前端性能优化 —— reflow(回流)和repaint(重绘)

mac as wifi

Android Studio的plugin中查找wifi adb ultimate

撤销重做

保存每一步的数据

历史切换的时候只要数据切换就好,所以只要构建一个数据链表就ok

保存每一步变化的数据

保存一份初始数据,然后每次变化记录每次变化的数据。这里需要注意:

  1. 所有对数据的操作都要保存起来,因为数据的恢复需要一个连续的线性的步骤,不能跳跃回退
  2. 多种步骤要能实现作为一个步骤撤销重做

co源码解读

总结

  • co并不是手写一个generator,只是将generator和promise结合起来,在async还没成为标准的情况下让异步流程更漂亮

说明

  1. 为什么需要使用co来让generator实现异步控制呢?因为不能yield一个promise,否则只是直接返回一个promise对象,并不是异步后的值
var a = yield Promise.resolve(1);
console.log(a) // {value: Promise, done: false}

Promise 是一种编程**,用于“当xx数据准备完毕,then执行xx动作”这样的场景,不只是异步,同步代码也可以用 Promise。而 generator 在 ES6 中是迭代器生成器,被 TJ 创造性的拿来做异步流程控制了。真正的异步解决方案请大家期待 ES7 的 async 吧!co 模块是能让我们以同步的形式编写异步代码的 nodejs 模块,主要得益于 ES6 的 generator。

用法

  1. 同步执行异步需要在co函数里面
  2. 需要用yield promise
  3. 有时候我们把yield看成是return,next方法也接受传参,传入参数作为yield语句的返回值
    先通过异步请求获取页面数据,然后根据页面数据请求用户信息,最后根据用户信息请求用户的产品列表。过多的回调函数嵌套,使得程序难以维护,发展成万恶的回调。
$.get('/api/data', function(data) {
    console.log(data);
    $.get('/api/user', function(user) {
        console.log(user);
        $.get('/api/products', function(products) {
            console.log(products)
        });
    });
});

异步流程控制
最原始异步流程的写法,就是类似上面例子里的回调函数嵌套法,用过的人都知道,那叫一个酸爽。
后来出现了 Promise ,它极大提高了代码的可维护性,消除了万恶的回调嵌套问题,并且现在已经成为 ES6 标准的一部分。

$.get('/api/data')
.then(function(data) {
    console.log(data);
    return $.get('/api/user');
})
.then(function(user) {
    console.log(user);
    return $.get('/api/products');
})
.then(function(products) {
    console.log(products);
});

之后在 nodejs 圈出现了 co 模块,它基于 ES6 的 generator 和 yield ,让我们能用同步的形式编写异步代码。

co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});

Iterator

Iterator 迭代器是一个对象,知道如何从一个集合一次取出一项,而跟踪它的当前序列所在的位置,它提供了一个next()方法返回序列中的下一个项目。

var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
var pair = it.next(); 
console.log(pair); // ["name", "JavaScript"]
pair = it.next(); 
console.log(pair); // ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown

乍一看好像没什么奇特的,不就是一步步的取对象中的 key 和 value 吗,for … in也能做到,但是把它跟 generator 结合起来就大有用途了。

Generator

Generator 生成器允许你通过写一个可以保存自己状态的的简单函数来定义一个迭代算法。Generator 是一种可以停止并在之后重新进入的函数。生成器的环境(绑定的变量)会在每次执行后被保存,下次进入时可继续使用。generator 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用于生成一个迭代器对象。

function *gen() {
    yield 'hello';
    yield 'world';
    return true;
}

以上代码定义了一个简单的 generator,看起来就像一个普通的函数,区别是function关键字后面有个*号,函数体内可以使用yield语句进行流程控制。

var iter = gen();
var a = iter.next();
console.log(a); // {value:'hello', done:false}
var b = iter.next();
console.log(b); // {value:'world', done:false}
var c = iter.next();
console.log(c); // {value:true, done:true}

当执行gen()的时候,并不执行 generator 函数体,而是返回一个迭代器。迭代器具有next()方法,每次调用 next() 方法,函数就执行到yield语句的地方。next() 方法返回一个对象,其中value属性表示 yield 关键词后面表达式的值,done 属性表示是否遍历结束。generator 生成器通过next和yield的配合实现流程控制,上面的代码执行了三次 next() ,generator 函数体才执行完毕。
有时候我们把yield看成是return,next方法也接受传参,传入参数作为yield语句的返回值

thunk函数

thunk函数具备以下两个要素:

  1. 有且只有一个参数是callback的函数;
  2. callback的第一个参数是error。
    使用thunk函数,同时结合co我们就可以像写同步代码那样来写书写异步代码,先来个例子感受下:
var co = require('co'),
    fs = require('fs'),
    Promise = require('es6-promise').Promise;

function readFile(path, encoding){
    return function(cb){
        fs.readFile(path, encoding, cb);
    };
}

co(function* (){// 外面不可见,但在co内部其实已经转化成了promise.then().then()..链式调用的形式

   var a = yield readFile('a.txt', {encoding: 'utf8'});
    console.log(a); // a
    var b = yield readFile('b.txt', {encoding: 'utf8'});
    console.log(b); // b
    var c = yield readFile('c.txt', {encoding: 'utf8'});
    console.log(c); // c
    return yield Promise.resolve(a+b+c);
}).then(function(val){
    console.log(val); // abc
}).catch(function(error){
    console.log(error);
});

yield 和 yield*

  1. yield* 是委托提取器。yield 是你给什么它提取什么,但是 yield* 会继续向下请求,直到没的提取为止。
  2. yield* 一般用来在一个 generator 函数里“执行”另一个 generator 函数,并可以取得其返回值。
  3. co 可以直接 yield 一个 generator 而不用 yield,更奇怪的是居然可以直接 yield 一个 generator function*实际上,co 里面做了封装,在 co 的 toPromise 代码里有这样的判断:
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);

所以,其实我们的代码:

// yield generator function
var b = yield fn;
// yield generator
var c = yield fn(5);

实际上会变成类似这样:

var b = yield co(fn);
var c = yield co(fn_generator);
function* a() { yield 1; yield 2; yield 3; }
function* b() { yield 4; yield* a(); yield 5; }
// 如果是yield b(),会返还b函数,((yield b()).toString()); // -> 输出:[object Generator]
function* c() { yield 6; yield* b(); yield 7; } 

var cc = c();

cc.next(); // {value: 6, done: false}
cc.next(); // {value: 4, done: false}
cc.next(); // {value: 1, done: false}
cc.next(); // {value: 2, done: false}
cc.next(); // {value: 3, done: false}
cc.next(); // {value: 5, done: false}
cc.next(); // {value: 7, done: false}
cc.next(); // {value: undefined, done: true}

普通yield

function* outer() {
    yield 'begin';
    yield inner();
    yield 'end';
}

function* inner() {
    yield 'inner';
}

var it = outer(), v;

v = it.next().value;
console.log(v);            // -> 输出:begin

v = it.next().value;
console.log(v);            // -> 输出:{}
console.log(v.toString()); // -> 输出:[object Generator]

v = it.next().value;
console.log(v);            // -> 输出:end

代理yield

function* outer() {
    yield 'begin';

    /*
     * 这行等价于 yield 'inner';就是把inner里面的代码替换过来
     * 同时获得的rt刚好就是inner的返回值
     */
    var rt = yield* inner();
    console.log(rt);  // -> 输出:return from inner

    yield 'end';
}

function* inner() {
    yield 'inner';
    return 'return from inner';
}

var it = outer(), v;

v = it.next().value;
console.log(v);            // -> 输出:begin

v = it.next().value;
console.log(v);            // -> 输出:inner

v = it.next().value;
console.log(v);            // -> 输出:end

co原理

  1. 执行generator得到一个很多拥有next的generator,然后执行next(就是yield一个函数)得到的返回值(可能是函数,也可能是promise)
  2. 将next得到的值封装成promise(数组、对象、truck函数),执行promise,执行完promise后再执行next

总结

  1. co可能会导致this绑定错误,建议使用原生yield*
  2. node已经支持原生generator
  3. co和generator还是有很大区别的,generator不能直接yield一个promise,否则返还一个promise对象,所以需要使用co封装一层,让generator也能执行异步
    参考:
    yield 和 yield*

时间复杂度和大O表示法 空间复杂度

概念

大O表示法就是将算法的所有步骤转换为代数项,然后排除不会对问题的整体复杂度产生较大影响的较低阶常数和系数。

描述

**O(1)— **常量时间:给定一个大小为n的输入,概算法只需要一步就可以完成任务。

**O(log n)— **对数时间:给定大小为n的输入,该算法每执行一步,它完成任务所需要的步骤数目会以一定的因子减少。

**O(n)— **线性时间:给定大小为n的输入,该算法完成任务所需要的步骤直接和n相关(1对1的关系)。

O(n²)—二次方时间:给定大小为n的输入,完成任务所需要的步骤是n的平方。

**O(C^n)— **指数时间:给定大小为n的输入,完成任务所需要的步骤是一个常数的n次方(非常大的数字)。

例子

有了这些概念,我们一起来看看每一个复杂度完成任务所需要的步骤数:

let n = 16;

O (1) = 1 step "(awesome!)"

O (log n) = 4 steps "(awesome!)" -- assumed base 2

O (n) = 16 steps "(pretty good!)"
O(n^2) = 256 steps "(uhh..we can work with this?)"
O(2^n) = 65,536 steps "(...)"

如你所见,随着算法复杂度的提高,事情的复杂度也会以数量级增长。幸运的是,计算机足够强悍能够相对快速的处理非常复杂的问题。

符号             名称
O(1)            常数的
O(log(n))        对数的
O((log(n))c)    对数多项式的
O(n)            线性的
O(n2)            二次的
O(nc)            多项式的
O(cn)            指数的

计算方法

  • 结果跟次数数学公式
    比如冒泡排序分析
    再来一个例子: 计算冒泡排序的算法复杂度
void sort(int *a, int len) {
    for (int i = 0; i < len; i++) {
        int min = i;
        for(int j = i; j< len; j++){
            if (a[j] < a[min]) // 比较动作
                min = j;
        }
        swap(a, min, i);
    }
}

对于基于比较的排序算法来说,我们在计算复杂度的时候,是计算比较了多少次。

第一次循环,比较了 N次;
第二次循环,比较了 N-1次;
第三次循环,比较了 N-2次;
总的比较次数就是 N+(N−1)+(N−2)+…+1,就是N的阶加。根据阶加公式可得:

N∼=n(n+1)/2⇒O(n^2)
那么,冒泡排序的时间复杂度就是 O(n2)。

空间复杂度

  • 其实就是存储单元存储数量跟结果的关系

引用

http://bigocheatsheet.com/
https://www.jianshu.com/p/59d09b9cee58

业务开发遇到的问题

pc管理端

  1. 显示0
<div>{item.show && <span>hello</span>}</div>

这样show是false时,会显示成

<div>0</div>

所以一般用两个!!,但是在js上用两个!!,eslint会报错

<div>{!!item.show && <span>hello</span>}</div>

js编译原理

分词/词法分析

这些代码块被称为词法单元(token)。例如,var a = 2;。这段程序通常会被分解成为下面这些词法单元:var、a、=、2 、;
函数在运行的瞬间,生成一个活动对象(Active Object),简称AO

  • 分析参数
  1. 函数接收形式参数,添加到AO的属性,并且这个时候值为undefine,即AO.age=undefine
  2. 接收实参,添加到AO的属性,覆盖之前的undefine
  • 在分析变量声明
    如var age;或var age=18;
  1. 如果上一步分析参数中AO还没有age属性,则添加AO属性为undefine,即AO.age=undefine
  2. 如果AO上面已经有age属性了,则不作任何修改
  • 分析函数声明
    如果有function age(){}把函数赋给AO.age ,覆盖上一步分析的值

实例1:

function func(age) {
    console.log(age);
    var age = 25;
    console.log(age);
    function age() {
    }
    console.log(age);

}
func(18);

词法分析:

第一步,分析函数参数:
  形式参数:AO.age = undefined
  实参:AO.age = 18
第二步,分析局部变量:
  第3行代码有var age,但此时第一步中已有AO.age = 18,故不做任何改变
  即AO.age = 18
第三步,分析函数声明:
  第5行代码有函数age,则将function age(){}付给AO.age,即AO.age = function age() {}
所以,执行代码时:
  第2行代码运行时拿到的age是词法分析后的AO.age,结果是:function age() {};
  第3行代码:25赋给age,此时age=25;
  第4行代码运行时age已被赋值为25,结果25;
  第5,6行代码是一个函数表达式,所以不会做任何操作;
  第7行代码运行时age仍然是25,结果也是25。看看浏览器执行的结果,bingo~~

实例2:

function t(age){
	var e = 2;
	var age = 10;
	function age() {
	}
}
t(2);

具体步骤:
1、 函数在运行前的瞬间,会生成一个活动对象(Active Object),简称AO。
2、分析函数的参数,并将其作为AO的属性,默认值全部为underfined,如:AO{age=underfined}。
3、分析函数接受到的参数,并将参数内容设置到对象的AO属性上,如:AO{age=2}
4、分析函数内部的变量声明,如var age,如果AO上还没有age属性,则添加AO属性,值是undefined;如果AO上已经有age属性则不做任何影响。如:AO{age=2, e=undefined}
5、分析函数声明,并将函数赋给成AO的属性。AO{age= function(){}, e=undefined}
注意:由于age属性已经存在,则被覆盖掉了。

实例3:

function a(b){
	alert(b);
	function b() {
		alert(b);
	}
	b();
}
a(1);

词法分析过程:
1、分析函数的参数,结果为:AO{b=undefined}
2、分析函数的接受到的参数,结果为:AO{b=1}
3、分析函数内部的变量声明(var),这里没有声明内部变量。
4、分析函数声明,结果为:AO{b=function(){alert(b)}}
词法分析完成之后将根据这个AO对象来运行,此时2个alert(b)都将会打印b函数。
注意:

function a(b){
	alert(b);
	b = function() {
		alert(b);
	}
	b();
}
a(1);

这里的b= function(){} 并不是函数声明,而是函数表达式的赋值,在词法分析阶段不会使b=function(){},而是在运行阶段赋值。因此这里的结果跟上面就不一样了,大家可以自行查看。词法只会分析var,不会执行赋值。

var a = 42;
var b = 5;
function addA(d) {
  return a + d;
}
var c = addA(2) + b;

经过词法分析后生成:
词法分析后
再经过语法分析后变成的语法树:
语法树

解析/语法分析

这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)

<script>
         variabledeclaration

    identifier = a      assignmentExpession
         
                                        numricliteral  =2
</script>

代码生成(语法树)

将AST转换为可执行代码的过程称被称为代码生成 。
Espsrima是一个较为成熟的JavaScript语法解析开源项目。使用Espsrima对js代码进行语法解析的步骤如下:
语法树测试

引用

JavaScript预编译原理分析
浅谈JavaScript词法分析步骤
JavaScript 语法解析、AST、V8、JIT
JavaScript语法解析与抽象语法树(AST)----Espsrima的使用方法
造轮子系列(三): 一个简单快速的html虚拟语法树(AST)解析器
抽象语法树(Abstract Syntax Tree)
官方抽象语法树规范ESTree

下载echart图表保存为图片

echart

  1. 通过myChart.getDataURLapi获取图片base64地址
var img = new Image();
      img.src = this.myChart.getDataURL({
        type:"png",
        pixelRatio: 2,
        backgroundColor: '#fff'
      });
  1. 生成canvas并转化成blob
img.onload=function(){
  var canvas=document.createElement("canvas");
    canvas.width=myChart.getWidth()*2;
    canvas.height=myChart.getHeight()*2;
    var ctx=canvas.getContext('2d');
    ctx.drawImage(img,0,0);
    canvas.toBlob(function(blobFile) {
  1. 通过触发a标签的鼠标事件和download属性触发浏览器下载,将blob转化为图片
    let $a = document.createElement('a');
    $a.setAttribute("href", window.URL.createObjectURL(blobFile));
    $a.setAttribute("download", "");
    let evt = new MouseEvent("click", {
        bubbles: true,
        cancelable: true,
        view: window,
      });
    $a.dispatchEvent(evt);

下载部分html保存为图片

  1. 使用html2canvas
let opts = {
        allowTaint: true,  
        taintTest: false
      };
      let timestamp = new Date().getTime();
      let imgName = '配送损耗统计' + timestamp;
      let domTarget = document.getElementById("report-common-wrapper");
      html2canvas(domTarget, opts).then(function(canvas){
        canvas.id = "mycanvas";  
        //生成base64图片数据  
        let dataUrl = canvas.toDataURL();  
        let newImg = document.createElement("img");  
        newImg.src =  dataUrl;  
        downloadImg(newImg, imgName, domTarget.offsetWidth, domTarget.offsetHeight); 
      });

问题

  1. 兼容性如何?

其他

  1. 将blog转化为文件再下载又如何?
var fileReader = new FileReader();
fileReader.readAsDataURL(blobFile);
fileReader.onload = function() {

mac环境变量修改

Mac系统的环境变量,加载顺序为:

/etc/profile /etc/paths ~/.bash_profile ~/.bash_login ~/.profile ~/.bashrc

当然/etc/profile和/etc/paths是系统级别的,系统启动就会加载,后面几个是当前用户级的环境变量。后面3个按照从前往后的顺序读取,如果~/.bash_profile文件存在,则后面的几个文件就会被忽略不读了,如果~/.bash_profile文件不存在,才会以此类推读取后面的文件。~/.bashrc没有上述规则,它是bash shell打开的时候载入的。

#中间用冒号隔开
export PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:------:<PATH N>

(一)全局设置
下面的几个文件设置是全局的,修改时需要root权限

1)/etc/paths (全局建议修改这个文件 )
编辑 paths,将环境变量添加到 paths文件中 ,一行一个路径
Hint:输入环境变量时,不用一个一个地输入,只要拖动文件夹到 Terminal 里就可以了。

2)/etc/profile (建议不修改这个文件 )
全局(公有)配置,不管是哪个用户,登录时都会读取该文件。

3)/etc/bashrc (一般在这个文件中添加系统级环境变量)
全局(公有)配置,bash shell执行时,不管是何种方式,都会读取此文件。

4)
1.创建一个文件:
sudo touch /etc/paths.d/mysql
2.用 vim 打开这个文件(如果是以 open -t 的方式打开,则不允许编辑):
sudo vim /etc/paths.d/mysql
3.编辑该文件,键入路径并保存(关闭该 Terminal 窗口并重新打开一个,就能使用 mysql 命令了)
/usr/local/mysql/bin
据说,这样可以自己生成新的文件,不用把变量全都放到 paths 一个文件里,方便管理。

(二)单个用户设置

1)~/.bash_profile (任意一个文件中添加用户级环境变量)
(注:Linux 里面是 .bashrc 而 Mac 是 .bash_profile)
若bash shell是以login方式执行时,才会读取此文件。该文件仅仅执行一次!默认情况下,他设置一些环境变量
设置命令别名alias ll=’ls -la’
设置环境变量:

export PATH=/opt/local/bin:/opt/local/sbin:$PATH

2)~/.bashrc 同上
如果想立刻生效,则可执行下面的语句:
$ source 相应的文件
一般环境变量更改后,重启后生效。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.