HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

mui集成百度语音转写成文字

(function($, doc) {  

    $.plusReady(function() {  
        var dst;  
        var src;  
        var recordCancel = false;  
        var recorder = null;  
        var audio_tips = document.getElementById("audio_tips");  
        var startTimestamp = null;  
        var stopTimestamp = null;  
        var stopTimer = null;  
        var path1 = null;  
        if(mui.os.android) {  
            var format = "amr";  
        } else if(mui.os.ios) {  
            var format = "wav";  
        }  
        var rate = "16000";  
        var channel = "1";  
        //      var cuid = 'plus.device.imei'; //mac和imei码data:audio/amr;base64,  
        var cuid = '';  
        var speech = '';  
        var token_url = "https://aip.baidubce.com/oauth/2.0/token?";  
        var len = '';  
        var grant_type = "grant_type=自己的&";  
        var APIkey = "client_id=自己的&";  
        var SecretKey = "client_secret=自己的&";  
        var AppID = "自己的";  
        var access_token = "";  
        var fileArr = [];  
        var ui = {  
            boxMsgSound: doc.querySelector('#voice'),  
            areaMsgList: doc.querySelector('#search'),  
            boxSoundAlert: doc.querySelector('#sound-alert'),  
            mistake: doc.querySelector('#mistake'),  
            voice: doc.querySelector('#voice'),  
        };  

        function msgTextFocus() {  
            ui.boxMsgText.focus();  
            setTimeout(function() {  
                ui.boxMsgText.focus();  
            }, 150);  
        }  
        //点击输入框话筒  
        ui.boxMsgSound.addEventListener('tap', function(event) {  
            recordCancel = false;  
            if(stopTimer) clearTimeout(stopTimer);  
            audio_tips.innerHTML = "点击完成";  
            ui.boxSoundAlert.classList.remove('rprogress-sigh');  
            setSoundAlertVisable(true);  
            jQuery('.shield').show();  
            recorder = plus.audio.getRecorder();  
            if(recorder == null) {  
                plus.nativeUI.toast("不能获取录音对象");  
                return;  
            }  
            startTimestamp = (new Date()).getTime();  
            recorder.record({  
                format: 'wav',  
                samplerate: '16000',  
                filename: "_doc/audio/"  
            }, function(path) {  
                fileArr.push(path);  
                if(recordCancel) return;  
                Audio2dataURL(path);  
            }, function(e) {  
                plus.nativeUI.toast("录音时出现异常: " + e.message);  
            });  

        }, false);  

        ui.boxSoundAlert.addEventListener('tap', function(event) {  
            setSoundAlertVisable(false);  
            jQuery('.shield').hide();  
            recorder.stop();  
        }, false);  
        var setSoundAlertVisable = function(show) {  
            if(show) {  
                ui.boxSoundAlert.style.display = 'block';  
                ui.boxSoundAlert.style.opacity = 1;  
            } else {  
                ui.boxSoundAlert.style.opacity = 0;  
                //fadeOut 完成再真正隐藏  
                setTimeout(function() {  
                    ui.boxSoundAlert.style.display = 'none';  
                }, 200);  
            }  
        };  

        /**  
         * 录音语音文件转base64字符串  
         * @param {Object} path  
         */  
        function Audio2dataURL(path) {  
            plus.io.resolveLocalFileSystemURL(path, function(entry) {  
                entry.file(function(file) {  
                    var reader = new plus.io.FileReader();  
                    reader.onloadend = function(e) {  
                        var strResult = e.target.result;  
                        var index = strResult.indexOf('base64,') + 7;  
                        var base64Str = strResult.slice(index, strResult.length);  
                        delVoiceFile();  
                        len = file.size;  
                        if(!pageUtils.netWork()) {  
                            pageUtils.netToast();  
                        } else {  
                            voiceToWord(base64Str);  
                        }  
                    };  
                    reader.readAsDataURL(file);  
                }, function(e) {  
                    pageUtils.showToast("读写出现异常: " + e.message);  
                })  
            })  
        }  

        token();  
                //获取token,需要取百度语音自己获取,相关操作参考百度语音api  
        function token() {  
            if(mui.os.android) {  
                data2 = grant_type + APIkey + SecretKey;  
            } else if(mui.os.ios) {  
                APIkey = 'client_id=自己的&';  
                SecretKey = 'client_secret=自己的';  
                AppID = '自己的';  
                data2 = grant_type + APIkey + SecretKey;  
            }  
            mui.ajax(token_url, {  
                data: data2,  
                type: 'post',  
                contentType: "application/json; charset=utf-8",  
                timeout: 5000,  
                success: function(resp) {  
                    access_token = resp.access_token;  
                },  
                error: function(xhr, type, errorThrown) {  
                    pageUtils.showToast("网络请求出错");  
                }  
            });  
        }  
                //将语音转成文字  
        function voiceToWord(speech) {  
            var data1 = {  
                "rate": rate,  
                "format": format,  
                "channel": channel,  
                "cuid": AppID,  
                "token": access_token,  
                "speech": speech,  
                "dev_pid": 1536,  
                "len": len  
            };  
            mui.ajax(pageUtils.VOICEURL, {  
                data: data1,  
                type: 'post',  
                contentType: "application/json; charset=utf-8",  
                timeout: 5000,  
                success: function(resp) {  
                    if(resp.result == undefined || resp.result == '') {  
                        return;  
                    }  
                    jQuery('#mistake').show();  
                    jQuery('#voice').hide();  
                    ui.areaMsgList.value = resp.result[0];  
                },  
                error: function(xhr, type, errorThrown) {  
                    if(type == 'timeout') {  
                        pageUtils.showToast("录音超时");  
                    } else {  
                        pageUtils.showToast("网络请求出错");  
                    }  
                }  
            });  
        }  
        //这里录音发送完成就不需要了,所以没有必要存在手机里,直接删除音频  
        function delVoiceFile() {  
            for(var i = 0; i < fileArr.length; i++) {  
                plus.io.resolveLocalFileSystemURL(fileArr[i], function(entry) {  
                    if(entry.isFile) {  
                        setTimeout(function() {  
                            entry.remove(function(entry) {  
                                fileArr.pop();  
                                //                              pageUtils.showToast('删除成功');  
                            }, function(e) {  
                                //                              pageUtils.showToast('删除失败');  
                            });  
                        }, 500);  

                    }  
                }, function(e) {  
                    alert("Resolve file URL failed: " + e.message);  
                });  
            }  

        }  

    });  
}(mui, document));
继续阅读 »
(function($, doc) {  

    $.plusReady(function() {  
        var dst;  
        var src;  
        var recordCancel = false;  
        var recorder = null;  
        var audio_tips = document.getElementById("audio_tips");  
        var startTimestamp = null;  
        var stopTimestamp = null;  
        var stopTimer = null;  
        var path1 = null;  
        if(mui.os.android) {  
            var format = "amr";  
        } else if(mui.os.ios) {  
            var format = "wav";  
        }  
        var rate = "16000";  
        var channel = "1";  
        //      var cuid = 'plus.device.imei'; //mac和imei码data:audio/amr;base64,  
        var cuid = '';  
        var speech = '';  
        var token_url = "https://aip.baidubce.com/oauth/2.0/token?";  
        var len = '';  
        var grant_type = "grant_type=自己的&";  
        var APIkey = "client_id=自己的&";  
        var SecretKey = "client_secret=自己的&";  
        var AppID = "自己的";  
        var access_token = "";  
        var fileArr = [];  
        var ui = {  
            boxMsgSound: doc.querySelector('#voice'),  
            areaMsgList: doc.querySelector('#search'),  
            boxSoundAlert: doc.querySelector('#sound-alert'),  
            mistake: doc.querySelector('#mistake'),  
            voice: doc.querySelector('#voice'),  
        };  

        function msgTextFocus() {  
            ui.boxMsgText.focus();  
            setTimeout(function() {  
                ui.boxMsgText.focus();  
            }, 150);  
        }  
        //点击输入框话筒  
        ui.boxMsgSound.addEventListener('tap', function(event) {  
            recordCancel = false;  
            if(stopTimer) clearTimeout(stopTimer);  
            audio_tips.innerHTML = "点击完成";  
            ui.boxSoundAlert.classList.remove('rprogress-sigh');  
            setSoundAlertVisable(true);  
            jQuery('.shield').show();  
            recorder = plus.audio.getRecorder();  
            if(recorder == null) {  
                plus.nativeUI.toast("不能获取录音对象");  
                return;  
            }  
            startTimestamp = (new Date()).getTime();  
            recorder.record({  
                format: 'wav',  
                samplerate: '16000',  
                filename: "_doc/audio/"  
            }, function(path) {  
                fileArr.push(path);  
                if(recordCancel) return;  
                Audio2dataURL(path);  
            }, function(e) {  
                plus.nativeUI.toast("录音时出现异常: " + e.message);  
            });  

        }, false);  

        ui.boxSoundAlert.addEventListener('tap', function(event) {  
            setSoundAlertVisable(false);  
            jQuery('.shield').hide();  
            recorder.stop();  
        }, false);  
        var setSoundAlertVisable = function(show) {  
            if(show) {  
                ui.boxSoundAlert.style.display = 'block';  
                ui.boxSoundAlert.style.opacity = 1;  
            } else {  
                ui.boxSoundAlert.style.opacity = 0;  
                //fadeOut 完成再真正隐藏  
                setTimeout(function() {  
                    ui.boxSoundAlert.style.display = 'none';  
                }, 200);  
            }  
        };  

        /**  
         * 录音语音文件转base64字符串  
         * @param {Object} path  
         */  
        function Audio2dataURL(path) {  
            plus.io.resolveLocalFileSystemURL(path, function(entry) {  
                entry.file(function(file) {  
                    var reader = new plus.io.FileReader();  
                    reader.onloadend = function(e) {  
                        var strResult = e.target.result;  
                        var index = strResult.indexOf('base64,') + 7;  
                        var base64Str = strResult.slice(index, strResult.length);  
                        delVoiceFile();  
                        len = file.size;  
                        if(!pageUtils.netWork()) {  
                            pageUtils.netToast();  
                        } else {  
                            voiceToWord(base64Str);  
                        }  
                    };  
                    reader.readAsDataURL(file);  
                }, function(e) {  
                    pageUtils.showToast("读写出现异常: " + e.message);  
                })  
            })  
        }  

        token();  
                //获取token,需要取百度语音自己获取,相关操作参考百度语音api  
        function token() {  
            if(mui.os.android) {  
                data2 = grant_type + APIkey + SecretKey;  
            } else if(mui.os.ios) {  
                APIkey = 'client_id=自己的&';  
                SecretKey = 'client_secret=自己的';  
                AppID = '自己的';  
                data2 = grant_type + APIkey + SecretKey;  
            }  
            mui.ajax(token_url, {  
                data: data2,  
                type: 'post',  
                contentType: "application/json; charset=utf-8",  
                timeout: 5000,  
                success: function(resp) {  
                    access_token = resp.access_token;  
                },  
                error: function(xhr, type, errorThrown) {  
                    pageUtils.showToast("网络请求出错");  
                }  
            });  
        }  
                //将语音转成文字  
        function voiceToWord(speech) {  
            var data1 = {  
                "rate": rate,  
                "format": format,  
                "channel": channel,  
                "cuid": AppID,  
                "token": access_token,  
                "speech": speech,  
                "dev_pid": 1536,  
                "len": len  
            };  
            mui.ajax(pageUtils.VOICEURL, {  
                data: data1,  
                type: 'post',  
                contentType: "application/json; charset=utf-8",  
                timeout: 5000,  
                success: function(resp) {  
                    if(resp.result == undefined || resp.result == '') {  
                        return;  
                    }  
                    jQuery('#mistake').show();  
                    jQuery('#voice').hide();  
                    ui.areaMsgList.value = resp.result[0];  
                },  
                error: function(xhr, type, errorThrown) {  
                    if(type == 'timeout') {  
                        pageUtils.showToast("录音超时");  
                    } else {  
                        pageUtils.showToast("网络请求出错");  
                    }  
                }  
            });  
        }  
        //这里录音发送完成就不需要了,所以没有必要存在手机里,直接删除音频  
        function delVoiceFile() {  
            for(var i = 0; i < fileArr.length; i++) {  
                plus.io.resolveLocalFileSystemURL(fileArr[i], function(entry) {  
                    if(entry.isFile) {  
                        setTimeout(function() {  
                            entry.remove(function(entry) {  
                                fileArr.pop();  
                                //                              pageUtils.showToast('删除成功');  
                            }, function(e) {  
                                //                              pageUtils.showToast('删除失败');  
                            });  
                        }, 500);  

                    }  
                }, function(e) {  
                    alert("Resolve file URL failed: " + e.message);  
                });  
            }  

        }  

    });  
}(mui, document));
收起阅读 »

uniapp启动画面

uniapp

uniapp启动画面什么时候有啊

uniapp启动画面什么时候有啊

解决同时使用MUI下拉刷新、mui-PopPicker、mui-popover相互冲突的问题

picker popover 下拉刷新 mui

分享一下我在开发应用时,遇到的一些问题的解决方法,不知道会不会给大家一些帮助。
如果您有更好的方法,也可以一起讨论。

在我开发的应用中,主要涉及到高级查询的操作,因为有更多的查询选项,我使用了mui-popover这个控件可进行弹出,然后,嵌入了一些查询表单和下拉列表的选择项。

下拉列表选择项,使用了mui-poppicker控件。在测试时,popover和poppicker控件在安卓上遮罩层会有问题,点击poppicker后,相互遮挡,并且页面上还启用了Mui的下拉刷新控件。

出现的问题:
1、在弹出poppicker选择下拉列表时,会触发下拉刷新的事件。
2、popover显示后,再触发poppicker后,遮罩层不能完全遮盖住popover,导致可以多次触发poppicker

解决方法:

解决问题1思路:
弹出poppicker选择下拉列表时,禁用下拉刷新功能,选择确定后再启用下拉刷新功能。

解决问题2思路:
popover显示后,如果触发poppicker控件,先隐藏popover,选择确定后再显示popover控件。

另外,为了创建下拉列表方便,我将poppicker控件封装了一个常用方法,可以直接调用。

以下是实现的代码:

var isActivePop; //POP刷新激活状态,解决下拉刷新冲突  
mui.plusReady(function() {  
    isActivePop = false;  
    mui("#popWindow .mui-scroll-wrapper").scroll();  
    mui("body").on("shown", ".mui-popover", function(e) {  
        plus.webview.currentWebview().setPullToRefresh({  
            support: false  
        }, function() {});  
        isActivePop = true;  
    });  
    mui("body").on("hidden", ".mui-popover", function(e) {  
        if(isActivePop) {  
            plus.webview.currentWebview().setPullToRefresh({  
                support: true  
            }, function() {  
                // downRefresh(); 你自己的逻辑,我这里用的隐含刷新  
            });  
            mui("#refreshName").pullRefresh().pulldownLoading(); // 调用下拉刷新  
        }  
        isActivePop = false;  
    });  
}  

// 下拉刷新  
function downRefresh() {  
    // 自己的逻辑处理  
}  

/**  
 * 获取选择列表 (返回值为表单input赋值)  
 * 使用时需要引用 mui.picker.all.js 和 mui.picker.all.css 文件  
 * @param {String} buttonId 触发及显示文本的id  
 * @param {String} dataId 数据表单value的id  
 * @param {JSONArray} data 格式为 [{ value: 'ywj', text: '董事长 叶XX' }]  
 * @param {String} selectedValue 为选中值 value  
 * @param {Function} _callbackBefore 确认前回调方法  
 * @param {Function} _callbackPull 确认后回调下拉方法  
 * @param {Function} _callbackExe 确认后执行回调方法  
 */  
function getSelect(buttonId, dataId, data, selectedValue, _callbackBefore, _callbackPull, _callbackExe) {  
    var userPicker = new mui.PopPicker();  
    userPicker.setData(data);  
    var resultText = document.getElementById(buttonId);  
    var resultValue = document.getElementById(dataId);  
    var mui_popover = mui(".mui-popover"); //获取页面上多个popover控件  
    //设置默认值  
    if(selectedValue != null && selectedValue != "") {  
        userPicker.pickers[0].setSelectedValue(selectedValue); //设置选中值value  
        resultText.value = userPicker.pickers[0].getSelectedText(); //获取当前取中值的文本text  
        resultValue.value = userPicker.pickers[0].getSelectedValue(); //获取当前取中值的value值给数据表单  
    }  
    resultText.removeEventListener('tap', function(event) {});  
    resultText.addEventListener('tap', function(event, selectedValue) {  
        var _isActivePop = (isActivePop == null)? false : isActivePop);  
        if(_isActivePop) {  
            isActivePop = false; //在隐藏前,关闭刷新状态  
            for(var i = 0; i < mui_popover.length; i++) {  
                mui(mui_popover[i]).popover("hide");  
            }  
        }  
        plus.webview.currentWebview().setPullToRefresh({  
            support: false,  
        }, function() {  
            if(_callbackBefore != null) _callbackBefore();  
        });  
        userPicker.show(function(items) {  
            resultValue.value = items[0].value;  
            resultText.value = items[0].text;  
            if(_isActivePop) {  
                for(var i = 0; i < mui_popover.length; i++) {  
                    mui(mui_popover[i]).popover("show"); //处理页面上多个popover  
                }  
                isActivePop = true; //在显示后,开启刷新状态,为了能在POP正常HIDE后激活下拉刷新  
            }  
            plus.webview.currentWebview().setPullToRefresh({  
                support: true  
            }, function() {  
                if(_callbackPull != null) _callbackPull();  
            });  
            if(_callbackExe != null) _callbackExe();  
        });  
    }, false);  
    return userPicker;  
}

第一次写,也自己记录一下方便以后查。
以上是我实现的一些解决办法,可能不是很好,JS水平也有限,大家可以一起来完善、提高,相互学习。

继续阅读 »

分享一下我在开发应用时,遇到的一些问题的解决方法,不知道会不会给大家一些帮助。
如果您有更好的方法,也可以一起讨论。

在我开发的应用中,主要涉及到高级查询的操作,因为有更多的查询选项,我使用了mui-popover这个控件可进行弹出,然后,嵌入了一些查询表单和下拉列表的选择项。

下拉列表选择项,使用了mui-poppicker控件。在测试时,popover和poppicker控件在安卓上遮罩层会有问题,点击poppicker后,相互遮挡,并且页面上还启用了Mui的下拉刷新控件。

出现的问题:
1、在弹出poppicker选择下拉列表时,会触发下拉刷新的事件。
2、popover显示后,再触发poppicker后,遮罩层不能完全遮盖住popover,导致可以多次触发poppicker

解决方法:

解决问题1思路:
弹出poppicker选择下拉列表时,禁用下拉刷新功能,选择确定后再启用下拉刷新功能。

解决问题2思路:
popover显示后,如果触发poppicker控件,先隐藏popover,选择确定后再显示popover控件。

另外,为了创建下拉列表方便,我将poppicker控件封装了一个常用方法,可以直接调用。

以下是实现的代码:

var isActivePop; //POP刷新激活状态,解决下拉刷新冲突  
mui.plusReady(function() {  
    isActivePop = false;  
    mui("#popWindow .mui-scroll-wrapper").scroll();  
    mui("body").on("shown", ".mui-popover", function(e) {  
        plus.webview.currentWebview().setPullToRefresh({  
            support: false  
        }, function() {});  
        isActivePop = true;  
    });  
    mui("body").on("hidden", ".mui-popover", function(e) {  
        if(isActivePop) {  
            plus.webview.currentWebview().setPullToRefresh({  
                support: true  
            }, function() {  
                // downRefresh(); 你自己的逻辑,我这里用的隐含刷新  
            });  
            mui("#refreshName").pullRefresh().pulldownLoading(); // 调用下拉刷新  
        }  
        isActivePop = false;  
    });  
}  

// 下拉刷新  
function downRefresh() {  
    // 自己的逻辑处理  
}  

/**  
 * 获取选择列表 (返回值为表单input赋值)  
 * 使用时需要引用 mui.picker.all.js 和 mui.picker.all.css 文件  
 * @param {String} buttonId 触发及显示文本的id  
 * @param {String} dataId 数据表单value的id  
 * @param {JSONArray} data 格式为 [{ value: 'ywj', text: '董事长 叶XX' }]  
 * @param {String} selectedValue 为选中值 value  
 * @param {Function} _callbackBefore 确认前回调方法  
 * @param {Function} _callbackPull 确认后回调下拉方法  
 * @param {Function} _callbackExe 确认后执行回调方法  
 */  
function getSelect(buttonId, dataId, data, selectedValue, _callbackBefore, _callbackPull, _callbackExe) {  
    var userPicker = new mui.PopPicker();  
    userPicker.setData(data);  
    var resultText = document.getElementById(buttonId);  
    var resultValue = document.getElementById(dataId);  
    var mui_popover = mui(".mui-popover"); //获取页面上多个popover控件  
    //设置默认值  
    if(selectedValue != null && selectedValue != "") {  
        userPicker.pickers[0].setSelectedValue(selectedValue); //设置选中值value  
        resultText.value = userPicker.pickers[0].getSelectedText(); //获取当前取中值的文本text  
        resultValue.value = userPicker.pickers[0].getSelectedValue(); //获取当前取中值的value值给数据表单  
    }  
    resultText.removeEventListener('tap', function(event) {});  
    resultText.addEventListener('tap', function(event, selectedValue) {  
        var _isActivePop = (isActivePop == null)? false : isActivePop);  
        if(_isActivePop) {  
            isActivePop = false; //在隐藏前,关闭刷新状态  
            for(var i = 0; i < mui_popover.length; i++) {  
                mui(mui_popover[i]).popover("hide");  
            }  
        }  
        plus.webview.currentWebview().setPullToRefresh({  
            support: false,  
        }, function() {  
            if(_callbackBefore != null) _callbackBefore();  
        });  
        userPicker.show(function(items) {  
            resultValue.value = items[0].value;  
            resultText.value = items[0].text;  
            if(_isActivePop) {  
                for(var i = 0; i < mui_popover.length; i++) {  
                    mui(mui_popover[i]).popover("show"); //处理页面上多个popover  
                }  
                isActivePop = true; //在显示后,开启刷新状态,为了能在POP正常HIDE后激活下拉刷新  
            }  
            plus.webview.currentWebview().setPullToRefresh({  
                support: true  
            }, function() {  
                if(_callbackPull != null) _callbackPull();  
            });  
            if(_callbackExe != null) _callbackExe();  
        });  
    }, false);  
    return userPicker;  
}

第一次写,也自己记录一下方便以后查。
以上是我实现的一些解决办法,可能不是很好,JS水平也有限,大家可以一起来完善、提高,相互学习。

收起阅读 »

使用javascript获取安全区信息.

iphonex safeAreaInsets 安全区

适用环境:浏览器、WKWebView
APP内判断系统在iOS11以下或UIWebView请使用h5 API兼容

文档:https://github.com/zhetengbiji/safeAreaInsets

继续阅读 »

适用环境:浏览器、WKWebView
APP内判断系统在iOS11以下或UIWebView请使用h5 API兼容

文档:https://github.com/zhetengbiji/safeAreaInsets

收起阅读 »

单webview模式下下拉加载数据结果下拉页面失效(有问题的解决)

官方例子im-chat.html 修改成聊天页面,添加下拉方案实现加载聊天记录,结果添加下拉之后,页面数据却拉不下来了,查阅了各种数据,
参照http://ask.dcloud.net.cn/article/12686 的帖子查看了淘宝,知乎的源码,结果发现注释掉官方例子上的html,body的样式即可解决,但是有两个新的问题
1.页面下输入框弹出键盘时,聊天记录不能自动向上伸缩

  1. 进页面加载完数据页面不会跳到最下面的记录
继续阅读 »

官方例子im-chat.html 修改成聊天页面,添加下拉方案实现加载聊天记录,结果添加下拉之后,页面数据却拉不下来了,查阅了各种数据,
参照http://ask.dcloud.net.cn/article/12686 的帖子查看了淘宝,知乎的源码,结果发现注释掉官方例子上的html,body的样式即可解决,但是有两个新的问题
1.页面下输入框弹出键盘时,聊天记录不能自动向上伸缩

  1. 进页面加载完数据页面不会跳到最下面的记录
收起阅读 »

动态修改原生标题的方法——弯道超车---翻车了~看评论吧

原生标题

关于使用原生标题,官方给出的方案是,在manifest.json中配置首页原生标题
如果是打开新页面,则可以配置mui.openWindow里面的titleNView来设置标题与按钮
但是,不支持动态修改原生标题。

最近在开发一个app,需要底部tar,关于此效果最好的方法,官方已经给出了一个demo
具体可看:http://ask.dcloud.net.cn/article/12602
我的需求:点击下面的选项卡的时候,原生标题可以显示不同的文字 ,比如点击'分类'的时候,标题上显示的是’分类‘
但是通过找api,发现,确实不能动态修改标题,不过克想到一个弯道超车的方法:

通过找api,发现不能动态修改,但是可以在这个原生View上添加新的文字,于是就想到了下面的方法:
就是在manifest.json中,原生标题titletext的值为空,这样就首页的标题就是空的,啥也没有
然后在首页的代码中,对这个原生View对象进行绘制文字,并且在点击选项卡的时候,先执行View.reset(),即清空,再重新绘制新的文字
这样就算是动态修改标题了,相关代码如下:

function titleRedraw(id)  
{     
    var titleArr = ['首页','分类','购物车','个人中心'];  
    var scan = window.innerWidth - 34;  

     _self = plus.webview.currentWebview();  
     var titleView = _self.getNavigationbar();  
    var bitmap_menu = new plus.nativeObj.Bitmap("saoyisao");  
    titleView.reset();    
    titleView.drawText(titleArr[id], {}, {color:'#ffffff'});      

        //以下代码为在右上角添加扫一扫按钮,不需要的可以删除  
     bitmap_menu.load("images/saoyisao.png");  
     titleView.drawBitmap(bitmap_menu, {}, {  
            top: "10px",  
            right: "10px",  
        width: "24px",  
        height: "24px",  
        color:'#ffffff'  
     });   

     titleView.interceptTouchEvent(true);  
     titleView.addEventListener("click", function(e) {  
        var x = e.clientX;  
        if(x > scan) { //触发menu菜单  
              console.log('扫一扫');  
         }  
    }, false);  
}

在首页可第一时间执行的地方,先执行一遍titleRedraw(0);这样显示的就是首页,
然后在点击底部选项卡的地方,再执行一遍,并传入对应的下标就可以了

===========担心的问题===========
现在比较担心的就是,因为一直在重绘,所以对性能影响大不大?有没有大牛可以出来帮忙解释一下。
另外还有一个优化的地方,不知道可不可行:即不使用reset()来清空所有
比如我这个,所有页面都有右上角的按钮,那就只清除drawText所绘制的内容,是不是可行?

继续阅读 »

关于使用原生标题,官方给出的方案是,在manifest.json中配置首页原生标题
如果是打开新页面,则可以配置mui.openWindow里面的titleNView来设置标题与按钮
但是,不支持动态修改原生标题。

最近在开发一个app,需要底部tar,关于此效果最好的方法,官方已经给出了一个demo
具体可看:http://ask.dcloud.net.cn/article/12602
我的需求:点击下面的选项卡的时候,原生标题可以显示不同的文字 ,比如点击'分类'的时候,标题上显示的是’分类‘
但是通过找api,发现,确实不能动态修改标题,不过克想到一个弯道超车的方法:

通过找api,发现不能动态修改,但是可以在这个原生View上添加新的文字,于是就想到了下面的方法:
就是在manifest.json中,原生标题titletext的值为空,这样就首页的标题就是空的,啥也没有
然后在首页的代码中,对这个原生View对象进行绘制文字,并且在点击选项卡的时候,先执行View.reset(),即清空,再重新绘制新的文字
这样就算是动态修改标题了,相关代码如下:

function titleRedraw(id)  
{     
    var titleArr = ['首页','分类','购物车','个人中心'];  
    var scan = window.innerWidth - 34;  

     _self = plus.webview.currentWebview();  
     var titleView = _self.getNavigationbar();  
    var bitmap_menu = new plus.nativeObj.Bitmap("saoyisao");  
    titleView.reset();    
    titleView.drawText(titleArr[id], {}, {color:'#ffffff'});      

        //以下代码为在右上角添加扫一扫按钮,不需要的可以删除  
     bitmap_menu.load("images/saoyisao.png");  
     titleView.drawBitmap(bitmap_menu, {}, {  
            top: "10px",  
            right: "10px",  
        width: "24px",  
        height: "24px",  
        color:'#ffffff'  
     });   

     titleView.interceptTouchEvent(true);  
     titleView.addEventListener("click", function(e) {  
        var x = e.clientX;  
        if(x > scan) { //触发menu菜单  
              console.log('扫一扫');  
         }  
    }, false);  
}

在首页可第一时间执行的地方,先执行一遍titleRedraw(0);这样显示的就是首页,
然后在点击底部选项卡的地方,再执行一遍,并传入对应的下标就可以了

===========担心的问题===========
现在比较担心的就是,因为一直在重绘,所以对性能影响大不大?有没有大牛可以出来帮忙解释一下。
另外还有一个优化的地方,不知道可不可行:即不使用reset()来清空所有
比如我这个,所有页面都有右上角的按钮,那就只清除drawText所绘制的内容,是不是可行?

收起阅读 »

认识 V8 引擎

JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了极强的生命力。编程语言分为编译型语言和解释型语言两类,编译型语言在执行之前要先进行完全编译,而解释型语言一边编译一边执行,很明显解释型语言的执行速度是慢于编译型语言的,而JavaScript就是一种解释型脚本语言,支持动态类型、弱类型、基于原型的语言,内置支持类型。鉴于JavaScript都是在前端执行,而且需要及时响应用户,这就要求JavaScript可以快速的解析及执行。
随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。
1.渲染引擎及网页渲染
浏览器自从上世纪80年代后期90年代初期诞生以来,已经得到了长足的发展,其功能也越来越丰富,包括网络、资源管理、网页浏览、多页面管理、插件和扩展、书签管理、历史记录管理、设置管理、下载管理、账户和同步、安全机制、隐私管理、外观主题、开发者工具等。在这些功能中,为用户提供网页浏览服务无疑是最重要的功能,下面将对相关内容进行介绍。
1.1渲染引擎
渲染引擎:能够将HTML/CSS/JavaScript文本及相应的资源文件转换成图像结果。渲染引擎的主要作用是将资源文件转化为用户可见的结果。在浏览器的发展过程中,不同的厂商开发了不同的渲染引擎,如Tridend(IE)、Gecko(FF)、WebKit(Safari,Chrome,Andriod浏览器)等。WebKit是由苹果2005年发起的一个开源项目,引起了众多公司的重视,几年间被很多公司所采用,在移动端更占据了垄断地位。更有甚者,开发出了基于WebKit的支持HTML5的web操作系统(如:Chrome OS、Web OS)。
下面是WebKit的大致结构:


上图中实线框内模块是所有移植的共有部分,虚线框内不同的厂商可以自己实现。下面进行介绍:
操作系统:是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。WebKit也是在操作系统上工作的。
第三方库,为了WebKit提供支持,如图形库、网络库、视频库等。
WebCore 是各个浏览器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。JavaScriptCore是WebKit的默认引擎,在谷歌系列产品中被替换为V8引擎。WebKit Ports是WebKit中的非共享部分,由于平台差异、第三方库和需求的不同等原因,不同的移植导致了WebKit不同版本行为不一致,它是不同浏览器性能和功能差异的关键部分。
WebKit嵌入式编程接口,供浏览器调用,与移植密切相关,不同的移植有不同的接口规范。
测试用例,包括布局测试用例和性能测试用例,用来验证渲染结果的正确性。

1.2.网页渲染流程
上面介绍了渲染引擎的各个模块,那么一张网页,要经历怎样的过程,才能抵达用户面前?

首先是网页内容,输入到HTML解析器,HTML解析器解析,然后构建DOM树,在这期间如果遇到JavaScript代码则交给JavaScript引擎处理;如果来自CSS解析的样式信息,构建一个内部绘图模型。该模型由布局模块计算模型内部各个元素的位置和大小信息,最后由绘图模块完成从该模型到图像的绘制。在网页渲染的过程中,大致可以分为下面3个阶段。
1.2.1从输入URL到生成DOM树
1.地址栏输入URL,WebKit调用资源加载器加载相应资源;

  1. 加载器依赖网络模块建立连接,发送请求并接收答复;
  2. WebKit接收各种网页或者资源数据,其中某些资源可能同步或异步获取;
  3. 网页交给HTML解析器转变为词语;
  4. 解释器根据词语构建节点,形成DOM树;
  5. 如果节点是JavaScript代码,调用JavaScript引擎解释并执行;
  6. JavaScript代码可能会修改DOM树结构;
  7. 如果节点依赖其他资源,如图片\css、视频等,调用资源加载器加载它们,但这些是异步加载的,不会阻碍当前DOM树继续创建;如果是JavaScript资源URL(没有标记异步方式),则需要停止当前DOM树创建,直到JavaScript加载并被JavaScript引擎执行后才继续DOM树的创建。
    1.2.2.从DOM树到构建WebKit绘图上下文
    1.CSS文件被CSS解释器解释成内部表示;
  8. CSS解释器完成工作后,在DOM树上附加样式信息,生成RenderObject树;
  9. RenderObject节点在创建的同时,WebKit会根据网页层次结构构建RenderLayer树,同时构建一个虚拟绘图上下文。
    1.2.3.绘图上下文到最终图像呈现
    1.绘图上下文是一个与平台无关的抽象类,它将每个绘图操作桥接到不同的具体实现类,也就是绘图具体实现类;
  10. 绘图实现类也可能有简单的实现,也可能有复杂的实现,软件渲染、硬件渲染、合成渲染等;
  11. 绘图实现类将2D图形库或者3D图形库绘制结果保存,交给浏览器界面进行展示。
    上述是一个完整的渲染过程,现代网页很多都是动态的,随着网页与用户的交互,浏览器需要不断的重复渲染过程。
    1.3.JavaScript引擎

JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度(如上图所示)。JavaScript代码是在浏览器端解析和执行的,如果需要时间太长,会影响用户体验。那么提高JavaScript的解析速度就是当务之急。JavaScript引擎和渲染引擎的关系如下图所示:

JavaScript语言是解释型语言,为了提高性能,引入了Java虚拟机和C++编译器中的众多技术。现在JavaScript引擎的执行过程大致是:
源代码-→抽象语法树-→字节码-→JIT-→本地代码(V8引擎没有中间字节码)。一段代码的抽象语法树示例如下:

function demo(name) {  
    console.log(name);  
}

抽象语法树如下:

V8更加直接的将抽象语法树通过JIT技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。在V8生成本地代码后,也会通过Profiler采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化,但极大减少了转换时间。
但是在2017年4月底,v8 的 5.9 版本发布了,新增了一个 Ignition 字节码解释器,将默认启动,从此之后将与JSCore有大致相同的流程。做出这一改变的原因为:(主要动机)减轻机器码占用的内存空间,即牺牲时间换空间;提高代码的启动速度;对 v8 的代码进行重构,降低 v8 的代码复杂度(V8 Ignition:JS 引擎与字节码的不解之缘 - CNode技术社区)。
JavaScript的性能和C相比还有不小的距离,可预见的未来估计也只能接近它,而不是与它相比,这从语言类型上已经决定。下面将对V8引擎进行更为细致的介绍。
2.V8引擎
V8引擎是一个JavaScript引擎实现,最初由一些语言方面专家设计,后被谷歌收购,随后谷歌对其进行了开源。V8使用C++开发,,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性。
V8项目代码结构如下:

2.1.数据表示
JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定,这就不像c++或者java等静态类型语言,在编译时候就可以确切知道变量的类型。然而,在运行时计算和决定类型,会严重影响语言性能,这也就是JavaScript运行效率比C++或者JAVA低很多的原因之一。
在C++中,源代码需要经过编译才能执行,在生成本地代码的过程中,变量的地址和类型已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间。由于JavaScript是无类型语言,那就不能像c++那样在执行时已经知道变量的类型和地址,需要临时确定。JavaScript 和C++有以下几个区别:
1.编译确定位置,C++编译阶段确定位置偏移信息,在执行时直接存取,JavaScript在执行阶段确定,而且执行期间可以修改对象属性;

  1. 偏移信息共享,C++有类型定义,执行时不能动态改变,可共享偏移信息,JavaScript每个对象都是自描述,属性和位置偏移信息都包含在自身的结构中;
  2. 偏移信息查找,C++查找偏移地址很简单,在编译代码阶段,对使用的某类型成员变量直接设置偏移位置,JavaScript中使用一个对象,需要通过属性名匹配才能找到相应的值,需要更多的操作。
    在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数两个汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间。示例如下:

在JavaScript中,除了boolean,number,string,null,undefined这五个简单变量外,其它的数据都是对象,V8使用一种特殊的方式来表示他们,进而优化JavaScript的内部表示问题。

在V8中,数据的内部表示由数据的实际内容和数据的句柄构成。数据的实际内容是变长的,类型也是不同的;句柄固定大小,包含指向数据的指针。这种设计可以方便V8进行垃圾回收和移动数据内容,如果直接使用指针的话就会出问题或者需要更大的开销,使用句柄的话,只需修改句柄中的指针即可,使用者使用的还是句柄,指针改动是对使用者透明的。
除少数数据(如整型数据)由handle本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度。一个句柄对象的大小是4字节(32位设备)或者8字节(64位设备),而在JavaScriptCore中,使用的8个字节表示句柄。在堆中存放的对象都是4字节对齐的,所以它们指针的后两位是不需要的,V8用这两位表示数据的类型,00为整数,01为其他。

JavaScript对象在V8中的实现包含三个部分:隐藏类指针,这是v8为JavaScript对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性。

2.2.工作过程
前面有过介绍,V8引擎在执行JavaScript的过程中,主要有两个阶段:编译和运行,与C++的执行前完全编译不同的是,JavaScript需要在用户使用时完成编译和执行。在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销。在V8引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用JIT编译器的全代码生成器从AST直接生成本地可执行代码。这个过程不同于JAVA先生成字节码或中间表示,减少了AST到字节码的转换时间,提高了代码的执行速度。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会。

V8引擎编译本地代码时使用的主要类如下所示:

1.Script:表示JavaScript代码,即包含源代码,又包含编译之后生成的本地代码,即是编译入口,又是运行入口;

  1. Compiler:编译器类,辅组Script类来编译生成代码,调用解释器(Parser)来生成AST和全代码生成器,将AST转变为本地代码;
  2. AstNode:抽象语法树节点类,是其他所有节点的基类,包含非常多的子类,后面会针对不同的子类生成不同的本地代码;
  3. AstVisitor:抽象语法树的访问者类,主要用来遍历异构的抽象语法树;
  4. FullCodeGenerator:AstVisitor类的子类,通过遍历AST来为JavaScript生成本地可执行代码。


JavaScript代码编译的过程大致为:Script类调用Compiler类的Compile函数为其生成本地代码。Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码。本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码。由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间的优化也就无从谈起。

总结
在过去几年,JavaScript在很多领域得到了广泛的应用,然而限于JavaScript语言本身的不足,执行效率不高。Google也推出了一些JavaScript网络应用,如Gmail、Google Maps及Google Docs office等。这些应用的性能不仅受到服务器、网络、渲染引擎以及其他诸多因素的影响,同时也受到JavaScript本身执行速度的影响。然而既有的JavaScript引擎无法满足新的需求,而性能不佳一直是网络应用开发者最关心的。Google就开始了V8引擎的研究,将一系列新技术引入JavaScript引擎中,大大提高了JavaScript的执行效率。相信随着V8引擎的不断发展,JavaScript也会有更广泛的应用场景,前端工程师也会有更好的未来!
那么结合上面对于V8引擎的介绍,我们在编程中应注意:
1.类型。对于函数,JavaScript是一种动态类型语言,JavaScriptCore和V8都使用隐藏类和内嵌缓存来提高性能,为了保证缓存命中率,一个函数应该使用较少的数据类型;对于数组,应尽量存放相同类型的数据,这样就可以通过偏移位置来访问。

  1. 数据表示。简单类型数据(如整型)直接保存在句柄中,可以减少寻址时间和内存占用,如果可以使用整数表示的,尽量不要用浮点类型。
  2. 内存。虽然JavaScript语言会自己进行垃圾回收,但我们也应尽量做到及时回收不用的内存,对不再使用的对象设置为null或使用delete方法来删除(使用delete方法删除会触发隐藏类新建,需要更多的额外操作)。
    4.优化回滚。在执行多次之后,不要出现修改对象类型的语句,尽量不要触发优化回滚,否则会大幅度降低代码的性能。
  3. 新机制。使用JavaScript引擎或者渲染引擎提供的新机制和新接口提高性能。
继续阅读 »

JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了极强的生命力。编程语言分为编译型语言和解释型语言两类,编译型语言在执行之前要先进行完全编译,而解释型语言一边编译一边执行,很明显解释型语言的执行速度是慢于编译型语言的,而JavaScript就是一种解释型脚本语言,支持动态类型、弱类型、基于原型的语言,内置支持类型。鉴于JavaScript都是在前端执行,而且需要及时响应用户,这就要求JavaScript可以快速的解析及执行。
随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。
1.渲染引擎及网页渲染
浏览器自从上世纪80年代后期90年代初期诞生以来,已经得到了长足的发展,其功能也越来越丰富,包括网络、资源管理、网页浏览、多页面管理、插件和扩展、书签管理、历史记录管理、设置管理、下载管理、账户和同步、安全机制、隐私管理、外观主题、开发者工具等。在这些功能中,为用户提供网页浏览服务无疑是最重要的功能,下面将对相关内容进行介绍。
1.1渲染引擎
渲染引擎:能够将HTML/CSS/JavaScript文本及相应的资源文件转换成图像结果。渲染引擎的主要作用是将资源文件转化为用户可见的结果。在浏览器的发展过程中,不同的厂商开发了不同的渲染引擎,如Tridend(IE)、Gecko(FF)、WebKit(Safari,Chrome,Andriod浏览器)等。WebKit是由苹果2005年发起的一个开源项目,引起了众多公司的重视,几年间被很多公司所采用,在移动端更占据了垄断地位。更有甚者,开发出了基于WebKit的支持HTML5的web操作系统(如:Chrome OS、Web OS)。
下面是WebKit的大致结构:


上图中实线框内模块是所有移植的共有部分,虚线框内不同的厂商可以自己实现。下面进行介绍:
操作系统:是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。WebKit也是在操作系统上工作的。
第三方库,为了WebKit提供支持,如图形库、网络库、视频库等。
WebCore 是各个浏览器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。JavaScriptCore是WebKit的默认引擎,在谷歌系列产品中被替换为V8引擎。WebKit Ports是WebKit中的非共享部分,由于平台差异、第三方库和需求的不同等原因,不同的移植导致了WebKit不同版本行为不一致,它是不同浏览器性能和功能差异的关键部分。
WebKit嵌入式编程接口,供浏览器调用,与移植密切相关,不同的移植有不同的接口规范。
测试用例,包括布局测试用例和性能测试用例,用来验证渲染结果的正确性。

1.2.网页渲染流程
上面介绍了渲染引擎的各个模块,那么一张网页,要经历怎样的过程,才能抵达用户面前?

首先是网页内容,输入到HTML解析器,HTML解析器解析,然后构建DOM树,在这期间如果遇到JavaScript代码则交给JavaScript引擎处理;如果来自CSS解析的样式信息,构建一个内部绘图模型。该模型由布局模块计算模型内部各个元素的位置和大小信息,最后由绘图模块完成从该模型到图像的绘制。在网页渲染的过程中,大致可以分为下面3个阶段。
1.2.1从输入URL到生成DOM树
1.地址栏输入URL,WebKit调用资源加载器加载相应资源;

  1. 加载器依赖网络模块建立连接,发送请求并接收答复;
  2. WebKit接收各种网页或者资源数据,其中某些资源可能同步或异步获取;
  3. 网页交给HTML解析器转变为词语;
  4. 解释器根据词语构建节点,形成DOM树;
  5. 如果节点是JavaScript代码,调用JavaScript引擎解释并执行;
  6. JavaScript代码可能会修改DOM树结构;
  7. 如果节点依赖其他资源,如图片\css、视频等,调用资源加载器加载它们,但这些是异步加载的,不会阻碍当前DOM树继续创建;如果是JavaScript资源URL(没有标记异步方式),则需要停止当前DOM树创建,直到JavaScript加载并被JavaScript引擎执行后才继续DOM树的创建。
    1.2.2.从DOM树到构建WebKit绘图上下文
    1.CSS文件被CSS解释器解释成内部表示;
  8. CSS解释器完成工作后,在DOM树上附加样式信息,生成RenderObject树;
  9. RenderObject节点在创建的同时,WebKit会根据网页层次结构构建RenderLayer树,同时构建一个虚拟绘图上下文。
    1.2.3.绘图上下文到最终图像呈现
    1.绘图上下文是一个与平台无关的抽象类,它将每个绘图操作桥接到不同的具体实现类,也就是绘图具体实现类;
  10. 绘图实现类也可能有简单的实现,也可能有复杂的实现,软件渲染、硬件渲染、合成渲染等;
  11. 绘图实现类将2D图形库或者3D图形库绘制结果保存,交给浏览器界面进行展示。
    上述是一个完整的渲染过程,现代网页很多都是动态的,随着网页与用户的交互,浏览器需要不断的重复渲染过程。
    1.3.JavaScript引擎

JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度(如上图所示)。JavaScript代码是在浏览器端解析和执行的,如果需要时间太长,会影响用户体验。那么提高JavaScript的解析速度就是当务之急。JavaScript引擎和渲染引擎的关系如下图所示:

JavaScript语言是解释型语言,为了提高性能,引入了Java虚拟机和C++编译器中的众多技术。现在JavaScript引擎的执行过程大致是:
源代码-→抽象语法树-→字节码-→JIT-→本地代码(V8引擎没有中间字节码)。一段代码的抽象语法树示例如下:

function demo(name) {  
    console.log(name);  
}

抽象语法树如下:

V8更加直接的将抽象语法树通过JIT技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。在V8生成本地代码后,也会通过Profiler采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化,但极大减少了转换时间。
但是在2017年4月底,v8 的 5.9 版本发布了,新增了一个 Ignition 字节码解释器,将默认启动,从此之后将与JSCore有大致相同的流程。做出这一改变的原因为:(主要动机)减轻机器码占用的内存空间,即牺牲时间换空间;提高代码的启动速度;对 v8 的代码进行重构,降低 v8 的代码复杂度(V8 Ignition:JS 引擎与字节码的不解之缘 - CNode技术社区)。
JavaScript的性能和C相比还有不小的距离,可预见的未来估计也只能接近它,而不是与它相比,这从语言类型上已经决定。下面将对V8引擎进行更为细致的介绍。
2.V8引擎
V8引擎是一个JavaScript引擎实现,最初由一些语言方面专家设计,后被谷歌收购,随后谷歌对其进行了开源。V8使用C++开发,,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性。
V8项目代码结构如下:

2.1.数据表示
JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定,这就不像c++或者java等静态类型语言,在编译时候就可以确切知道变量的类型。然而,在运行时计算和决定类型,会严重影响语言性能,这也就是JavaScript运行效率比C++或者JAVA低很多的原因之一。
在C++中,源代码需要经过编译才能执行,在生成本地代码的过程中,变量的地址和类型已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间。由于JavaScript是无类型语言,那就不能像c++那样在执行时已经知道变量的类型和地址,需要临时确定。JavaScript 和C++有以下几个区别:
1.编译确定位置,C++编译阶段确定位置偏移信息,在执行时直接存取,JavaScript在执行阶段确定,而且执行期间可以修改对象属性;

  1. 偏移信息共享,C++有类型定义,执行时不能动态改变,可共享偏移信息,JavaScript每个对象都是自描述,属性和位置偏移信息都包含在自身的结构中;
  2. 偏移信息查找,C++查找偏移地址很简单,在编译代码阶段,对使用的某类型成员变量直接设置偏移位置,JavaScript中使用一个对象,需要通过属性名匹配才能找到相应的值,需要更多的操作。
    在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数两个汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间。示例如下:

在JavaScript中,除了boolean,number,string,null,undefined这五个简单变量外,其它的数据都是对象,V8使用一种特殊的方式来表示他们,进而优化JavaScript的内部表示问题。

在V8中,数据的内部表示由数据的实际内容和数据的句柄构成。数据的实际内容是变长的,类型也是不同的;句柄固定大小,包含指向数据的指针。这种设计可以方便V8进行垃圾回收和移动数据内容,如果直接使用指针的话就会出问题或者需要更大的开销,使用句柄的话,只需修改句柄中的指针即可,使用者使用的还是句柄,指针改动是对使用者透明的。
除少数数据(如整型数据)由handle本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度。一个句柄对象的大小是4字节(32位设备)或者8字节(64位设备),而在JavaScriptCore中,使用的8个字节表示句柄。在堆中存放的对象都是4字节对齐的,所以它们指针的后两位是不需要的,V8用这两位表示数据的类型,00为整数,01为其他。

JavaScript对象在V8中的实现包含三个部分:隐藏类指针,这是v8为JavaScript对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性。

2.2.工作过程
前面有过介绍,V8引擎在执行JavaScript的过程中,主要有两个阶段:编译和运行,与C++的执行前完全编译不同的是,JavaScript需要在用户使用时完成编译和执行。在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销。在V8引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用JIT编译器的全代码生成器从AST直接生成本地可执行代码。这个过程不同于JAVA先生成字节码或中间表示,减少了AST到字节码的转换时间,提高了代码的执行速度。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会。

V8引擎编译本地代码时使用的主要类如下所示:

1.Script:表示JavaScript代码,即包含源代码,又包含编译之后生成的本地代码,即是编译入口,又是运行入口;

  1. Compiler:编译器类,辅组Script类来编译生成代码,调用解释器(Parser)来生成AST和全代码生成器,将AST转变为本地代码;
  2. AstNode:抽象语法树节点类,是其他所有节点的基类,包含非常多的子类,后面会针对不同的子类生成不同的本地代码;
  3. AstVisitor:抽象语法树的访问者类,主要用来遍历异构的抽象语法树;
  4. FullCodeGenerator:AstVisitor类的子类,通过遍历AST来为JavaScript生成本地可执行代码。


JavaScript代码编译的过程大致为:Script类调用Compiler类的Compile函数为其生成本地代码。Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码。本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码。由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间的优化也就无从谈起。

总结
在过去几年,JavaScript在很多领域得到了广泛的应用,然而限于JavaScript语言本身的不足,执行效率不高。Google也推出了一些JavaScript网络应用,如Gmail、Google Maps及Google Docs office等。这些应用的性能不仅受到服务器、网络、渲染引擎以及其他诸多因素的影响,同时也受到JavaScript本身执行速度的影响。然而既有的JavaScript引擎无法满足新的需求,而性能不佳一直是网络应用开发者最关心的。Google就开始了V8引擎的研究,将一系列新技术引入JavaScript引擎中,大大提高了JavaScript的执行效率。相信随着V8引擎的不断发展,JavaScript也会有更广泛的应用场景,前端工程师也会有更好的未来!
那么结合上面对于V8引擎的介绍,我们在编程中应注意:
1.类型。对于函数,JavaScript是一种动态类型语言,JavaScriptCore和V8都使用隐藏类和内嵌缓存来提高性能,为了保证缓存命中率,一个函数应该使用较少的数据类型;对于数组,应尽量存放相同类型的数据,这样就可以通过偏移位置来访问。

  1. 数据表示。简单类型数据(如整型)直接保存在句柄中,可以减少寻址时间和内存占用,如果可以使用整数表示的,尽量不要用浮点类型。
  2. 内存。虽然JavaScript语言会自己进行垃圾回收,但我们也应尽量做到及时回收不用的内存,对不再使用的对象设置为null或使用delete方法来删除(使用delete方法删除会触发隐藏类新建,需要更多的额外操作)。
    4.优化回滚。在执行多次之后,不要出现修改对象类型的语句,尽量不要触发优化回滚,否则会大幅度降低代码的性能。
  3. 新机制。使用JavaScript引擎或者渲染引擎提供的新机制和新接口提高性能。
收起阅读 »

官方的上拉下拉加载...无力吐槽

官方的上拉下拉加载...无力吐槽

例子和文档写的真心烂......

跪求用户体验!!!

官方的上拉下拉加载...无力吐槽

例子和文档写的真心烂......

跪求用户体验!!!

清除文件缓存

看论坛中有很多人在问这个问题,于是就写了一个读取和清除指定目录中文件缓存的方法,分享出来,希望能帮助到需要的人。

//以下是读取指定目录下子目录以及文件的大小的方法  
function showCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) { //通过URL参数获取目录对象或文件对象  
        var fileSize = 0;  
        var directoryReader = entry.createReader();  
        directoryReader.readEntries(function(entries) {   //获取当前目录中的所有文件和子目录  
            for(var i = 0; i < entries.length; i++) {  
                if(entries[i].isFile) {  
                    entries[i].file(function(file) {  
                        fileSize += (file.size * 0.0009766);  
                    }, function(e) {  
                        mui.toast(e.message);  
                    });  
                } else {  
                    entries[i].getMetadata(function(metadata) {  
                        fileSize += (metadata.size * 0.0009766); //1字节=0.0009766kb  
                    }, function() {  
                        mui.toast(e.message);  
                    });  
                }  
            }  
        }, function(e) {  
            mui.toast('文件读取失败');  
        });  
        setTimeout(function() {  
            $('#size').text(Math.ceil(fileSize) + 'kb');  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}  

//以下是清除缓存在指定目录中文件的方法  
function clearCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) {  
        entry.removeRecursively(function(entry) { //递归删除其下的所有文件及子目录  
            mui.toast("缓存清理完成");  
        }, function(e) {  
            mui.toast(e.message);  
        });  
        setTimeout(function() {  
            showCache();  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}

好了,完成了,android和ios下都测试过,没有问题。

继续阅读 »

看论坛中有很多人在问这个问题,于是就写了一个读取和清除指定目录中文件缓存的方法,分享出来,希望能帮助到需要的人。

//以下是读取指定目录下子目录以及文件的大小的方法  
function showCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) { //通过URL参数获取目录对象或文件对象  
        var fileSize = 0;  
        var directoryReader = entry.createReader();  
        directoryReader.readEntries(function(entries) {   //获取当前目录中的所有文件和子目录  
            for(var i = 0; i < entries.length; i++) {  
                if(entries[i].isFile) {  
                    entries[i].file(function(file) {  
                        fileSize += (file.size * 0.0009766);  
                    }, function(e) {  
                        mui.toast(e.message);  
                    });  
                } else {  
                    entries[i].getMetadata(function(metadata) {  
                        fileSize += (metadata.size * 0.0009766); //1字节=0.0009766kb  
                    }, function() {  
                        mui.toast(e.message);  
                    });  
                }  
            }  
        }, function(e) {  
            mui.toast('文件读取失败');  
        });  
        setTimeout(function() {  
            $('#size').text(Math.ceil(fileSize) + 'kb');  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}  

//以下是清除缓存在指定目录中文件的方法  
function clearCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) {  
        entry.removeRecursively(function(entry) { //递归删除其下的所有文件及子目录  
            mui.toast("缓存清理完成");  
        }, function(e) {  
            mui.toast(e.message);  
        });  
        setTimeout(function() {  
            showCache();  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}

好了,完成了,android和ios下都测试过,没有问题。

收起阅读 »

js new Date

new Date()对参数不管是格式还是内容都要求,且只返回字符串

new Date();  
//Fri Aug 21 2015 15:51:55 GMT+0800 (中国标准时间)  
new Date(1293879600000);  
new Date('2011-01-01T11:00:00')  
new Date('2011/01/01 11:00:00')  
new Date(2011,0,1,11,0,0)  
new Date('jan 01 2011,11 11:00:00')  
new Date('Sat Jan 01 2011 11:00:00')  
//Sat Jan 01 2011 11:00:00 GMT+0800 (中国标准时间)  
new Date('sss');  
new Date('2011/01/01T11:00:00');  
new Date('2011-01-01-11:00:00')  
new Date('1293879600000');  
//Invalid Date  
new Date('2011-01-01T11:00:00')-new Date('1992/02/11 12:00:12')  
//596069988000

从上面几个测试结果可以很容易发现

new Date()在参数正常的情况只会返回当前时间的字符串(且是当前时区的时间)
new Date()在解析一个具体的时间的时候,对参数有较严格的格式要求,格式不正确的时候会直接返回Invalid Date,比如将number类的时间戳转换成string类的时候也会导致解析出错
虽然new Date()的返回值是字符串,然而两个new Date()的结果字符串是可以直接相减的,结果为相差的毫秒数。
那么,new Date()能接受的参数格式到底是什么标准呢?(相对于严格要求的多参数传值方法。非严格的单参数(数字日期表示格式)更常用且更容易出错,所以下文只考虑单参数数字时间字符串转换的情况)
参考:http://chitanda.me/2015/08/21/the-trivia-of-js-date-function/

继续阅读 »

new Date()对参数不管是格式还是内容都要求,且只返回字符串

new Date();  
//Fri Aug 21 2015 15:51:55 GMT+0800 (中国标准时间)  
new Date(1293879600000);  
new Date('2011-01-01T11:00:00')  
new Date('2011/01/01 11:00:00')  
new Date(2011,0,1,11,0,0)  
new Date('jan 01 2011,11 11:00:00')  
new Date('Sat Jan 01 2011 11:00:00')  
//Sat Jan 01 2011 11:00:00 GMT+0800 (中国标准时间)  
new Date('sss');  
new Date('2011/01/01T11:00:00');  
new Date('2011-01-01-11:00:00')  
new Date('1293879600000');  
//Invalid Date  
new Date('2011-01-01T11:00:00')-new Date('1992/02/11 12:00:12')  
//596069988000

从上面几个测试结果可以很容易发现

new Date()在参数正常的情况只会返回当前时间的字符串(且是当前时区的时间)
new Date()在解析一个具体的时间的时候,对参数有较严格的格式要求,格式不正确的时候会直接返回Invalid Date,比如将number类的时间戳转换成string类的时候也会导致解析出错
虽然new Date()的返回值是字符串,然而两个new Date()的结果字符串是可以直接相减的,结果为相差的毫秒数。
那么,new Date()能接受的参数格式到底是什么标准呢?(相对于严格要求的多参数传值方法。非严格的单参数(数字日期表示格式)更常用且更容易出错,所以下文只考虑单参数数字时间字符串转换的情况)
参考:http://chitanda.me/2015/08/21/the-trivia-of-js-date-function/

收起阅读 »

如何使用HBuilderX开发微信小程序

mpvue 小程序 微信小程序

注意,本文讲的是使用HBuilderX开发原生微信小程序,不是uni-app。使用uni-app请在HBuilderX中新建uni-app项目

很多开发者需要开发小程序,但小程序的开发IDE却总被众多开发者吐槽。
很多开发者只把微信开发工具当模拟器用,代码编写仍然在其他专业编辑器里。

HBuilder作为专业的开发工具,近期也提供了对微信小程序的开发支持:

  • 强大的代码提示
  • 高效的字处理
  • 保存代码时自动刷新微信模拟器。

下文简单讲解使用步骤。

新建微信小程序

在HBuilderX中新建项目时,支持小程序类型,如下:

小程序项目创建后,默认工程目录如下:

]

导入已有小程序

若已存在微信小程序项目,则可以直接将工程目录拖到HBuidlerX中。

小程序语法提示

语法提示是HBuilder一贯的长项,在HBuilder中对小程序语法也有很好的提示。

小程序JS API提示:

小程序wxml标签提示:

同步到小程序模拟器

HBuilderX支持同步代码到微信开发者工具,如下图所示,点击“微信开发者工具”

系统会尝试检测并启动微信开发者工具。

若是已存在的微信项目(之前已使用微信开发者工具打开过的项目),则会直接打开并显示模拟器、编译器等截面,直接跳到下方第4步骤继续阅读即可。

若是新建的项目,则需要按照如下步骤进行小程序项目的初始化导入。

1、微信开发者工具成功启动后界面:

2、选择小程序项目,并在新打开的窗口中点击右下角的“+”,打开新项目向导:

3、项目目录设置为刚刚在HBuilderX中新建的工程根目录

4、在微信开发者工具中,点击左上角的“编译器”,关闭微信编译器;若暂时不需要调试,也可以将调试器关闭,仅保留模拟器。

5、拖一下HBuilderX和微信开发者工具的位置,像如下方式,左侧为HBuilderX的编辑器截面,右侧为小程序的模拟器截面

之后,在左侧HBuilderX中修改小程序代码,右侧模拟器会自动刷新,如下是一个实际录屏示例:

最后,微信小程序有自己的appid,向微信申请后,把appid填写在项目的project.config.json里,有个appid参数。
有appid才能正式发布上传。

继续阅读 »

注意,本文讲的是使用HBuilderX开发原生微信小程序,不是uni-app。使用uni-app请在HBuilderX中新建uni-app项目

很多开发者需要开发小程序,但小程序的开发IDE却总被众多开发者吐槽。
很多开发者只把微信开发工具当模拟器用,代码编写仍然在其他专业编辑器里。

HBuilder作为专业的开发工具,近期也提供了对微信小程序的开发支持:

  • 强大的代码提示
  • 高效的字处理
  • 保存代码时自动刷新微信模拟器。

下文简单讲解使用步骤。

新建微信小程序

在HBuilderX中新建项目时,支持小程序类型,如下:

小程序项目创建后,默认工程目录如下:

]

导入已有小程序

若已存在微信小程序项目,则可以直接将工程目录拖到HBuidlerX中。

小程序语法提示

语法提示是HBuilder一贯的长项,在HBuilder中对小程序语法也有很好的提示。

小程序JS API提示:

小程序wxml标签提示:

同步到小程序模拟器

HBuilderX支持同步代码到微信开发者工具,如下图所示,点击“微信开发者工具”

系统会尝试检测并启动微信开发者工具。

若是已存在的微信项目(之前已使用微信开发者工具打开过的项目),则会直接打开并显示模拟器、编译器等截面,直接跳到下方第4步骤继续阅读即可。

若是新建的项目,则需要按照如下步骤进行小程序项目的初始化导入。

1、微信开发者工具成功启动后界面:

2、选择小程序项目,并在新打开的窗口中点击右下角的“+”,打开新项目向导:

3、项目目录设置为刚刚在HBuilderX中新建的工程根目录

4、在微信开发者工具中,点击左上角的“编译器”,关闭微信编译器;若暂时不需要调试,也可以将调试器关闭,仅保留模拟器。

5、拖一下HBuilderX和微信开发者工具的位置,像如下方式,左侧为HBuilderX的编辑器截面,右侧为小程序的模拟器截面

之后,在左侧HBuilderX中修改小程序代码,右侧模拟器会自动刷新,如下是一个实际录屏示例:

最后,微信小程序有自己的appid,向微信申请后,把appid填写在项目的project.config.json里,有个appid参数。
有appid才能正式发布上传。

收起阅读 »

ajax如何带上cookie

ajax Cookie

之前都有这样一个理解:
ajax请求时是不会自动带上cookie的,要是想让他带上的话,必须哟啊设置withCredential为true。
这个说法会让人产生完全扭曲的误解,我就是其中之一。
完整的无歧义的表述应该是这样:
1.ajax会自动带上同源的cookie,不会带上不同源的cookie

  1. 可以通过前端设置withCredentials为true, 后端设置Header的方式让ajax自动带上不同源的cookie,但是这个属性对同源请求没有任何影响。会被自动忽略。
  2. 这是MDN对withCredentials的解释: MDN-withCredentials ,我接着解释一下同源。
    众所周知,ajax请求是有同源策略的,虽然可以应用CORS等手段来实现跨域,但是这并不是说这样就是“同源”了。ajax在请求时就会因为这个同源的问题而决定是否带上cookie,这样解释应该没有问题了吧,还不知道同源策略的,应该去谷歌一下看看。

实验
第一步: 建立一个本地服务器

1.新建一个demo文件夹,进入文件夹,用php -S localhost:9000开启一个本地服务器
2.在demo文件夹下新建一个index.php文件, 内容为:

<?php  
    header("Access-Control-Allow-Origin: http://localhost:9999");  
    header('Access-Control-Allow-Credentials:true');  
    $value = "something with cookie";  
    setcookie("testcookie", $value, time() + 3600);  
    echo "cookie has seted";

注意: Access-Control-Allow-Origin必须制定特定的URL,不能是*, 且需要加上Access-Control-Allow-Credentials

第二步: 编写请求测试代码

在桌面上新建一个test.html文件,内容为:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>  
</head>  
<body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000/",  
            type: 'GET',  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
</body>  
</html>
  1. 在desktop目录下起一个服务器,用php -S localhost:9999开启一个本地服务器
    第三步: 测试
    1.在浏览器中访问localhost:9999/test.html,打开调试工具->application->cookie可以看到cookie设置成功。
    2.打开调试工具->netwoek,刷新一下,可以看到一个localhost请求,检查这个localhost请求的Request Headers,发现没有cookie这个头部,说明不同源的请求时不会带上cookie的(即使有CORS)
    3.把test.html放到demo文件夹下,在访问localhost:9000/test.html,查看Request Headers,会发现cookie头部, 说明同源请求自动带上了cookie
    4.把test.html的内容更改为以下内容,请求时会有cookie头部。说明withCredentials起作用了
    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="jquery-2.2.4.min.js"></script>  
    </head>  
    <body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000",  
            type: 'GET',  
            xhrFields: {  
                withCredentials: true // 这里设置了withCredentials  
            },  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
    </body>  
    </html>

    参考:https://zhuanlan.zhihu.com/p/28818954
    https://www.zhihu.com/question/25427931
    同源策略
    http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
    https://stackoverflow.com/questions/2870371/why-is-jquerys-ajax-method-not-sending-my-session-cookie
    服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

同源策略是浏览器最核心也最基本的安全功能首先:web是开放的世界, 需要互联链接. 你的网站, 可以使用别人的图片, img, 使用别人 script 做统计, 做广告联盟

假设没有同源, 互联网世界是什么样?链接跳转导致的问题. http://a.com , 放一个链接到 icbc.com, 然后 window.open来打开, 获取窗口句柄, 然后可以拥有对这个页面完全的控制权. 拦截表单,捕获数据,将账号密码上传到a.com.ajax请求, 要啥就有啥. 你登录jd.com; 然后打开a.com, 通过ajax 请求http://jd.com 的用户信息接口, 这时候因为访问的jd.com,所以浏览器自动带上了jd的cookie,然后获取到你的订单list ,昵称, 所有私密信息.所以,需要要同源策略

在同一个域内,客户端脚本可以任意读写同源内的资源,dom,cookie;但是不同的域,就不行.

继续阅读 »

之前都有这样一个理解:
ajax请求时是不会自动带上cookie的,要是想让他带上的话,必须哟啊设置withCredential为true。
这个说法会让人产生完全扭曲的误解,我就是其中之一。
完整的无歧义的表述应该是这样:
1.ajax会自动带上同源的cookie,不会带上不同源的cookie

  1. 可以通过前端设置withCredentials为true, 后端设置Header的方式让ajax自动带上不同源的cookie,但是这个属性对同源请求没有任何影响。会被自动忽略。
  2. 这是MDN对withCredentials的解释: MDN-withCredentials ,我接着解释一下同源。
    众所周知,ajax请求是有同源策略的,虽然可以应用CORS等手段来实现跨域,但是这并不是说这样就是“同源”了。ajax在请求时就会因为这个同源的问题而决定是否带上cookie,这样解释应该没有问题了吧,还不知道同源策略的,应该去谷歌一下看看。

实验
第一步: 建立一个本地服务器

1.新建一个demo文件夹,进入文件夹,用php -S localhost:9000开启一个本地服务器
2.在demo文件夹下新建一个index.php文件, 内容为:

<?php  
    header("Access-Control-Allow-Origin: http://localhost:9999");  
    header('Access-Control-Allow-Credentials:true');  
    $value = "something with cookie";  
    setcookie("testcookie", $value, time() + 3600);  
    echo "cookie has seted";

注意: Access-Control-Allow-Origin必须制定特定的URL,不能是*, 且需要加上Access-Control-Allow-Credentials

第二步: 编写请求测试代码

在桌面上新建一个test.html文件,内容为:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>  
</head>  
<body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000/",  
            type: 'GET',  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
</body>  
</html>
  1. 在desktop目录下起一个服务器,用php -S localhost:9999开启一个本地服务器
    第三步: 测试
    1.在浏览器中访问localhost:9999/test.html,打开调试工具->application->cookie可以看到cookie设置成功。
    2.打开调试工具->netwoek,刷新一下,可以看到一个localhost请求,检查这个localhost请求的Request Headers,发现没有cookie这个头部,说明不同源的请求时不会带上cookie的(即使有CORS)
    3.把test.html放到demo文件夹下,在访问localhost:9000/test.html,查看Request Headers,会发现cookie头部, 说明同源请求自动带上了cookie
    4.把test.html的内容更改为以下内容,请求时会有cookie头部。说明withCredentials起作用了
    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="jquery-2.2.4.min.js"></script>  
    </head>  
    <body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000",  
            type: 'GET',  
            xhrFields: {  
                withCredentials: true // 这里设置了withCredentials  
            },  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
    </body>  
    </html>

    参考:https://zhuanlan.zhihu.com/p/28818954
    https://www.zhihu.com/question/25427931
    同源策略
    http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
    https://stackoverflow.com/questions/2870371/why-is-jquerys-ajax-method-not-sending-my-session-cookie
    服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

同源策略是浏览器最核心也最基本的安全功能首先:web是开放的世界, 需要互联链接. 你的网站, 可以使用别人的图片, img, 使用别人 script 做统计, 做广告联盟

假设没有同源, 互联网世界是什么样?链接跳转导致的问题. http://a.com , 放一个链接到 icbc.com, 然后 window.open来打开, 获取窗口句柄, 然后可以拥有对这个页面完全的控制权. 拦截表单,捕获数据,将账号密码上传到a.com.ajax请求, 要啥就有啥. 你登录jd.com; 然后打开a.com, 通过ajax 请求http://jd.com 的用户信息接口, 这时候因为访问的jd.com,所以浏览器自动带上了jd的cookie,然后获取到你的订单list ,昵称, 所有私密信息.所以,需要要同源策略

在同一个域内,客户端脚本可以任意读写同源内的资源,dom,cookie;但是不同的域,就不行.

收起阅读 »