➤基础:
1、7种基础类型
Boolean Null Undefined Number String Symbol(原始类型不包含) Object (原始类型不包含)
2、类型判断的方法
Typeof 引用类型:对象、数组、函数(引用地址的传递)
值类型 typeof undefined //undefined
typeof isNaN //function eg typeof isNaN(1) boolean typeof 'abc' //string typeof 123 //number typeof true //boolean typeof Symbol() //Symbol -------------------------------- 引用类型 typeof {} //object typeof [] //object typeof null //object (除了这个不是引用类型) typeof console.log typeof fn//function
3、instanceof用法
用于判断一个变量是否某个对象的实例(原理是通过判断左操作数的对象的原型链上是否具有右操作数的构造函数的prototype属性)
// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例 function Aoo(){} function Foo(){} Foo.prototype = new Aoo();//JavaScript 原型继承 var foo = new Foo(); console.log(foo instanceof Foo)//true console.log(foo instanceof Aoo)//true
4、js的常见内置对象类
Date、Array、Math、Number、Boolean、String、RegExp、Function、Object…
5、js变量按照存储方式区分为哪些类型,并描述其特点
1)值类型:存在
var a=100;
var b=a;
a=200;
console.log(b);//100 值类型的特点:它每个变量都能存储各自的值,不会相互影响
2)引用类型
var a={age:20};
var b=a;
b.age=21;
console.log(a.age);//21 引用类型的特点:不同变量之间的值,会相互影响
1)存储
基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中
引用类型的值是对象, 保存在堆内存中. 包含引用类型的变量实际上包含的并不是对象本身, 而是一个指向改对象的指针
2)复制
会创建这个值的一个副本
复制的其实是指针, 两个变量最终都指向同一个对象
3)检测类型
确定一个值是哪种基本类型可以用typeof操作符,
而确定一个值是哪种引用类型可以使用instanceof操作符
6、浅拷贝和深拷贝
var person={
name:'cj',
age:22,
address:{
home:'home address',
office:'office address'
},
school:['xiaoxue','daxue']
};
var programer={
language:'javascript'
}
浅拷贝:父子两个对象拷贝的属性成员是一种引用值的拷贝,指向同一个对象,改变一个会影响另外一个
function extend(p,c){
var c=c||{};
for(var prop in p){
c[prop]=p[prop]
}
}
extend(person,programer)
深拷贝:深拷贝之后,父与子没有什么关系了,子改变,父也不会跟着改变 ,用递归实现深拷贝
1)深复制在计算机中开辟了一块内存地址用于存放复制的对象,
2)而浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
function extendDeep(p,c){
var c=c||{};
for(var prop in p){
if(typeof p[prop] === 'object'){
//还要判断p[prop]是数组或者是对象,是数组的话c[prop] 返回出来的是[]
c[prop] = (p[prop].constructor === Array)?[]:{}
extendDeep(p[prop],c[prop])
}else{
c[prop]=p[prop]
}
}
}
extendDeep(person,programer)
programer.school[0]='111111';
console.log(programer.school[0]);//111111
console.log(person.school[0]);//xiaoxue
var obj = {a:{b:10}}; function deepCopy(obj){ if(typeof obj != 'object'){ return obj; } var newobj = {}; for ( var attr in obj) { newobj[attr] = deepCopy(obj[attr]); } return newobj; } var obj2 = deepCopy(obj); obj2.a.b = 20; alert(obj.a.b); //10
利用一些已经有的封装好的库函数实现深拷贝:
①利用jquery中的$.exten()函数可以实现深拷贝
②lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝
7、继承方式
原型链继承、借用构造函数继承、原型+构造函数组合继承、寄生式继承
//原型链继承 function Animal(){ this.age=20 } function Cat(){ this.name='jacy' } //原型链指向Animal,拥有age这个属性 Cat.prototype=new Animal(); //这一步让Cat的对象拥有了Animal对象的属性、方法 var cat=new Cat(); cat.name //jacy cat.age //undefined 没有被定义过的属性 //借用构造函数继承 function Animal(){ this.age=20 } function Cat(){ Animal.call(this) //让Cat的所有对象借用了Animal对象的构造函数 } var cat=new Cat(); cat.age //20 //原型+构造函数组合继承 function Cat(){ this.name='jacy' //构造函数继承 this.run=function(){ console.log(this.name+'在跑步') } } //原型继承 Cat.prototype.sayHello=function(){ console.log('1111') } var cat=new Cat(); console.log(cat.run) //寄生式继承(是工厂模式的变种,只不过是放在其他构造函数中创建对象) //工厂模式创建对象 /* function creat(age,sex){ var obj={} obj.age=age obj.sex=sex return obj } var person1=creat("张三","男") */ //寄生(类似代孕) function Japanese(name,language){ this.name=name this.language=language } //寄生模式 function createChinese(name,language){ var obj={} Japanese.call(obj,name,language) } var zs=createChinese("张三","普通话") console.log(zs.constructor) //Object
call
//函数 function Parent(){ this.name='abc', this.address={home:'home'} } function Child(){ Parent.call(this) this.language='java' } var c=new Child(); var p=new Parent(); c.address.home='1111111'; console.log(p.address.home) //对象 var a = { name: 'A', fn: function () { console.log(this.name) } } a.fn() // this === a a.fn.call({name: 'B'}) // this === {name: 'B'} 改变this,并执行输出 ‘B’ var fn1 = a.fn fn1() // this === window...
8、创建对象的方式
第一种:对象字面量、Object构造函数
第二种:构造函数
第三种:纯构造函数
第四种:空构造函数+原型
第五种:混合构造函数+原型
第六种:动态原型
第七种:寄生构造函数
第八种:Object creat() es5 兼容ie9以上
//对象字面量 var obj={} obj.gender="女" console.log(obj.gender) //获取属性 console.log(obj["gender"]) //记得里面是字符串 //通过obj构造函数创建对象 var obj2=new Object(); //动态原型 function Person(name,work){ this.namename; if(work){ //是否到了法定工作年龄 Person.prototype.working=function(){ console.log("我的工作是:"+work) } } } var p1=new Person("张三") var p2=new Person("李四","程序猿") console.log(p1.working) //undefined console.log(p2.working) //程序猿 //Object creat()用法 var p3=Object.create(p1); //把p1里面的属性和方法都拷贝过来了 console.log(p3.name) //张三 //封装Object creat() function extend(obj){ var result={}; for(var prop in obj){ result[prop]=obj[prop] } //为了让result具有跟obj一样的构造函数,所以让对象的constructor属性指向obj的constructor属性 result.constructor=obj.constructor return result } var p1=extend(p1)
9、检验数组的几种方式
var arr=['1111','2222','44444']
console.log(Array.isArray(arr)) //true
console.log(toString.call([])) //[object Array]
console.log(toString.call(/[0-9]/)) //[object RegExp]
var arr=[]
arr.constructor //Array 不严谨,因为可以读他也可以写他
console.log([] instanceof Array) //true
8、如何准确判断一个对象是数组类型
//instanceof 判断一个变量是否某个对象的实例 var arr=[]; console.log(arr instanceof Array) //返回true //constructor 构造器 console.log([].constructor == Array); console.log({}.constructor == Object); console.log("string".constructor == String); console.log((123).constructor == Number); console.log(true.constructor == Boolean); //判断对象是否数组类型 function isArray(o) { return Object.prototype.toString.call(o) === "[object Array]"; } //判断是否数组类型 var arr = [1,2,3,1]; var arr2 = [{ abac : 1, abc : 2 }]; function isArrayFn(value){ if (typeof Array.isArray === "function") { return Array.isArray(value); }else{ return Object.prototype.toString.call(value) === "[object Array]"; } } alert(isArrayFn(arr));// true alert(isArrayFn(arr2));// true
//闭包 function doSth(){ var len-10 /* 使用闭包的意义: 让step1这个函数只能在doSth里面调用,而且又封装了细节 如果把step1这个函数放在全局作用域中,而且又永远不可能被其他函数调用 那样就没有意义,而且污染了全局作用域,占用内存,除非手动清除设置成null */ function step1(){ console.log(len) //10 } } //闭包函数另外的意义:可以用来实现模块化 var common=(function(){ var name="通用模块"; function initPage(){ console.log(name) } retrun{ initPage2:initPage } })() common.initPage2(); //点击li弹出对应信息 for(var i=0;i<lis.length;i++){ var li=lis[i]; //触发这个函数的时候i已经等于5了,所以都弹出5 li.onclick=(function(index){ //利用闭包函数实现了我们点击某个li标签的时候弹出他的真实索引 return function(){ console.log(index) } })(i) }
10、输出今天的日期,以YYYY-MM-DD的方式,比如2019-09-10
var d=new Date(); var year=d.getFullYear(); //获取年,返回4位的数字 var month=d.getMonth()+1 //月比较特殊,0是1月,11是12月 month=month<10?'0'+month:month var day=d.getDate() day=day<10?'0'+day:day console.log(year+'-'+month+'-'+day)
11、可枚举属性:这个属性能否被for…in查找遍历到 https://www.cnblogs.com/kongxy/p/4618173.html
➤理解题:
1、eval是做什么的?
把字符串解析成js代码并返回结果,使用eval()对json数据结构求值存在风险,因为可能会执
行一些恶意代码,非常耗性能(2次,一次解析成js语句,一次执行)。
eval("2+3");//执行加运算,并返回运算值。 eval("var age=10");//声明一个age变量
作用域
其他作用:可以把JSON字符串转换为JSON对象的
var json="{name:'xiaoming',age:18}"; var jsonObj=eval("("+json+")"); console.log(jsonObj); //Object {name: "xiaoming", age: 18}
2、定时器的执行顺序或机制
setTimeout
或者setInterval
会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。即使把定时器的时间设置为0,还是会先执行当前的一些代码。3、页面编码和被请求的资源编码如果不一致如何处理?
比如:a.html 中嵌入了一个test.js a.html 的编码是gbk或gb2312的。
而引入的js编码为utf-8的 ,那就需要在引入的时候
<script src="http://www.xxx.com/test.js" charset="utf-8"></script>
同理,如果你的页面是utf-8的,引入的js是gbk的,那么就需要加上charset="gbk"
让利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行!
原理:
事件的冒泡;通过父元素监听子元素触发的事件。
DOM的遍历:父元素拥有多个子元素,当一个事件触发,那么就触发了某一类型的元素(拥有相同CLASS)
什么时候要用到事件委托?
很多商品放在一个ul下面的li标签里面,点击添加或删除商品,就可以绑定商品的父元素ul标签,通过事件代理去找到要点击的商品,完成添加删除事件
为什么要使用?
绑定事件太多,浏览器占用内存变大,严重影响性能
Ajax出现,局部刷新盛行,每次加载完,都要重新绑定事件
部分浏览器移除元素时,绑定的事件没有被及时移除,导致内存泄漏,严重影响性能
Ajax中重复绑定,导致代码耦合性过大,影响后期维护
什么时候使用?
只在必须的时候,比如Ajax局部刷新区域
绑定层级比较低的时候,不在body上绑定
绑定次数较少的时候,把多个事件绑定合并到一次事件委托中,由这个事件委托的回调,来进行分发
提高事件委托性能
降低绑定层级
减少绑定次数
//如果这个UL中的LI子元素频繁的添加或删除,我们就需要在每次添加LI的时候为它绑定事件。这就添加了复杂度,并且造成内存开销较大。 (function(){ var oUlItem = document.getElementById('ul-item'); var oLi = oUlItem.getElementsByTagName('li'); for(var i=0, l = oLi.length; i < l; i++){ oLi[i].addEventListener('click',show); }; function show(e){ e = e || window.event; alert(e.target.innerHTML); }; })(); //修改: (function(){ var oUlItem = document.getElementById('ul-item'); oUlItem.addEventListener('click',show); function show(e){ e = e || window.event; var src = e.target; if(src && src.nodeName.toLowerCase() === 'li'){ alert(src.innerHTML); } } })();
7、javascript的事件流模型都有什么?(DOM事件流)
“事件冒泡”:事件开始由最具体的元素接收,然后逐渐向上传播
“事件捕获”:事件由最不具体的节点接收,然后逐渐向下,一直到最具体的元素
“DOM事件流”:三个阶段:事件捕获,目标阶段,事件冒泡
6、如何理解Json
json是一个js对象(描述数据结构有以对象的形式存在,轻量级的数据交换格式)
JSON.stringify({1:10,b:20}) //json格式字符串
JSON.parse('{"a":10,"b":20}') //json格式对象
性能优化: 1.将那些改变样式的操作集合在一次完事,直接改变className或者cssText 2.使用文档碎片DocumentFragment,事先把内容拼接好,最后统一添加到页面中,只引发一次回流(DocumentFragment节点不属于文档树,继承的 parentNode 属性总是 null) 3.不要经常访问会引起浏览器flush队列的属性,非要高频访问的话建议缓存到变量; 4.将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位;
//文档碎片 var frg=document.createDocumentFragment(); for(var i=0;i<arr.length;i++){ var oLi=document.createElement('li'); oLi.innerHTML='<span>'+arr[i]+'</span>'; frg.appendChild(oLi) } oUl.appendChild(frg); frg=null;//手动释放
8、页面编码和被请求的资源编码如果不一致如何处理?
比如:a.html 中嵌入了一个test.js a.html 的编码是gbk或gb2312的。
而引入的js编码为utf-8的 ,那就需要在引入的时候
<script src="http://www.xxx.com/test.js" charset="utf-8"></script>
同理,如果你的页面是utf-8的,引入的js是gbk的,那么就需要加上charset="gbk"
这种写法叫做短路表达式 if(!foo)foo=bar //如果foo存在,值不变,否则把bar赋值给foo 短路表达式:作为“&&”和“||”操作符的操作数表达式,这些表达式在进行求值时,只要最终的结果已经确定是真或假,求值过程便告终止,这称之为短路求值 注意if条件的真假判定,记住以下是false的情况: 空字符、false、undefined、null、0
➤混淆对比题:
1、什么时候用for循环什么时候用forEach?
forEach没有 变量 i 来循环 ,内部自己维护这样的下标
遍历数组的两种方法:
//标准的for循环 var arr = [1,2,3,4]; for(var i=1;i<arr.length;i++){ console.log(typeof i) //Number } var arr = [1,2,3,4]; arr.forEach((item,index)=>{ console.log(typeof index) //Number })
衍生:
for…in 用于遍历数组或者对象的属性
//遍历数组: var arr = ["apple","banana","orange","xiao"]; for( i in arr){ console.log(typeof i); // string console.log("i的值为:",i); // 0 1 2 3 4 console.log("每一项的值为:",arr[i]); // apple banana orange xiao } //遍历对象: var obj = { 'aa': 1,'bb':3,'cc':5}; for( i in obj ){ console.log(typeof i); //string console.log(i); //属性 aa bb cc console.log(obj[i]); //属性值 1 3 5 }
for in 遍历和for循环的区别:https://www.jb51.net/article/127387.htm
2、return break continue的区别
break是跳出一层循环,continue是结束一趟循环 ,return才是结束所有层循环!
如果有多层for循环,break会跳出当前这一层,去执行最外层循环(而不是退出所有层循环);而continue则结束当前次循环(继
续)而去执行下次循环,但本层循环没有结束.(注意一层循环和一次循环的区别:一层循环包含若干(i)次循环)
return退出了所有的循环!
for(var i = 1; i < 10; i++){ if(i == 4){ continue; } console.log(n);//1 2 3 5 6 7 8 9 } for(var i = 1; i < 10; i++){ if(i == 4){ break; } console.log(i);//1 2 3 }
3、onload和ready的区别
1.执行时间 window.onload必须等到页面内包括图片的所有元素加载完毕后才能执行 $(document).read()是DOM结构绘制完毕后就执行,不必等到加载完毕 2.编写个数不同 window.onload不能同时编写多个,如果有多个window.onload方法,只会执行一个 $(document).ready()可以同时编写多个,并且都可以得到执行 3.简化方法 window.onload没有简化写法 $(document).ready(function(){})可以简写成$(function(){})
4、undefined和not defined
undefined是javascript语言中定义的五个原始类中的一个,换句话说,undefined并不是程序报错,而是程序允许的一个值。
not defined是javascript在运行我们的javascript代码时,遇到没有定义就用来运算的变量时爆出来的错误。
5、jQuery中的on和bind绑定事件区别
jQuery中的on和bind绑定事件区别 $(selector).bind(event,data,function,map) $(selector).on(event,childSelector,data,function) .on方法比.bind方法多一个参数'childSelector' 这个childSelector参数的好处是什么? 可以进行事件委托,利用冒泡原理,子元素把事件委托给父元素进行处理 事件委托的好处: 1、万一子元素非常多,给每个子元素都添加一个事件,会影响到性能 2、为动态添加的元素也绑定上指定事件 $('ul li').on('click', function(){console.log('click');})的绑定方式和$('ul li').bind('click', function(){console.log('click');})一样; 我通过js给ul添加了一个li:$('ul').append('<li>js new li<li>');');这个新加的li是不会被绑上click事件的 但是我用$('ul').on('click', 'li', function(){console.log('click');}方式绑定,然后动态添加li:$('ul').append('<li>js new li<li>');这个新生成的li被绑上了click事件
适用于未创建的元素,动态创建元素的时候 可以对新创建的元素进行事件的监听
6、事件绑定和普通事件的区别
传统事件( onclick onmuseover)和W3C标准事件绑定的方式(addEventListener/attachEvent)
两者区别:
1)普通事件同一元素绑定多次相同事件,后面的绑定会覆盖前面的绑定
2)普通事件不支持DOM事件流(事件捕获阶段--->目标元素阶段--->事件冒泡)
element.addEventListener(eventType, fn, false) 第三个参数为false,按照事件冒泡的执行顺序进行。
element.attachEvent('on' + eventType, fn); 只支持冒泡,不支持捕获 ie9以下
//普通 添加事件:只会alert 2 var btn = document.getElementById("hello"); btn.onclick = function(){ alert(1); } btn.onclick = function(){ alert(2); } //事件绑定 添加事件:会先alert 1 再 alert 2 var btn = document.getElementById("hello"); btn.addEventListener("click",function(){ alert(1); },false); btn.addEventListener("click",function(){ alert(2); },false);
7、== 和 ===
宽松相等== 和严格相等===都是来判断两个值是够“相等”,
==允许在相等比较中进行强制类型转换,而===不允许
何时使用===和==?
if(obj.a == null){ //这里相当于obj.a === null ||obj.a ===undefined 简写形式 } 只有这个地方用==,其它地方用===
8、null和undefined
Null是无和空,例如,函数本返回一个结构体,或者一个指针,却返回null,意味没成功或失败。
Undefined错误、不明确、未定义,例如一个变量未声明,或一个函数未声明和定义就使用,就会得到错误信息未定义。
相同点:
在 if判断语句中,值都默认为 false
差异:
null转为数字类型值为0,而undefined转为数字类型为 NaN(Not a Number)
undefined是代表调用一个值而该值却没有赋值,这时候默认则为undefined
null是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
设置为null的变量或者对象会被内存机制回收
undefined会在以下三种情况下产生 1、一个变量定义了却没有被赋值 2、想要获取一个对象上不存在的属性或者方法 3、一个数组中没有被赋值的元素 var arr=[1,2,3] arr[6] //undefinde
var arr = ['a', 'b', 'c', 'd']; for (let a in arr) { console.log(a); // 0 1 2 3 console.log(arr[a]);// a b c d 如果要键值要这样求 } for (let a of arr) { console.log(a); // a b c d }
11、返回上一页刷新和不刷新
返回并刷新 window.location.href=document.referrer; 返回不刷新 window.history.back(-1); 其他: window.history.go(-1)和window.location.go(-1)的区别 虽然都跑到上一页去了,但是: window.history.go(-1) 是返回上一页 window.location.go(-1) 是刷新上一页
window.location.reload(); //①可以重新提交POST数据。 ②刷新 ③处理页面的锚点# //比如说,如果你采用POST方式提交了付款数据,当前页面是付款完成页面,这个时候如果你window.location.reload(),也许会重新POST一次付款…… window.location.href=window.location.href; //①刷新 ②做跳转 ="baidu.com"
十四、call、apply的区别
都是借用别人的方法:通过改变this指向,来执行借用这个方法
两个方法产生的作用是完全一样的,都用来改变当前函数调用的对象,只是调用的参数方式不同。
foo.call(this,arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3)
区别:第二个参数接收方式不一样 call接收一个参数列表 apply接收一个参数数组
var obj1={ say:function(){ alert("hello") } } var obj2={ haveLunch:function(name,age){ console.log(this) //obj1 console.log(name,age) //test.html:21 小明 20 } } //让obj1执行haveLunch方法,借用obj2的方法 obj2.haveLunch.call(obj1,"小明","20") obj2.haveLunch.apply(obj1,["小明","20"])
十五、confrim()和return confirm
区别:return confirm不会再跳转到链接
因为点击取消时返回false 即return false, 相当于event.preventDefault() ,阻止浏览器默认行为
<a href="http://www.baidu.com" target="_blank" onclick="confirm();">百度</a> <a href="http://www.baidu.com" target="_blank" onclick="return confirm();">百度</a>
//阻止form提交 <form action="http://www.baidu.com"> <button type="submit" onclick="confirm();">提交</button> </form> <form action="http://www.baidu.com"> <button type="submit" onclick="return confirm();">提交</button> </form>
➤兼容:
1、addEventListener 支持火狐...,不是以"on"开头,有第三个参数,一般指定false就可以 attachEvent 支持ie ,事件是以"on"开头的
element.addEventListener(eventType, fn, false) false表示冒泡,默认为false
element.attachEvent('on' + eventType, fn); 只支持冒泡,不支持捕获 ie9以下
if (window.addEventListener) { window.addEventListener("load", foo, false); }else if (window.attachEvent) { window.attachEvent("onload", foo); } function foo() { alert("ok"); }
2、IE和标准下游哪些兼容性的写法
ev=ev || window.event (window.event是在ie下) //获取触发事件的对象
document.documentElementclientWidth || document.body.clientWidth (前者兼容IE和FireFox)
var target=ev.srcElement || ev.target (前者兼容ie) //获取事件的源对象
➤框架:
1、zepto jquery 中如何用原型?
//插件扩展 $.fn.getNodeName=function(){ alert(this[0].nodeName) } var $p=$('p'); $p.css('font-size','40px') alert($p.html()) console.log($p) $p.getNodeName() var $div=$('#div') $div.css('color','blue') alert($div.html())
描述一下 jquery 如何使用原型
(function(window){ var jQuery=function(selector){ return new jQuery.fn.init(selector) } jQuery.fn={ css:function(key,value){ alert('css') }, html:function(value){ return 'html' } } var init=jQuery.fn.init=function(selector){ var slice=Array.prototype.slice //把dom变成数组的形式 var dom=slice.call(document.querySelectorAll(selector)) var i,len=dom?dom.length:0 //是不是有dom,没有给它0,有给它长度 for(i=0;i<len;i++){//遍历,把所有的元素都变成实例的属性 this[i]=dom[i] } this.length=len this.selector=selector || '' } init.prototype=jQuery.fn window.$=jQuery })(window)
描述一下 zepto 如何使用原型
(function(window){ //空对象 var zepto={} //构造函数 function Z(dom,selector){ var i,len=dom?dom.length:0 //是不是有dom,没有给它0,有给它长度 for(i=0;i<len;i++){//遍历,把所有的元素都变成实例的属性 this[i]=dom[i] } this.length=len this.selector=selector || '' } zepto.Z=function(dom,selector){ return new Z(dom,selector) } zepto.init=function(selector){ var slice=Array.prototype.slice //把dom变成数组的形式 var dom=slice.call(document.querySelectorAll(selector)) return zepto.Z(dom,selector) } var $=function(selector){ return zepto.init(selector) } window.$=$ $.fn={ css:function(key,value){ alert('css') }, html:function(value){ return '这是一个模拟的html函数' } } Z.prototype=$.fn // zepto.Z.prototype=Z.prototype=$.fn })(window)
再结合自己的项目经验,说一个自己开发的例子
为何要把原型方法放在 $.fn ?
好处:
只有 $ 会暴露在 window 全局变量
将插件扩展统一到 $.fn.xxx 这一个接口,方便使用