Commit eedc3d22 authored by rockyl's avatar rockyl

mvvm实现

parent 43f8406e
This diff is collapsed.
This diff is collapsed.
{"id":"engine","url":"engine.85a610a71df748fd9a6ece34f456a06e8f3f1b63.js"}
\ No newline at end of file
{"id":"engine","url":"engine.210e93fc8ecfcb27da00c77582649b96949d28c3.js"}
\ No newline at end of file
{
"name": "aaaa",
"version": "1.0.0",
"name": "zeroing-engine",
"version": "0.1.0",
"description": "",
"main": "index.js",
"types": "index.d.ts",
......
function getPxToken(callback) {
if (window['ohjaiohdf']) {
var xhr = new XMLHttpRequest();
xhr.open('get', 'getToken', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.response);
if (response.success) {
window.eval(response.data);
callback(null, window['ohjaiohdf']());
} else {
callback('state invalid');
}
}
};
xhr.onerror = function (e) {
failedCallback();
};
xhr.onloadend = function () {
if (xhr.status === 404) {
failedCallback();
}
};
xhr.send();
} else {
callback('need login');
}
function failedCallback() {
callback('net error');
}
}
......@@ -423,11 +423,11 @@ export default class Container extends DisplayObject {
}
}
if(this._transform === this.transform && isUI(this) && this._lastLocalID !== this.transform.localID){
/*if(this._transform === this.transform && isUI(this) && this._lastLocalID !== this.transform.localID){
this._lastLocalID = this.transform.localID;
//console.log(this.name, this.instanceId , 'dirty!');
this.stage.layoutInvalid = true;
}
}*/
}
/**
......@@ -699,7 +699,7 @@ export default class Container extends DisplayObject {
if (this._width !== value) {
//子类有用,有_width,才需设置scaleX
this._width = value;
if(this.stage) this.stage.layoutInvalid = true;
//if (this.stage) this.stage.layoutInvalid = true;
this.dispatchEvent(Event.RESIZE);
}
}
......@@ -722,7 +722,7 @@ export default class Container extends DisplayObject {
// }
if (this._height !== value) {
this._height = value;
if(this.stage) this.stage.layoutInvalid = true;
//if (this.stage) this.stage.layoutInvalid = true;
this.dispatchEvent(Event.RESIZE);
}
}
......@@ -730,8 +730,10 @@ export default class Container extends DisplayObject {
clone(withEvents = false, withScripts = false) {
let target = this.constructor.apply(Object.create(this.constructor.prototype));
const {name, properties, events, scripts} = this['__originConfig'];
const originConfig = this['__originConfig'];
const {name, properties, events, scripts} = originConfig;
target.name = name;
target['__originConfig'] = originConfig;
injectProperties(target, properties);
if (withScripts) {
......@@ -755,6 +757,20 @@ export default class Container extends DisplayObject {
return target;
}
get $store() {
let p = this;
while (p.parent) {
p = p.parent;
if (p['$isViewRoot']) {
break;
}
}
if (p) {
return p['$_store'];
}
}
//全局遍历
/**
* @method _getElementsByName
......
......@@ -83,7 +83,20 @@ export class FloatDisplay extends DisplayObject {
}
});
s.addEventListener(Event.ADDED_TO_STAGE, function (e: Event) {
if(!container){
this.addHtmlElement();
}, s);
this._transformID = -1;
}
addHtmlElement() {
let s = this;
if(!s.stage){
return;
}
if (!container) {
container = document.createElement('div');
container.style.position = "absolute";
container.style.left = "0";
......@@ -104,9 +117,6 @@ export class FloatDisplay extends DisplayObject {
}
}
}
});
this._transformID = -1;
}
get htmlElement() {
......@@ -126,7 +136,7 @@ export class FloatDisplay extends DisplayObject {
* @param {HtmlElement} htmlElement 需要封装起来的html元素的引用。你可以通过这个引用来调用或设置此元素自身的属性方法和事件,甚至是样式
*/
protected init(htmlElement: any): void {
if(!htmlElement){
if (!htmlElement) {
return;
}
let s = this;
......@@ -160,6 +170,8 @@ export class FloatDisplay extends DisplayObject {
s._localBoundsSelf.width = w;
s._localBoundsSelf.height = h;
s._htmlElement = she;
this.addHtmlElement();
}
/**
......
......@@ -115,11 +115,6 @@ export class Stage extends Container {
*/
private static _stageList: any = {};
/**
* 布局失效
*/
layoutInvalid: boolean = false;
/**
* 是否暂停
* @property pause
......@@ -438,7 +433,7 @@ export class Stage extends Container {
s.dispatchEvent(Event.ON_INIT_STAGE);
// }
}, 100);
let rc = canvas;//s.rootDiv;
let rc = s.rootDiv; //canvas
let mouseEvent = s.onMouseEvent.bind(s);
//鼠标事件
if (osType != "pc") {
......@@ -648,8 +643,10 @@ export class Stage extends Container {
cp = new Point();
}
let rootDiv = s.rootDiv;
let doc = document.documentElement;
let box = points[o].target.getBoundingClientRect();
let box = rootDiv.getBoundingClientRect();//points[o].target
console.log(box.y);
let left = box.left + window.pageXOffset - doc.clientLeft;
let top = box.top + window.pageYOffset - doc.clientTop;
cp.x = (points[o].pageX - left) * devicePixelRatio;
......@@ -1016,14 +1013,6 @@ export class Stage extends Container {
}
}
afterUpdateTransform() {
/*this.calculateBounds();
if (this.layoutInvalid) {
this.dispatchEvent(Event.LAYOUT_INVALID);
this.layoutInvalid = false;
}*/
}
public destroy(): void {
let s = this;
Stage.removeUpdateObj(s);
......
......@@ -134,8 +134,6 @@ export default class CanvasRenderer extends SystemRenderer {
displayObject.updateTransform();
displayObject.parent = cacheParent;
displayObject.stage && displayObject.stage.afterUpdateTransform();
//初始化上下文状态
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
......
......@@ -179,8 +179,6 @@ export class WebglRenderer extends SystemRenderer {
displayObject.updateTransform();
displayObject.parent = cacheParent;
displayObject.stage && displayObject.stage.afterUpdateTransform();
//绑定渲染对象,没有则是默认root
this.bindRenderTexture(renderTexture, transform);
......
......@@ -7,14 +7,16 @@ import {StackContainer} from "./StackContainer";
import {loadAssets} from "./assets-manager";
import {instantiate} from "./view-interpreter";
import {dataCenter, DataCenter} from "./data-center";
import {setProcessMetaLibs} from "../behavior-runtime";
import {Tween} from "../../2d/tween";
import {Rect} from "./nodes";
import {setProcessMetaLibs} from "../behavior-runtime/index";
import {Tween} from "../../2d/tween/index";
import {Rect} from "./nodes/index";
import {injectEnv} from "./enviroment";
import {Toast} from "./Toast";
import {arrayFind} from "../utils";
import {registerCustomModules, registerScripts} from "..";
import {arrayFind} from "../utils/index";
import {Node} from "./nodes/Node";
import {bind, createStore} from "./mvvm/index";
import {safeEval} from "../utils/utils";
import {registerCustomModules} from "./custom-module";
/**
* 游戏舞台
......@@ -62,6 +64,7 @@ export class GameStage extends Node {
this._popupContainer.name = 'popup-container';
this._popupContainer.addEventListener('change', this.onPopupContainerChange, this);
}
/**
......@@ -180,6 +183,23 @@ export class GameStage extends Node {
let viewConfig = this.getViewConfigByName(name);
if (viewConfig) {
view = instantiate(viewConfig);
let store = {};
if(viewConfig.store){
const {exp, computed} = viewConfig.store;
store = createStore(exp, computed);
}
view['$isViewRoot'] = true;
view['$_store'] = store;
/*let label = view.children[0];
label['z-for'] = 'item in list';*/
console.time('bind');
bind(store, view);
console.timeEnd('bind');
if (cache) {
this._viewCache[name] = view;
}
......
/**
* Created by Raykid on 2016/12/16.
*/
import {IAres, Compiler, AresOptions, IWatcher, WatcherCallback, AresCommandData} from "./Interfaces";
import {Mutator} from "./Mutator";
import {Watcher} from "./Watcher";
import {CommandContext, Command, commands} from "./Commands"
export const defaultCmdRegExp:RegExp = /^(data\-)?a[\-_](\w+)([:\$](.+))?$/;
/**
* 将数据模型和视图进行绑定
* @param data
* @param compiler 视图解析器,不同类型的视图需要使用不同的解析器解析后方可使用
* @param options 一些额外参数
* @returns {IAres} 绑定实体对象
*/
export function bind(data:any, compiler:Compiler, options?:AresOptions):IAres
{
return new Ares(data, compiler, options);
}
export class Ares implements IAres
{
private _data:any;
private _compiler:Compiler;
private _options:any;
/** 获取ViewModel */
public get data():any
{
return this._data;
}
/** 获取编译器 */
public get compiler():Compiler
{
return this._compiler;
}
public constructor(data:any, compiler:Compiler, options?:AresOptions)
{
// 记录变异对象
this._data = Mutator.mutate(data);
this._compiler = compiler;
this._options = options;
// 初始化Compiler
this._compiler.init(this);
// 调用回调
if(this._options && this._options.inited)
{
this._options.inited.call(this._data, this);
}
}
public createWatcher(target:any, exp:string, scope:any, callback:WatcherCallback):IWatcher
{
return new Watcher(this, target, exp, scope, callback);
}
/**
* 解析表达式成为命令数据
* @param key 属性名,合法的属性名应以a-或a_开头,以:或$分隔主命令和子命令
* @param value 属性值,如果属性名合法则会被用来作为表达式的字符串
* @param cmdRegExp 可选,如果不传则使用默认的命令正则表达式解析命令
* @return {CommandData|null} 命令数据,如果不是命令则返回null
*/
public parseCommand(key:string, value:string, cmdRegExp?:RegExp):AresCommandData
{
var result:RegExpExecArray = (cmdRegExp || defaultCmdRegExp).exec(key);
if(!result) return null;
// 取到key
var key:string = result[0];
// 取到命令名
var cmdName:string = result[2];
// 取到命令字符串
var exp:string = value;
// 取到子命令名
var subCmd:string = result[4] || "";
// 返回结构体
return {
cmdName: cmdName,
subCmd: subCmd,
propName: key,
exp: exp
};
}
/**
* 测试是否是通用命令
* @param data 命令数据
* @return {boolean} 返回一个布尔值,表示该表达式是否是通用命令
*/
public testCommand(data:AresCommandData):boolean
{
// 非空判断
if(!data) return false;
// 取到通用命令
var cmd:Command = commands[data.cmdName];
return (cmd != null);
}
/**
* 执行通用命令,如果该表达式是通用命令则直接执行,否则什么都不做
* @param data 命令数据
* @param target 目标对象
* @param scope 变量作用域
* @return {boolean} 返回一个布尔值,表示该表达式是否是通用命令
*/
public execCommand(data:AresCommandData, target:any, scope:any):boolean
{
// 非空判断
if(!data || !scope) return false;
// 取到通用命令
var cmd:Command = commands[data.cmdName];
// 没找到命令就返回false
if(!cmd) return false;
// 找到命令了,执行之
cmd({
target: target,
scope: scope,
entity: this,
data: data
});
return true;
}
}
\ No newline at end of file
......@@ -2,15 +2,15 @@
* Created by Raykid on 2017/7/19.
*/
import {IAres, AresCommandData} from "./Interfaces"
import {IZri, ZriCommandData} from "./Interfaces"
import {runExp} from "./Utils"
export interface CommandContext
{
target:any;
scope:any;
entity:IAres;
data:AresCommandData;
entity:IZri;
data:ZriCommandData;
}
export interface Command
......
......@@ -4,18 +4,15 @@
import {Watcher} from "./Watcher";
export class Dep
{
private _map:{[uid:number]:Watcher} = {};
export class Dep {
private _map: { [uid: number]: Watcher } = {};
/**
* 添加数据变更订阅者
* @param watcher 数据变更订阅者
*/
public watch(watcher:Watcher):void
{
if(!this._map[watcher.uid])
{
public watch(watcher: Watcher): void {
if (!this._map[watcher.uid]) {
this._map[watcher.uid] = watcher;
}
}
......@@ -24,11 +21,9 @@ export class Dep
* 数据变更,通知所有订阅者
* @param extra 可能的额外数据
*/
public notify(extra?:any):void
{
for(var uid in this._map)
{
var watcher:Watcher = this._map[uid];
public notify(extra?: any): void {
for (var uid in this._map) {
var watcher: Watcher = this._map[uid];
watcher.update(extra);
}
}
......
......@@ -7,9 +7,9 @@ export interface Compiler
root:any;
/**
* 初始化编译器
* @param entity Ares实例
* @param entity Zri实例
*/
init(entity:IAres):void;
init(entity:IZri):void;
/**
* 编译方法
* @param target 要编译的显示节点
......@@ -18,7 +18,7 @@ export interface Compiler
compile(target:any, scope:any):void;
}
export interface IAres
export interface IZri
{
/** 获取ViewModel */
data:any;
......@@ -39,13 +39,13 @@ export interface IAres
* @param value 属性值,如果属性名合法则会被用来作为表达式的字符串
* @return {CommandData|null} 命令数据,如果不是命令则返回null
*/
parseCommand(key:string, value:string):AresCommandData;
parseCommand(key:string, value:string):ZriCommandData;
/**
* 测试是否是通用命令
* @param data 命令数据
* @return {boolean} 返回一个布尔值,表示该表达式是否是通用命令
*/
testCommand(data:AresCommandData):boolean;
testCommand(data:ZriCommandData):boolean;
/**
* 执行通用命令,如果该表达式是通用命令则直接执行,否则什么都不做
* @param data 命令数据
......@@ -53,12 +53,12 @@ export interface IAres
* @param scope 变量作用域
* @return {boolean} 返回一个布尔值,表示该表达式是否是通用命令
*/
execCommand(data:AresCommandData, target:any, scope:any):boolean
execCommand(data:ZriCommandData, target:any, scope:any):boolean
}
export interface AresOptions
export interface ZriOptions
{
inited?:(entity?:IAres)=>void;
inited?:(entity?:IZri)=>void;
}
export interface IWatcher
......@@ -82,7 +82,7 @@ export interface WatcherCallback
(newValue?:any, oldValue?:any, extra?:any):void;
}
export interface AresCommandData
export interface ZriCommandData
{
/** 主命令名 */
cmdName:string;
......
......@@ -5,10 +5,9 @@
import {Watcher} from "./Watcher";
import {Dep} from "./Dep";
export class Mutator
{
export class Mutator {
// 记录数组中会造成数据更新的所有方法名
private static _arrMethods:string[] = [
private static _arrMethods: string[] = [
"push",
"pop",
"unshift",
......@@ -23,17 +22,14 @@ export class Mutator
* @param data 原始数据
* @returns {any} 变异后的数据
*/
public static mutate(data:any):any
{
public static mutate(data: any): any {
// 如果是简单类型,则啥也不做
if(!data || typeof data != "object") return;
if (!data || typeof data != "object") return;
// 是个复杂类型对象,但是以前变异过了就不再重做一遍了
if(!data.__ares_mutated__)
{
if (!data.__ares_mutated__) {
// 针对每个内部变量都进行一次变异
for(var key in data)
{
Mutator.mutateObject(data, key, data[key]);
for (var key in data) {
Mutator.mutateProp(data, key, data[key]);
}
// 打一个标记表示已经变异过了
Object.defineProperty(data, "__ares_mutated__", {
......@@ -46,64 +42,62 @@ export class Mutator
return data;
}
private static mutateObject(data:any, key:string, value:any):void
{
private static mutateProp(data: any, key: string, value: any): void {
// 对每个复杂类型对象都要有一个对应的依赖列表
var dep:Dep = new Dep();
var dep: Dep = new Dep();
// 变异过程
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get: ()=>{
get: () => {
// 如果Watcher.updating不是null,说明当前正在执行表达式,那么获取的变量自然是其需要依赖的
var watcher:Watcher = Watcher.updating;
if(watcher) dep.watch(watcher);
var watcher: Watcher = Watcher.updating;
if (watcher) dep.watch(watcher);
// 利用闭包保存原始值
return value;
},
set: v=>{
if(v == value) return;
set: v => {
if (v == value) return;
value = v;
// 如果是数组就走专门的数组变异方法,否则递归变异对象
if(Array.isArray(v)) Mutator.mutateArray(v, dep);
if (Array.isArray(v)) Mutator.mutateArray(v, dep);
else Mutator.mutate(v);
// 触发通知
dep.notify();
}
});
if(Array.isArray(value)){
Mutator.mutateArray(value, dep);
}else{
// 递归变异
Mutator.mutate(value);
}
}
private static mutateArray(arr:any[], dep:Dep):void
{
private static mutateArray(arr: any[], dep: Dep): void {
// 变异当前数组
arr["__proto__"] = Mutator.defineReactiveArray(dep);
// 遍历当前数组,将内容对象全部变异
for(var i:number = 0, len:number = arr.length; i < len; i++)
{
for (var i: number = 0, len: number = arr.length; i < len; i++) {
Mutator.mutate(arr[i]);
}
}
private static defineReactiveArray(dep:Dep):any[]
{
var proto:any[] = Array.prototype;
var result:any[] = Object.create(proto);
private static defineReactiveArray(dep: Dep): any[] {
var proto: any[] = Array.prototype;
var result: any[] = Object.create(proto);
// 遍历所有方法,一个一个地变异
Mutator._arrMethods.forEach((method:string)=>{
Mutator._arrMethods.forEach((method: string) => {
// 利用闭包记录一个原始方法
var oriMethod:Function = proto[method];
var oriMethod: Function = proto[method];
// 开始变异
Object.defineProperty(result, method, {
value: function(...args:any[]):any
{
value: function (...args: any[]): any {
// 首先调用原始方法,获取返回值
var result:any = oriMethod.apply(this, args);
var result: any = oriMethod.apply(this, args);
// 数组插入项
var inserted:any[];
switch(method)
{
var inserted: any[];
switch (method) {
case "push":
case "unshift":
inserted = args;
......@@ -113,8 +107,7 @@ export class Mutator
break
}
// 监视数组插入项,而不是重新监视整个数组
if(inserted && inserted.length)
{
if (inserted && inserted.length) {
Mutator.mutateArray(inserted, dep);
}
// 触发更新
......@@ -126,19 +119,17 @@ export class Mutator
});
// 提供替换数组设置的方法,因为直接设置数组下标的方式无法变异
Object.defineProperty(result, "$set", {
value: function(index:number, value:any):any
{
value: function (index: number, value: any): any {
// 超出数组长度默认追加到最后
if(index >= this.length) index = this.length;
if (index >= this.length) index = this.length;
return this.splice(index, 1, value)[0];
}
});
// 提供替换数组移除的方法,因为直接移除的方式无法变异
Object.defineProperty(result, "$remove", {
value: function(item:any):any
{
value: function (item: any): any {
var index = this.indexOf(item);
if(index > -1) return this.splice(index, 1);
if (index > -1) return this.splice(index, 1);
return null;
}
});
......
......@@ -6,25 +6,25 @@
* @param exp 表达式
* @returns {Function} 创建的方法
*/
export function createEvalFunc(exp:string):(scope:any)=>any
{
var func:(scope:any)=>any;
try
{
func = Function("scope", "with(scope){return " + exp + "}") as (scope:any)=>any;
}
catch(err)
{
export function createEvalFunc(exp: string): (scope: any) => any {
var func: (scope: any) => any;
try {
func = Function("scope", "with(scope){return " + exp + "}") as (scope: any) => any;
} catch (err) {
// 可能是某些版本的解释器不认识模板字符串,将模板字符串变成普通字符串
var sepStr:string = (exp.indexOf('"') < 0 ? '"' : "'");
var sepStr: string = (exp.indexOf('"') < 0 ? '"' : "'");
// 将exp中的·替换为'
var reg:RegExp = /([^\\]?)`/g;
var reg: RegExp = /([^\\]?)`/g;
exp = exp.replace(reg, "$1" + sepStr);
// 将exp中${...}替换为" + ... + "的形式
reg = /\$\{(.*?)\}/g;
exp = exp.replace(reg, sepStr + "+($1)+" + sepStr);
// 重新生成方法并返回
func = Function("scope", "with(scope){return " + exp + "}") as (scope:any)=>any;
try {
func = Function("scope", "with(scope){return " + exp + "}") as (scope: any) => any;
}catch (e) {
console.error('非法的表达式:', exp);
}
}
return func;
}
......@@ -35,8 +35,7 @@ export function createEvalFunc(exp:string):(scope:any)=>any
* @param scope 表达式的作用域
* @returns {any} 返回值
*/
export function evalExp(exp:string, scope:any):any
{
export function evalExp(exp: string, scope: any): any {
return createEvalFunc(exp)(scope);
}
......@@ -45,8 +44,7 @@ export function evalExp(exp:string, scope:any):any
* @param exp 表达式
* @returns {Function} 创建的方法
*/
export function createRunFunc(exp:string):(scope:any)=>void
{
export function createRunFunc(exp: string): (scope: any) => void {
return createEvalFunc("(function(){" + exp + "})()");
}
......@@ -55,7 +53,6 @@ export function createRunFunc(exp:string):(scope:any)=>void
* @param exp 表达式
* @param scope 表达式的作用域
*/
export function runExp(exp:string, scope:any):void
{
export function runExp(exp: string, scope: any): void {
createRunFunc(exp)(scope);
}
\ No newline at end of file
import {IAres, IWatcher, WatcherCallback} from "./Interfaces";
import {IZri, IWatcher, WatcherCallback} from "./Interfaces";
import {createEvalFunc} from "./Utils";
/**
* Created by Raykid on 2016/12/22.
* 数据更新订阅者,当依赖的数据有更新时会触发callback通知外面
*/
export class Watcher implements IWatcher
{
export class Watcher implements IWatcher {
/** 记录当前正在执行update方法的Watcher引用 */
public static updating:Watcher = null;
public static updating: Watcher = null;
private static _uid:number = 0;
private static _uid: number = 0;
private _uid:number;
private _uid: number;
/** 获取Watcher的全局唯一ID */
public get uid():number
{
public get uid(): number {
return this._uid;
}
private _value:any;
private _value: any;
private _entity:IAres;
private _target:any;
private _exp:string;
private _scope:any;
private _expFunc:(scope:any)=>any;
private _callback:WatcherCallback;
private _entity: IZri;
private _target: any;
private _exp: string;
private _scope: any;
private _expFunc: (scope: any) => any;
private _callback: WatcherCallback;
private _disposed:boolean = false;
private _disposed: boolean = false;
public constructor(entity:IAres, target:any, exp:string, scope:any, callback:WatcherCallback)
{
public constructor(entity: IZri, target: any, exp: string, scope: any, callback: WatcherCallback) {
// 记录entity
this._entity = entity;
// 生成一个全局唯一的ID
this._uid = Watcher._uid ++;
this._uid = Watcher._uid++;
// 记录作用目标、表达式和作用域
this._target = target;
this._exp = exp;
......@@ -52,10 +49,9 @@ export class Watcher implements IWatcher
* 获取到表达式当前最新值
* @returns {any} 最新值
*/
public getValue():any
{
if(this._disposed) return null;
var value:any = null;
public getValue(): any {
if (this._disposed) return null;
var value: any = null;
// 记录自身
Watcher.updating = this;
// 设置通用属性
......@@ -74,12 +70,9 @@ export class Watcher implements IWatcher
writable: false
});
// 表达式求值
try
{
try {
value = this._expFunc.call(this._scope, this._scope);
}
catch(err)
{
} catch (err) {
// 输出错误日志
console.warn("表达式求值错误\nerr: " + err.toString() + "\nexp:" + this._exp + ",scope:" + JSON.stringify(this._scope));
}
......@@ -95,20 +88,18 @@ export class Watcher implements IWatcher
* 当依赖的数据有更新时调用该方法
* @param extra 可能的额外数据
*/
public update(extra?:any):void
{
if(this._disposed) return;
var value:any = this.getValue();
if(!Watcher.isEqual(value, this._value))
{
public update(extra?: any): void {
if (this._disposed) return;
var value: any = this.getValue();
if (!Watcher.isEqual(value, this._value)) {
this._callback && this._callback(value, this._value, extra);
this._value = Watcher.deepCopy(value);
}
}
/** 销毁订阅者 */
public dispose():void
{
if(this._disposed) return;
public dispose(): void {
if (this._disposed) return;
this._value = null;
this._target = null;
this._exp = null;
......@@ -121,8 +112,7 @@ export class Watcher implements IWatcher
/**
* 是否相等,包括基础类型和对象/数组的对比
*/
private static isEqual(a:any, b:any):boolean
{
private static isEqual(a: any, b: any): boolean {
return (a == b || (
Watcher.isObject(a) && Watcher.isObject(b)
? JSON.stringify(a) == JSON.stringify(b)
......@@ -133,23 +123,18 @@ export class Watcher implements IWatcher
/**
* 是否为对象(包括数组、正则等)
*/
private static isObject(obj:any):boolean
{
private static isObject(obj: any): boolean {
return (obj && typeof obj == "object");
}
/**
* 复制对象,若为对象则深度复制
*/
private static deepCopy(from:any):any
{
if (Watcher.isObject(from))
{
private static deepCopy(from: any): any {
if (Watcher.isObject(from)) {
// 复杂类型对象,先字符串化,再对象化
return JSON.parse(JSON.stringify(from));
}
else
{
} else {
// 基本类型对象,直接返回之
return from;
}
......
/**
* Created by rockyl on 2020-03-19.
*/
import {IZri, Compiler, ZriOptions, IWatcher, WatcherCallback, ZriCommandData} from "./Interfaces";
import {Mutator} from "./Mutator";
import {Watcher} from "./Watcher";
import {CommandContext, Command, commands} from "./Commands"
export const defaultCmdRegExp:RegExp = /^(data\-)?z[\-_](\w+)([:\$](.+))?$/;
export class Zri implements IZri
{
private _data:any;
private _compiler:Compiler;
private _options:any;
/** 获取ViewModel */
public get data():any
{
return this._data;
}
/** 获取编译器 */
public get compiler():Compiler
{
return this._compiler;
}
public constructor(data:any, compiler:Compiler, options?:ZriOptions)
{
// 记录变异对象
this._data = Mutator.mutate(data);
this._compiler = compiler;
this._options = options;
// 初始化Compiler
this._compiler.init(this);
// 调用回调
if(this._options && this._options.inited)
{
this._options.inited.call(this._data, this);
}
}
public createWatcher(target:any, exp:string, scope:any, callback:WatcherCallback):IWatcher
{
return new Watcher(this, target, exp, scope, callback);
}
/**
* 解析表达式成为命令数据
* @param key 属性名,合法的属性名应以a-或a_开头,以:或$分隔主命令和子命令
* @param value 属性值,如果属性名合法则会被用来作为表达式的字符串
* @param cmdRegExp 可选,如果不传则使用默认的命令正则表达式解析命令
* @return {CommandData|null} 命令数据,如果不是命令则返回null
*/
public parseCommand(key:string, value:string, cmdRegExp?:RegExp):ZriCommandData
{
var result:RegExpExecArray = (cmdRegExp || defaultCmdRegExp).exec(key);
if(!result) return null;
// 取到key
var key:string = result[0];
// 取到命令名
var cmdName:string = result[2];
// 取到命令字符串
var exp:string = value;
// 取到子命令名
var subCmd:string = result[4] || "";
// 返回结构体
return {
cmdName: cmdName,
subCmd: subCmd,
propName: key,
exp: exp
};
}
/**
* 测试是否是通用命令
* @param data 命令数据
* @return {boolean} 返回一个布尔值,表示该表达式是否是通用命令
*/
public testCommand(data:ZriCommandData):boolean
{
// 非空判断
if(!data) return false;
// 取到通用命令
var cmd:Command = commands[data.cmdName];
return (cmd != null);
}
/**
* 执行通用命令,如果该表达式是通用命令则直接执行,否则什么都不做
* @param data 命令数据
* @param target 目标对象
* @param scope 变量作用域
* @return {boolean} 返回一个布尔值,表示该表达式是否是通用命令
*/
public execCommand(data:ZriCommandData, target:any, scope:any):boolean
{
// 非空判断
if(!data || !scope) return false;
// 取到通用命令
var cmd:Command = commands[data.cmdName];
// 没找到命令就返回false
if(!cmd) return false;
// 找到命令了,执行之
cmd({
target: target,
scope: scope,
entity: this,
data: data
});
return true;
}
}
/**
* Created by rockyl on 2020-03-19.
*/
import {DisplayObject} from "../../../2d/display/DisplayObject";
import {ZriCompiler} from "./ZriCompiler";
import {IWatcher, IZri, ZriCommandData} from "./Interfaces";
import {evalExp, runExp} from "./Utils";
import {Container} from "../../../2d/display/index";
export interface Command {
/**
* 执行命令
* @param context 命令上下文
*/
(context?: CommandContext): DisplayObject;
}
export interface CommandContext {
scope: any;
target: Container;
entity: IZri;
cmdData: ZriCommandData;
[name: string]: any;
}
export const commands: { [name: string]: Command } = {
prop: (context: CommandContext) => {
let cmdData: ZriCommandData = context.cmdData;
let target: DisplayObject = context.target;
context.entity.createWatcher(target, cmdData.exp, context.scope, (value: any) => {
if (cmdData.subCmd != "") {
target[cmdData.subCmd] = value;
} else {
for (let name in value) {
target[name] = value[name];
}
}
});
// 返回节点
return target;
},
on: (context: CommandContext) => {
let cmdData: ZriCommandData = context.cmdData;
if (cmdData.subCmd != "") {
let handler: Function = context.scope[cmdData.exp] || window[context.cmdData.exp];
if (typeof handler == "function") {
// 是函数名形式
context.target.addEventListener(cmdData.subCmd, function () {
handler.apply(this, arguments);
}, context.scope);
} else {
// 是方法执行或者表达式方式
context.target.addEventListener(cmdData.subCmd, (evt: Event) => {
// 创建一个临时的子域,用于保存参数
let scope: any = Object.create(context.scope);
scope.$event = evt;
scope.$target = context.target;
runExp(cmdData.exp, scope);
});
}
}
// 返回节点
return context.target;
},
if: (context: CommandContext) => {
let cmdData: ZriCommandData = context.cmdData;
// 记录一个是否编译过的flag
let compiled: boolean = false;
// 插入一个占位元素
let refNode: Container = new Container();
refNode.mouseChildren = refNode.mouseEnabled = false;
let parent: Container = context.target.parent;
let index: number = parent.getChildIndex(context.target);
parent.removeChildAt(index);
parent.addChildAt(refNode, index);
// 只有在条件为true时才启动编译
let watcher: IWatcher = context.entity.createWatcher(context.target, cmdData.exp, context.scope, (value: boolean) => {
// 如果refNode被从显示列表移除了,则表示该if指令要作废了
if (!refNode.parent && !context.target.parent) {
watcher.dispose();
return;
}
if (value == true) {
// 插入节点
if (!context.target.parent) {
let parent = refNode.parent;
let index: number = parent.getChildIndex(refNode);
parent.removeChild(refNode);
parent.addChildAt(context.target, index);
}
// 启动编译
if (!compiled) {
context.compiler.compile(context.target, context.scope);
compiled = true;
}
} else {
// 移除元素
if (context.target.parent) {
let parent = context.target.parent;
let index: number = parent.getChildIndex(context.target);
parent.removeChild(context.target);
parent.addChildAt(refNode, index);
}
}
});
// 返回节点
return context.target;
},
for: (context: CommandContext) => {
let cmdData: ZriCommandData = context.cmdData;
let options = evalExp(cmdData.subCmd, context.scope) || {};
let page: number = (options.page || Number.MAX_VALUE);
// 解析表达式
let reg: RegExp = /^\s*(\S+)\s+in\s+([\s\S]+?)\s*$/;
let res: RegExpExecArray = reg.exec(cmdData.exp);
if (!res) {
console.error("for命令表达式错误:" + cmdData.exp);
return;
}
let itemName: string = res[1];
let arrName: string = res[2];
// 生成一个容器替换原始模板
let index: number = context.target.parent.getChildIndex(context.target);
let parent: Container = new Container();
context.target.parent.addChildAt(parent, index);
context.target.parent.removeChild(context.target);
// 生成一个新的scope,要向其中添加属性
let forScope: any = Object.create(context.scope);
Object.defineProperty(forScope, "$forTarget", {
configurable: true,
enumerable: false,
value: context.target,
writable: false
});
// 如果有viewport命令,则将其转移至容器上
/*let viewportCmds:ZriCommandData[] = context.cmdDict["viewport"];
if(viewportCmds)
{
let viewportCmd:ZriCommandData = viewportCmds[0];
if(viewportCmd)
{
parent[viewportCmd.propName] = viewportCmd.exp;
delete context.target[viewportCmd.propName];
}
}*/
// 使用原始显示对象编译一次parent
context.compiler.compile(parent, forScope);
// 获取窗口显示范围
//let viewportHandler:ViewPortHandler = getViewportHandler(parent);
// 声明闭包数据
let isArray: boolean;
let curList: any[];
let curIndex: number;
let lastNode: DisplayObject;
// 添加订阅
let watcher: IWatcher = context.entity.createWatcher(context.target, arrName, forScope, (value: any) => {
// 如果refNode被从显示列表移除了,则表示该for指令要作废了
if (!parent.parent) {
watcher.dispose();
return;
}
// 清理原始显示
for (let i: number = parent.children.length - 1; i >= 0; i--) {
parent.removeChildAt(i).destroy();
}
// 如果是数字,构建一个数字列表
if (typeof value == "number") {
let temp: number[] = [];
for (let i: number = 0; i < value; i++) {
temp.push(i);
}
value = temp;
}
// 如果不是数组,而是字典,则转换为数组,方便中断遍历
isArray = (value instanceof Array);
let list: any[];
if (isArray) {
list = value;
} else {
list = [];
for (let key in value) {
list.push({
key: key,
value: value[key]
});
}
}
// 初始化数据
curList = list;
curIndex = 0;
lastNode = null;
for (let li = curList.length; curIndex < li; curIndex++) {
//渲染
// 拷贝一个target
let newNode: DisplayObject = context.target.clone(true, true);
// 添加到显示里
parent.addChild(newNode);
// 生成子域
let newScope: any = Object.create(forScope);
// 这里一定要用defineProperty将目标定义在当前节点上,否则会影响forScope
Object.defineProperty(newScope, "$index", {
configurable: true,
enumerable: false,
value: curIndex,
writable: false
});
// 如果是字典则额外注入一个$key
if (!isArray) {
Object.defineProperty(newScope, "$key", {
configurable: true,
enumerable: true,
value: curList[curIndex].key,
writable: false
});
}
// 注入上一个显示节点
Object.defineProperty(newScope, "$last", {
configurable: true,
enumerable: false,
value: lastNode,
writable: false
});
// 添加长度
Object.defineProperty(newScope, "$length", {
configurable: true,
enumerable: false,
value: curList.length,
writable: false
});
// 注入遍历名
Object.defineProperty(newScope, itemName, {
configurable: true,
enumerable: true,
value: (isArray ? curList[curIndex] : curList[curIndex].value),
writable: false
});
// 开始编译新节点
context.compiler.compile(newNode, newScope);
// 赋值上一个节点
lastNode = newNode;
}
});
// 返回节点
return context.target;
}
};
\ No newline at end of file
/**
* Created by rockyl on 2020-03-19.
*/
import {Compiler, IZri, ZriCommandData} from "./Interfaces";
import {Container, } from "../../../2d/display/index";
import {Command, commands, CommandContext} from "./ZriCommands";
import {cmdPrefix} from "../../utils/utils";
const interruptCmds = ['for', 'if'];
export class ZriCompiler implements Compiler {
private _root: Container;
private _entity: IZri;
constructor(root: Container) {
this._root = root;
}
get root(): Container {
return this._root;
}
init(entity: IZri): void {
this._entity = entity;
this.compile(this._root, entity.data);
}
compile(target: any, scope: any): void {
let cmdDatas = [];
let needInterrupt = false;
for (let key in target) {
if(key.indexOf(cmdPrefix) < 0){
continue;
}
let cmdData = this._entity.parseCommand(key, target[key]);
if (cmdData) {
cmdDatas.push(cmdData);
if(interruptCmds.indexOf(cmdData.cmdName) >= 0){
needInterrupt = true;
cmdDatas.splice(0, cmdDatas.length - 1);
break;
}
}
}
for (let cmdData of cmdDatas) {
delete target[cmdData.propName];
if (interruptCmds.indexOf(cmdData.cmdName) >= 0 && target['__originConfig']) {
delete target['__originConfig'].properties[cmdData.propName];
}
let cmd: Command = commands[cmdData.cmdName];
if (!cmd) {
cmdData.subCmd = cmdData.cmdName || "";
cmdData.cmdName = "prop";
cmd = commands[cmdData.cmdName];
}
cmd({
scope,
target,
entity: this._entity,
cmdData,
compiler: this,
})
}
if (!needInterrupt && target.children && target.children.length > 0) {
for (let child of target.children) {
this.compile(child, scope);
}
}
}
}
......@@ -2,3 +2,40 @@
* Created by rockyl on 2020-03-02.
*/
import {Zri} from "./Zri";
import {Compiler, IZri, ZriOptions} from "./Interfaces";
import {ZriCompiler} from "./ZriCompiler";
import {Container} from "../../../2d/display/index";
import {safeEval} from "../../utils/utils";
/**
* 将数据模型和视图进行绑定
* @param store 数据
* @param view 视图
* @param options 一些额外参数
* @returns {IZri} 绑定实体对象
*/
export function bind(store: any, view: Container, options?: ZriOptions): IZri {
let compiler = new ZriCompiler(view);
return new Zri(store, compiler, options);
}
/**
* 通过配置生成数据
* @param exp
* @param computed
*/
export function createStore(exp, computed) {
let store = safeEval(exp) || {};
for (let item of computed) {
let getterCode = `return function(){
${item.script}
}`;
if (name && !store.hasOwnProperty(name)) {
Object.defineProperty(store, item.name, {
get: safeEval(getterCode),
})
}
}
return store;
}
......@@ -4,7 +4,7 @@
import {Stage} from "../2d/display";
import {registerCustomModuleFromConfig, registerScripts, RENDERER_TYPE, setProcessMetaLibs, StageScaleMode} from "..";
import {GameStage} from "./game-warpper";
import {GameStage} from "./game-warpper/index";
import {setGlobalContext} from "./behavior-runtime";
import {globalLoader} from "../2d/loader/Loader";
import {Event} from "../2d/events/Event";
......
......@@ -68,6 +68,22 @@ export function getDataByPath(scope, path, throwException?) {
}
}
/**
* 安全的eval方法
* @param code
* @param throwException
*/
export function safeEval(code, throwException = false){
let func = new Function(code);
try {
return func();
}catch (e) {
if (throwException) {
throw e;
}
}
}
/**
* 属性注入方法
* @param target 目标对象
......@@ -303,6 +319,9 @@ export function instantiateScript(node, ScriptConfig) {
const script = node.scripts.add(scriptName, props, disabled);
}
export const cmdPrefix = 'z-';
export const cmdOldPrefix = '//z-';
/**
* 属性注入
* @param target
......@@ -310,8 +329,10 @@ export function instantiateScript(node, ScriptConfig) {
*/
export function injectProperties(target, source) {
for (let key in source) {
if(!source.hasOwnProperty(cmdPrefix + key)){
propertyParse(key, target, source);
}
}
return target;
}
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Get Token Test</title>
</head>
<body>
<script src="getTokenKey"></script>>
<script src="//yun.duiba.com.cn/js-libs/px-token/0.0.1.4/px-token.min.js"></script>
<script>
function callApi(uri){
getPxToken(async function(e, t){
console.log(e, t);
let token = t;
let el = document.getElementById('token');
el.innerText = 'Token已获取:' + t;
try {
let resp = await fetch(uri + '?token=' + token);
let data = await resp.json();
console.log(data);
}catch (e) {
console.log(e);
}
});
}
</script>
<span id="token">Token获取中...</span>
<div>
<button onclick="callApi('join/tokenJoin.do')">请求[join/tokenJoin.do]</button>
<button onclick="callApi('join/join.do')">请求[join/join.do]</button>
</div>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment