1、不使用 min.js 在 wp-config.php 中增加一行 define( 'SCRIPT_DEBUG', true ); 即可让 WordPress 前端不使用 xx.min.js,直接使用 xx.js。 其他更多的调试设置,包括输出 php 日志、打印 sql 执行语句等,请阅读:debugging-in-wordpress 2、BUG,无法删除中文命名的模板 今天遇到一个 WordPress 自身的 BUG,就是当你在编辑文章的时候,创建了一个文章模板,并使用中文命名。然后当你删除这个模板的时候,页面不会有任何提示,但它提交的 ajax post 请求其实返回了一个404错误: 其表面原因是这个 URL 中,在主题名与模板名之间,变成了两个斜杠/。URL 中的 %e8%87%aa%e5%ae%9a%e4%b9%89%e6%a8%a1%e6%9d%bf22 是模板名「自定义模板22」的 URL 编码。 我的开发环境: WordPress 6.1.1 Nginx 1.23.3 3、DEBUG 在源码中一通乱找跟 'delete template' 有关的信息,先是定位到 core-data.js 文件。 其中 path = "/wp/v2/templates/twentytwentytwo//%e8%87%aa%e5%ae%9a%e4%b9%89%e6%a8%a1%e6%9d%bf22" 组成 path 的 recordId 应该来自 edit-post.js 中的 template.id。 template.id = "twentytwentytwo//%e8%87%aa%e5%ae%9a%e4%b9%89%e6%a8%a1%e6%9d%bf22" template 来自于 这一小段代码实在看得人太痛苦了,对 javascript 的语法糖简直是无所不用其极。😤 (1) => 箭头函数 (x) => x + 6 相当于 function(x){ return x + 6; } (2) {} 解构 const {foo, bar} = obj 相当于 const foo = obj.foo const bar = obj.bar (3) (0, function)(params) 间接调用 (0, function_name)(params); 等价于 function_name(params); 但是 (0, function_name)(params) 的变量作用域会被扩大。 其本质是: fun1 = (0, function_name) // 逗号隔开的多个表达式,会返回最后一个表达式的结果。(所以前头这个 0,你也可以用任意其他数字或表达式) fun1(params) // 此时 fun1 仍是 function_name,但是内部作用域变了 但是上面的 useSelect 函数源码我还是没看懂。 总之最后能追踪到的就是在 getEditedPostTemplate 函数中,它给加上了双斜杠! 4、解决办法 (1) 已经提交了 BUG 给 WordPress 官网,等他们团队解决。 (2) URL 中的双斜杠处理还跟 HTTP 服务程序有关,我用的是 Nginx, 网上可以找到 Nginx 处理双斜杠的方法。 (3) 临时修改,可以直接在浏览器开发者工具的 console 窗口中,修改 path 变量,删除一个斜杠即可。
Laravel 6 - laravel/ui 中的 vue 与 jquery
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全局对象中。理论上我们就可以直接在前端页面的代码中直接使用$符号了: <script type="application/javascript"> console.log($('.container')); </script> 但是实际上它却报错了:Uncaught ReferenceError: $ is not defined。$未定义?它不是已经在/resources/js/bootstrap.js中注册了$符号了嘛。而如果我们将前端页面的这个代码修改一下: <script type="application/javascript"> window.onload = function() { console.log($('.container')); } // console.log($('.container')); </script> 在window.onload事件函数中使用这个$符号却是可行的。 这其实是由于laravel/ui前端模版页面app.blade.php在引入app.js文件的<script>标签中,使用了defer属性:<script src="{{ asset('js/app.js') }}" defer></script> 。defer的意思是告诉浏览器将该脚本文件异步下载并延迟执行。 关于浏览器对JavaScript文件的下载执行顺序,参考该讨论:https://segmentfault.com/q/1010000000640869 关于原生的 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加载,并将<script>标签移到html文件尾 既然如上面所诉,那么我们把laravel/ui前端模版页面中<script src='app.js'>标签的defer属性去掉会怎么样呢?<script src="{{ asset('js/app.js') }}" ></script> [Vue warn]: Cannot find element: #app 可以在浏览器的控制台窗口看到它报错了!jquery可以用了,却引发了vue的错误。 原因是在laravel/ui的前端模版app.blade.php中,这个<script>标签是放在文件头的,浏览器解析到这里后就会“阻塞”地去下载这个app.js文件并执行。而在app.js文件中,它创建了一个vue实例,并绑定到id为 '#app' 的这个<div>元素。但此时浏览器还没解析到前端页面的<div id="app"> 这一句,所以此时是找不到 '#app' 这个元素的。 所以,在去掉defer属性的同时,我们还需要将这个<script>标签移动到html文件的尾部。比如将app.blade.php修改成如下样式: ... ... <script src="{{ asset('js/app.js') }}"></script> @yield('js') // 后续继承这个app.blade的模版页都可以在这个section('js')中引入JavaScript // 在这个section('js')中就能直接使用jquery了。 </body> </html> ps:顺便吐槽一下,我猜测Laravel之所以把app.js文件放在html头部使用defer属性加载。是因为所谓的“前端工程化”(我觉得应该叫“前端复杂化”)后,会导致打包出来的app.js文件特别大,所以它必须让浏览器在解析文件头时就开始异步下载app.js文件。 b. 使用DOMContentLoaded事件 如果不改动默认的<script src="app.js">标签的话,上面我们已经试过可以在window.onload事件函数中直接使用jquery。但windows.onload事件的触发时间太滞后了,我们可以采用DOMContentLoaded事件。在DOMContentLoaded事件触发之前,<script src="app.js" defer>标签就已经下载并执行完了,app.js中的jquery注册已经完成。所以在DOMContentLoaded事件函数中就可以直接使用jquery了。 <script type="application/javascript"> document.addEventListener("DOMContentLoaded", function(event) { console.log($('.container')); }); </script> c. 从app.js中剥离jquery 更彻底的解决方案就是不使用 laravel/ui 默认的将vue与jquery、bootstrap统一打包进app.js这种方法,可以将二者分开打包。 或者再简单一点,对于我这种前端苦手,由于比较少用,可以把app = new Vue({}); 这一块从app.js文件中删掉,哪个页面要用vue了,再单独在那个页面创建这个vue实例。
【转】实用的javascript调试小技巧
首先得强调一下调试前端javascript的基本命令:console.log(obj),用来在浏览器控制台输出对象。其实除了console.log,还有各种console.error、console.warn、console.info、console.assert等命令。console代表的是浏览器控制台,firefox与chrome的console命令大部分是通用的,更详细的命令介绍可以参考:Chrome Console API Reference (ps:在旧版本的ie,比如ie9,如果在js代码中使用了console.log,那么只有在打开F12,即ie的开发者工具时候,才能识别console对象。如果没有打开ie的开发者工具,会导致脚本无法识别console,js代码无法执行😰 @20171029) 原文地址:10+ 实用的 JavaScript 调试小技巧 1. debugger 除了console.log,debugger就是另一个我很喜欢的快速调试的工具,将debugger加入代码之后,Chrome会自动在插入它的地方停止,很像C或者Java里面打断点。你也可以在一些条件控制中插入该调试语句,譬如: if (thisThing) { debugger; } 2. 将Objects以表格形式展示 有时候我们需要看一些复杂的对象的详细信息,最简单的方法就是用console.log然后展示成一个列表状,上下滚动进行浏览。不过似乎用console.table展示成列表会更好呦,大概是介个样子: var animals = [ { animal: 'Horse', name: 'Henry', age: 43 }, { animal: 'Dog', name: 'Fred', age: 13 }, { animal: 'Cat', name: 'Frodo', age: 18 } ]; console.table(animals); 3. 多屏幕尺寸测试 Chrome有一个非常诱人的功能就是能够模拟不同设备的尺寸,在Chrome的Inspector中点击toggle device mode按钮,然后就可以在不同的设备屏幕尺寸下进行调试咯: 4. 在Console快速选定DOM元素 在Elements选择面板中选择某个DOM元素然后在Console中使用该元素也是非常常见的一个操作,Chrome Inspector会缓存最后5个DOM元素在它的历史记录中,你可以用类似于Shell中的$0等方式来快速关联到元素。譬如下图的列表中有‘item-4′, ‘item-3’, ‘item-2’, ‘item-1’, ‘item-0’这几个元素,你可以这么使用: 5. 获取某个函数的调用追踪记录 JavaScript框架极大方便了我们的开发,但是也会带来大量的预定义的函数,譬如创建View的、绑定事件的等等,这样我们就不容易追踪我们 自定义函数的调用过程了。虽然JavaScript不是一个非常严谨的语言,有时候很难搞清楚到底发生了啥,特别是当你需要审阅其他人的代码的时候。这时 候console.trace就要起作用咯,它可以帮你进行函数调用的追踪。譬如下面的代码中我们要追踪出car对象中对于funcZ的调用过程: var car; var func1 = function() { func2(); } var func2 = function() { func4(); } var func3 = function() { } var func4 = function() { car = new Car(); car.funcX(); } var Car = function() { this.brand = ‘volvo’; this.color = ‘red’; this.funcX = function() { this.funcY(); } this.funcY = function() { this.funcZ(); } this.funcZ = function() { console.trace(‘trace car’) } } func1(); 这边就可以清晰地看出func1调用了func2,然后调用了func4,func4创建了Car的实例然后调用了car.funcX。 6. 格式化被压缩的代码 有时候在生产环境下我们发现了一些莫名奇妙的问题,然后忘了把sourcemaps放到这台服务器上,或者在看别人家的网站的源代码的时候,结果就 看到了一坨不知道讲什么的代码,就像下图。Chrome为我们提供了一个很人性化的反压缩工具来增强代码的可读性,大概这么用: 7. 快速定位调试函数 当我们想在函数里加个断点的时候,一般会选择这么做: 在Inspector中找到指定行,然后添加一个断点 在脚本中添加一个debugger调用 不过这两种方法都存在一个小问题就是都要到对应的脚本文件中然后再找到对应的行,这样会比较麻烦。这边介绍一个相对快捷点的方法,就是在console中使用debug(funcName)然后脚本会在指定到对应函数的地方自动停止。这种方法有个缺陷就是无法在私有函数或者匿名函数处停止,所以还是要因时而异: var func1 = function() { func2(); }; var Car = function() { this.funcX = function() { this.funcY(); } this.funcY = function() { this.funcZ(); } } var car = new Car(); 8. 禁止不相关的脚本运行 当我们开发现代网页的时候都会用一些第三方的框架或者库,它们几乎都是经过测试并且相对而言Bug较少的。不过当我们调试我们自己的脚本的时候也会一不小心跳到这些文件中,引发额外的调试任务。解决方案呢就是禁止这部分不需要调试的脚本运行,详情可见这篇文章:: javascript-debugging-with-black-box。 9. 在较复杂的调试情况下发现关键元素 在一些复杂的调试环境下我们可能要输出很多行的内容,这时候我们习惯性的会用console.log, console.debug, console.warn, console.info, console.error这些来进行区分,然后就可以在Inspector中进行过滤。不过有时候我们还是希望能够自定义显示样式,你可以用CSS来定 义个性化的信息样式: console.todo = function(msg) { console.log(‘ % c % s % s % s‘, ‘color: yellow; background - color: black;’, ‘–‘, msg, ‘–‘); } console.important = function(msg) { console.log(‘ % c % s % s % s’, ‘color: brown; font - weight: bold; text - decoration: underline;’, ‘–‘, msg, ‘–‘); } console.todo(“This is something that’ s need to be fixed”); console.important(‘This is an important message’); 在console.log()中你可以使用%s来代表一个字符串 , %i 来代表数字, 以及 %c 来代表自定义的样式。 10.…
javascript的面向对象
1. 原始类型和对象类型 javascript的数据类型分为两类,原始类型(primitive type)和对象类型(object type)。原始类型包括javascript已定义的数值、字符串、布尔值。对象类型包括用户定义的对象以及函数(对,函数在javascript中其实也是一种对象)。原始类型的赋值是传值赋值,对象类型的赋值是引用赋值。在下面的例子可以看出来。 var n = 10; var m = n; // 传值赋值,m的值等于n m = 13; console.log('n = %d, m = %d.', n, m); // n = 10, m = 13. var onePerson = {}; // 新建一个空对象 onePerson.fullname = 'HarryPotter'; // 为对象新建属性fullname onePerson.age = 21; // 为对象新建属性age onePerson.talk = function () { console.log('Hi, my name is %s. My age is %d.', this.fullname, this.age); } // 为对象新建方法talk,在对象的方法中,this指向该对象 var otherPerson = onePerson; // 对象赋值是引用赋值,otherPerson就是onePerson,二者共同指向一块内存地址。 otherPerson.fullname = 'Kate'; // 等同于修改了onePerson.fullname onePerson.talk(); // Hi, my name is Kate. My age is 21. 2. 将函数作为构造函数 javascript的函数主要有三种调用方式。作为函数直接调用,作为对象的方法调用,作为对象的构造函数调用。 function Person() { global_firstname = 'funway'; // 定义一个全局变量,无法被对象继承 var local_lastname = 'wang'; // 定义一个局部变量,无法被对象继承 this.fullname = 'funway.wang'; // 定义一个继承属性,可以被对象继承 console.log('In Person function as a constructor.'); } Person(); // 直接作为函数调用,打印 "In Person function as a constructor." AA = new Person(); // 作为构造函数调用, 同样会打印 "In Person function as a constructor." console.log(global_firstname); // "funway" console.log(AA.global_firstname); // undefined console.log(AA.local_lastname); // undefined console.log(AA.fullname); // "funway.wang" 上面的代码中,我们通过将Person函数作为构造函数创建了一个新的对象AA,用C++的面向对象来看,函数名Person就相当于一个类名。 实际上,Person作为一个javascript函数,默认有一个prototype属性。prototype属性值是一个对象,我们叫做原型对象。当函数作为构造函数被调用时候,该prototype原型对象就会被当作新对象的原型,所有新创建的对象均有一个__proto__对象,指向该原型对象。另外,prototype原型对象内部有一个constructor属性,指向构造函数Person,这个constructor负责调用构造函数对新对象进行初始化。可以这么说,AA = new Person() 相当于执行了以下三步: AA = {}; // 创建空对象 AA.__ptoto__ = Person.prototype; 执行AA.__proto__.constructor指向的函数对AA进行初始化,创建AA对象的属性 上图中的Object与Function这俩个底层的概念这里就不多解释了,javascript的Object与Function的关系看到哭。。。=。=# 3. prototype原型,原型链与动态继承 从上面可以看出,由Person构造函数构造的对象,都有一个默认的__proto__属性指向Person.prototype,那么我对Person.prototype的修改都会实时的传递到所有Person对象上。另外,对象可以直接通过.属性名/.方法名访问到其原型对象的属性或者方法,前提是该对象本地没有重名的属性/方法。这就是javascript动态继承的基础。而原型链的意义就是,要访问一个对象x的属性/方法的时候,会先查找对象本地空间,没有,则查找x.__proto__空间,然后再x.__proto__.__proto__。这样递归直到找到所要的属性/方法或到达底层的Object基本对象。 function Person(name) { this.name = name? name : 'noname'; } bill = new Person('bill'); // 创建新对象 kate = new Person('kate'); // 创建新对象 console.log(bill.name); // "bill" console.log(bill.age); // undefined,bill对象没有该属性 Person.prototype.age = 18; // 增加age属性,所有的Person对象都自动拥有该属性。 Person.prototype.sayHi = function() { console.log('hi, my name is %s, i am %d yeas old.', this.name, this.age); } // 增加sayHi方法,所有的Person对象都自动拥有该方法 bill.sayHi(); // "hi, my name is bill, i am 18 yeas old." kate.sayHi(); // "hi, my name is kate, i am 18 yeas old." bill.age = 25; // 给bill对象添加了一个本地属性age,之后bill.age将不再访问bill.__proto__.age console.log(bill.age); // 25,访问的是bill.age(不会再跳进去访问bill.__proto__.age了)…