多终端自定义window方法

1,288 阅读3分钟

这是我参与更文挑战的第20天,活动详情查看: 更文挑战

项目问题,最近要将游戏项目布置在电视端,甲方的机顶盒是华为的(巨坑),很多权限拿不到,按键冲突也是常有的事,今天的问题是,电视端、移动端按键有限,项目中如何自定义window方法,传给后端去调用

前言

JavaScript的window对象

所有浏览器都支持 window 对象,所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员,你可以在任何地方使用window已有的属性和方法,比如 常用对象对象有location ,history ,navigator、screen 常用的方法有:window.open()、window.close() 、window.moveTo()、window.resizeTo()、alert()、confirm()、prompt() 常用的属性有:var w=window.innerWidt || document.documentElement.clientWidth || document.body.clientWidth(这个栗子具有代表性)

自定义window方法

已有的window被占用或者不足以满足某个功能(比如我现在),需要在React或者Vue里自定义window方法方便 后端去调用。

// 方式一
window.functionA = () => {}
window.functionB = () => {}
window.functionC = () => {}

// 方式二, 引用JQuery
$.extend(window, {
    A:function(){},
    B:function(){},
    C:function(){},
})

// 方式三
(() => {
   var defining = {
      A: function() { },
      B: function() { },
      C: function() { }
};
Object.keys(defining).forEach(key => {
    window[key] = defining[key];
    });
})(); 

// 方式四(window对象下的变量是对象时)
window[变量] = function(){}

特别注意:有的同学说方法定义失败,也许你没用箭头函数,所以别忘了 let _this = this

Vue 自定义window方法

下面方法不写在全局app.vue里,而写在组件里,好处是避免因为涉及到作用域、以及组件内属性函数的异步执行所导致 vue 的方法在第三方组件内无法执行,或者在其外部执行时存在执行顺序的问题(本意是想在第三方组件函数执行完毕后再执行我的函数)。

methods:{
  // 创建电话组件
  createCallComponent(phoneNum) {
    if (window.callComp && !window.callComp.isDestroyed) {
      return;
    };
    window.callComp = new UdeskCallcenterComponent({
       container: document.body,
       token: this.agentToken,
       subDomain: this.subDomain,
       showManualScreenPop: false,
       onHangup: function(conversation) { // 电话挂断时执行
         getCallId(conversation.call_id);  // 这里调用绑定后的方法,并传入callid
         window.callComp.destroy(); // 销毁电话组件
         window.callComp = null; // 销毁电话组件
       }
    });
  },

  // 每次呼叫挂断时获取当前callid,后续把获取值保存到后台接口中
  callIdFun(val) {
    this.call_id = val;
    console.log(this.call_id);
    ...
  },   
}

mounted() {
  // 把vue组件的methods方法绑定到window
  window['getCallId'] = val => {
    this.callIdFun(val)
  }
}

在混合开发过程中,通过jsBridge方法,H5可以调用客户端(ios,android)的内部方法,同样,客户端也需要能调用H5页面里定义的js方法,但是在vue里,所有的方法都是在组件内部声明的,也只能在组件内部调用,并没有绑定window对象下面,可以使用下列方法在vue组件里定义的方法暴露给window对象。

created() {
},
mounted() {
    // 将backToday方法绑定到window下面,提供给外部调用
     window['backToday'] = () => {
        this.KeyBack()
     }
 },
 methods: {
    KeyBack() {
        // to do something
    }
 }
// 有些情况别忘了 let _this = this

混合开发过程中,vue自定义的window方法如何与原生通信 因为Android无法直接调用每个Vue对象的methods里的方法,只能调用window下面的function,所以我们需要一个模型: 先将消息发至window下的function,通过window下的function调用父组件的代码,再由父组件调用子组件的代码。Vue代码:使用Vue cli直接创建一个project, 然后在config/index.js中将host由localhost改为电脑的本地Ip,以方便使用在手机端去调试. 举个栗子:

// 建立一个ChildComponent.vue的子组件,在其中定义receiveMsgFromParent(msg)函数
<template>
  <div id="childComponent" style="background-color: #42b983">
    <div>{{childMsg}}</div>
    <br/>
    <button @click="toSecondComponent">去第二个界面</button>
  </div>
</template>
<script>
  export default {
    name: 'childComponent',
    data: function () {
      return {
        childMsg: "This is child component"
      }
    },
    methods: {
      receiveMsgFromParent: function (msg) {
        alert("" + msg);
        this.childMsg = "receive msg = " + msg;
      },
      toSecondComponent: function () {
        this.$router.replace({path: "/SecondComponent"});
      }
    },
    created() {
      this.$parent.setComponent(this);
    }
  }
</script>

在App.vue定义一个window下的function,用来让Android的WebView找到该方法

// 同时在 App.vue里定义一个setComponent(component)方法
var curComponent;
export default {
    name: 'App',
    components: {
        ChildComponent,
    },
        data: function() {
            return {
            richtext: 'App.vue receive msg',
        };
    },
        methods: {
            setComponent: function(component) {
            curComponent = component;
        },
    },
        created() {
            this.$router.push({ path: 'ChildComponent' });
        },
    };

    window['receiveMsgFromNative'] = function(msg) {
    curComponent.receiveMsgFromParent(msg);
};

这个Window下的function如何于路由最上层的Component建立联系,每个ChildComponent在created()时都去调用App.vue的 this.$parent.setComponent(this);方法,这样就能正确的收到消息传递给路由最上层的vue组件了。

安卓部分

Android这块,根据网友反馈,建议使用使用QQ x5浏览器去加载的,他崩溃率比较低。这里注意的是如果要传递Stirng类型的数据给Js的话,需要在数据前面加上引号。

题外话

如果抛开头痛的电视端window函数,我们平常写全局方法,有哪几种方式?

// 方式一 ,直接在main.js里写
Vue.prototype.changeData = function (){ //changeData是自定义的函数名
  console.log('222');
}
// 组件内直接通过this运行函数

// 方式二 ,创建一个global.js文件,然后main.js引用(推荐)
exports.install = function (Vue, options) {
   Vue.prototype.text1 = function (){//全局函数1
    console.log('全局函数1');
    };
    Vue.prototype.text2 = function (){//全局函数2
    console.log('全局函数2');
    };
};
// Vue.use(global);//将全局函数当做插件来进行注册,组件内仍然用this调用

React自定义window方法

以ReactNative为例

React Native 指定页面物理返回监听,RN的项目我还没接触过,这里引用网友的栗子。注意:回调函数onBackAndroid中的return true是必不可少的。

componentWillMount(){
        BackHandler.addEventListener('hardwareBackPress', this.onBackAndroid);
}
componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.onBackAndroid);
}
onBackAndroid = () => {
    if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
    //最近2秒内按过back键,可以退出应用。
        BackHandler.exitApp();
          return;
        }
        this.lastBackPressed = Date.now();
        ToastAndroid.show('再按一次退出应用',ToastAndroid.SHORT);
        return true;
    };
onBackAndroid = () => {
    if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
       //最近2秒内按过back键,可以退出应用。
        BackHandler.exitApp();
        return;
    }
        this.lastBackPressed = Date.now();
        ToastAndroid.show('再按一次退出应用',ToastAndroid.SHORT);
        return true;
};

原生JS监听手机物理返回键

JavaScript没有监听物理返回键的API,所以只能使用 popstate 事件监听。

pushHistory(); 
   window.addEventListener("popstate", function(e) { 
     window.location = 'http://www.baidu.com';
   }, false); 
   function pushHistory() { 
     var state = { 
       title: "title", 
       url: "#"
     }; 
     window.history.pushState(state, "title", "#"); 
   }
此声明函数在xback.js文件里有,在app.js里必须再声明一次,不然监听返回事件失败。

/**
 * 使用 HTML5 的 History 新 API pushState 来曲线监听 Android 设备的返回按钮
 * XBack.listen(function(){
    alert('oh! you press the back button');
  });
 */
;!function(pkg, undefined){
  var STATE = 'x-back';
  var element;
  var onPopState = function(event){
    event.state === STATE && fire();
  }
  var record = function(state){
    history.pushState(state, null, location.href);
  }
  var fire = function(){
    var event = document.createEvent('Events');
    event.initEvent(STATE, false, false);
    element.dispatchEvent(event);
  }
  var listen = function(listener){
    element.addEventListener(STATE, listener, false);
  }
  ;!function(){
    element = document.createElement('span');
    window.addEventListener('popstate', onPopState);
    this.listen = listen;
    record(STATE);
  }.call(window[pkg] = window[pkg] || {});
}('XBack');
// 调用方式

XBack.listen(function(){
  alert('back');
});

参考资料

jsBridge文档