Laravel 应该是在5.几之后吧,其laravel/ui项目的前端框架默认使用Bootstrap与Vue。
1. 无法打印vue实例
对于vue官方文档中的例子,我们都可以在浏览器console窗口通过app变量打印出vue实例。
但是在laravel/ui的页面中,我们打印app变量时候,输出的却是id="app"的那个dom元素,并不是在/resources/js/app.js中定义的vue实例。
const app = new Vue({
el: '#app',
});
原因是laravel mix在编译打包js文件的时候,会将每个js源文件作为一个独立的作用域,这样可以使不同js文件中的变量互不干扰。而这个app变量(打包时候还会将const变成var。。。=。=# js真是迷)的作用域就只在该/resources/js/app.js文件中。在其他地方是无法读取该变量的。
我们可以看一下laravel mix打包后生成的/pulbic/js/app.js文件,整个/resources/js/app.js源文件的代码(包括app变量)都被function(module, exports, __webpack_require__) 这个匿名函数包裹起来,所以app变量只作为该匿名函数的一个局部变量。/*!*****************************!*\
!*** ./resources/js/app.js ***!
\*****************************/
/***/ (function(module, exports, __webpack_require__) {
/**
* ...
*/
var app = new Vue({
el: '#app'
});
/***/ }),
一个题外话,关于webpack打包的基本原理。(laravel-mix就是基于webpack的封装)
webpack在打包项目中的js源文件,最终生成一个app.js的时候。我们来看一下最终的app.js的简化结构:(function(modules) { // 这是一个立即执行函数(IIFE)的形式
对于modules实参数组中的每个module:
执行module源代码 // 一个module就代表一个被打包的js源文件
})({"module_filename_1": (function() { module_1 源码 }),
"module_filename_2": (function() { module_2 源码 })
// 这就是modules的实参数组,每个js源文件的代码都被包裹在闭包中
});
这样子,每个js源文件就是一个module,用闭包将它们的作用域隔离开。然后通过IIFE的形式立即执行所有module闭包,即执行每个js源文件。
如果非要在全局范围使用该app变量,可以在/resources/js/app.js源文件中将该变量定义为window全局对象的一个属性:window.app = new Vue({
el: '#app',
});
这样在浏览器的console窗口打印app时候,就能打印出该vue实例了。
2. 无法立即使用jquery
我们来看一下laravel/ui是如何引入jquery的,在/resources/js/app.js中,通过require('./bootstrap'); 引入同目录下的bootstrap.js文件:try {
window.Popper = require('popper.js').default; // bootstrap的tooltips与popovers组件需要popper.js
window.$ = window.jQuery = require('jquery'); // 导入并注册jquery全局变量
require('bootstrap'); // 导入bootstrap
} catch (e) {}
所以我们可以看到,它已经将jQuery以及$符号注册到window全局对象中。理论上我们就可以直接在前端页面的代码中直接使用$符号了:
console.log($('.container'));
但是实际上它却报错了:Uncaught ReferenceError: $ is not defined。$未定义?它不是已经在/resources/js/bootstrap.js中注册了$符号了嘛。而如果我们将前端页面的这个代码修改一下:
window.onload = function() {
console.log($('.container'));
}
// console.log($('.container'));
在window.onload事件函数中使用这个$符号却是可行的。
这其实是由于laravel/ui前端模版页面app.blade.php在引入app.js文件的 。defer的意思是告诉浏览器将该脚本文件异步下载并延迟执行。
关于原生的 window.onload 、DOMContentLoaded 与 jquery的 $(document).ready 之间的区别,参考:https://api.jquery.com/ready/。(实际上去看jquery的源码会发现,jQuery.ready就是由DOMContentLoaded触发的)
总结一下js代码在浏览器中的执行顺序:
由于defer属性,使得当浏览器解析并执行console.log($('.container')); 这一句的时候,app.js文件还没被执行,jquery还没被引入。所以这时候“$ is not defined”。如果把这一句放在 window.onload 事件函数中,window.onload事件是在所有资源(包括图片、css、script)都加载完后才触发的,此时app.js已经下载并执行了。那么这时候再调用$符号就是OK的。
2.1 如何补救
a. 取消defer加载,并将
既然如上面所诉,那么我们把laravel/ui前端模版页面中
[Vue warn]: Cannot find element: #app
可以在浏览器的控制台窗口看到它报错了!jquery可以用了,却引发了vue的错误。
原因是在laravel/ui的前端模版app.blade.php中,这个
所以,在去掉defer属性的同时,我们还需要将这个
...
@yield('js')
// 后续继承这个app.blade的模版页都可以在这个section('js')中引入JavaScript
// 在这个section('js')中就能直接使用jquery了。