引言:
要成为一名的优秀的前端工程师,首先最基本的就是要成为一名优秀的“JavaScripter”(杜撰词,意为使用JS编写代码的人)。一名优秀的Javascripter,除了要对JS基础知识如数家珍,更要对JS的运行机制、底层原理了然于心。对于想要在前端长期、深度发展的Javascripter,读懂JS类库源码是前进道路上不可或缺的一关。
jQuery作为前端发展史上的一个经典类库、前端模块化的起点,对前端发展有不可磨灭的贡献。如今的前端,虽然是框架大行其道,难见jQuery踪影,但jQuery中的很多优秀而经典编程思想依然影响着现在的Javascripters。那么就站在jQuery的肩膀上,开启JS进阶之路。
写此文章以作为阅读的记录,小标题大部分是曾经产生的疑问。
目录:
一、为什么引入jQuery后,jQuery中定义的变量不会与同名的全局变量冲突,而且都可以正常使用?
二、为什么引入jQuery后,能直接以$.xxx或$().xxx的形式调用,而不是定义一个命名空间接收返回值?
一、为什么jQuery中定义的变量不会与同名全局变量冲突而且都可以正常使用?
答案很简单,是「闭包」,但单纯的知道闭包并没有太大意义,因为在闭包的背后,关系的是前端的发展。
1995年,网页从静态跨入动态后,后端需要完成页面数据绑定的工作,SSR兴起。在SSR时代,前端要做的工作很少,只需完成页面结构+样式这样简单的工作,绝大部分工作都由后端完成,前端的代码量非常少。这个时代前端还不被重视,甚至前端的活后端人员就能完成。
AJAX技术出现后,谷歌基于AJAX开发了Gmail和Google Map两款应用,在当时可谓惊艳众人,自此,WEB迎来CSR时代,服务器分层,并且不再负责数据绑定,前后端分离有了雏形。这时的前端不仅要写页面和结构,还要渲染数据,代码量逐渐增多,因此也引发了一个问题:全局变量污染。当时解决这个问题的方法是,使用对象的形式来储存变量,这一操作大量减少了全局变量,只需要有意使用不同的对象名称即可,这里的对象也被称为「命名空间 nameSpace」。使用命名空间的这种方式也叫「单例设计模式」,而在实际开发中,单例设计模式常与闭包一起出现:利用函数私有上下文的特点,需求都在函数内部完成,避免变量污染问题,而函数拥有返回值的特性就能达到暴露内部成员供外部使用的目的,命名空间接收函数返回值即可。jQuery实现的核心技术之一就是对函数闭包的使用。
let name = 'window' // 全局变量
let nameSpace = (function () {
// ...
let age = "19";
let name = 'nameSpace' // 同名私有变量
function sum(a, b) { // 内部方法
return a + b;
}
return {
//将提供给外部使用的功能、数据等放在对象中返回给命名空间
sum: sum,
};
})();
// 外部要使用sum这个功能,只需nameSpace.sum
let res = nameSpace.sum(1, 2);
console.log(res); // => 3
// 私有作用域内的变量全局无法直接访问
console.log(age);// => Uncaught ReferenceError: age is not defined
// 全局变量name不会被污染
console.log(name)// => window
二、为什么引入jQuery.js后,能直接以$.xxx或$().xxx的形式调用,而不是定义一个命名空间接收返回值?
先思考这样一个问题:为什么document.getElementById()中的document不需要定义就能直接使用?
如果不知道答案:浏览器执行代码时,是基于window这个对象的,document的实质是全局执行环境window的一个属性,相当于window.document。
jQuery正是使用了这一点。在「jQuery.js」的尾部有这么一行代码:window.jQuery = window.$ = jQuery; --> 将 jQuery/$挂载到window下,如此就可以直接使用$,那么$.xxx形式的实质就是:对象的xxx属性,$其实就是一个普通的对象。问题又来了,$()等于是对象后加(),但对象是不允许加()的,那$()是什么?
$().xxx/jQuery()
js中,变量名后跟小括号是函数执行,所以$不仅是普通对象,也是函数,而可以.xxx的不止是对象,$()返回了什么是关键。我们在使用jQuery的过程中知道$()返回的是jQuery的实例,但是要通过构造函数创建实例不应该先new一个jQuery()么?看一段代码
jQuery = function( selector, context ) {
return new jQuery.prototype.init( selector, context );
};
好家伙,一旦调用$(),jQuery 自己就new了一个jQuery.prototype.init的实例并返回,根本不用别人来new,只管拿实例来用就行,我只能说:jQuery牛逼。
不过问题也来了,实际使用中,都是$().xxx()的形式,实例调用的都是jQuery构造函数原型上的方法,但这里new的并不是jQuery的实例,那么jQuery.prototype上的方法,非jQuery实例是不能调用的,那凭什么还可以$().xxx()?再看一段代码
jQuery.fn = jQuery.prototype = {//一堆代码...}
init.prototype = jQuery.fn;
以上代码回答了该问题:原型重定向-->面向对象编程的思想,jQuery实现的另一个核心技术就是原型和原型链,而核心思想就是面向对象
基于闭包、原型、原型链,jQuery就实现了最初的“模块化”。而这些技术和思想,如今的框架也依旧在使用。