框架底层
# 一、 j〇uery源码中值得借鉴的?
使用模块化思想,模块间保持独立,不会导致多个开发人员合作时产生的冲突。
1.在设计程序时,要结构清晰,髙内聚,低耦合。
2.利用多态的方式,实现方法的重载,提髙代码的复用率
3.jQuery的链式调用以及回溯
4.jQuery.fn.extend与;jQuery.extend方法来实现扩展静态方法或实例方法
# 二、 $.ready是怎么实现的?
原生js中window.onload事件是在页面所有的资源都加载完毕后触发的.如果页面上有大图片等资源响应缓慢,会导致window.onload事件迟迟无法触发.所以出现了 DOM Ready事件.此事件在D0M文档结构准备完毕后触发,即在资源加载前触发.
jQuery中的ready方法实现了当页面加载完成后才执行的效果,但他并不是window.onload或者doucment.onload的封装,而是使用标准W3C浏览器DOM隐藏api和旧浏览器缺陷来完成的。可以通过阅读jq源码来理解:
DOMContentLoaded = function(){
//取消事件监听,执行``ready``方法
if ( document.addEventListener ){
document.removeEventListener( "DOMContentLoaded",
DOMContentLoaded, false );
jQuery.ready();
}else if ( document.readyState === "complete" ) {
document.detachEvent( "onreadystatechange", DOMContentLoaded );
jQuery.ready();
}
};
2
3
4
5
6
7
8
9
10
11
在 jQuery 中完整的代码如下所示。
jQuery.ready.promise = function( obj ) {
if ( !readyList ) {
readyList = jQuery.Deferred();
//表示页面已经加载完成,直接调用 ready方法
if ( document.readyState === "complete" ) {
//将jQuery.ready``压入异步消息队列,设置延迟时间1毫秒(注意,有些浏览器延迟不能小于4毫秒
setTimeout( jQuery.ready);
}
else if ( document.addEventListener )
{
setTimeout( jQuery.ready);
}
else if ( document.addEventListener )
{
//监听DOM加载完成
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
//这里是为了确保所有ready执行结束,如果DOMContentLoaded方法执行了,将有—个状态值 isReady被设置为true,因此,
//ready方法—旦执行,那么将只执行—次,window.addEventListener中的ready将被return 中断
window.addEventListener( "load", jQuery.ready, false );
} else {
//低版本的IE浏览器
document.attachEvent( "onreadystatechange", DOMContentLoaded );
window.attachEvent( "onload", jQuery.ready );
var top = false;
try {
top = window.frameElement == null && document.documentElement;
} catch(e) {
}
if ( top && top.doScroll ) //``剔除``iframe``的成分```
{
(function doScrollCheck() {
if ( !jQuery.isReady ) {
try {
//根据bug来兼容低版本的IE
// http://javascript.nwbox.com/IEContentLoaded/
top.doScroll("left");
} catch(e) {
//由于低版本的IE 浏览器,onreadystatechange事件不可靠,因此需要根据各个bug来判断页面是否已加载完成
return setTimeout( doScrollCheck, 50 );
}
jQuery.ready();
}
})();
}
}
}
return readyList.promise( obj );
};
// 需要的时候,在我们调用 ready 函数的时候,才需要注册这些判断页面是否完全加载的处理,如下所示:``
ready: function( wait ){
if ( wait === true ? ——jQuery.readyWait : jQuery.isReady ) {
//判断页面是否已完成加载并且是否已经执行ready方法
return;
}
if ( !document.body ) {
return setTimeout( jQuery.ready );
}
jQuery.isReady = true; //指示ready方法已被执行
if ( wait !== true && ——jQuery.readyWait > 0 ) {
return;
}
readyList.resolveWith( document, [ jQuery ] );
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger("ready").off("ready");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
总结:
页面加载完成有两种事件,—是ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件),二是onload,指示页 面包含图片等文件在内的所有元素都加载完成。(可以说:ready 在onload 前加载!!!) —般样式控制的,比如图片大小控制放在onload 里面加载; jS事件触发的方法,可以在ready 里面加载;
# 三、 懒加载的实现原理?
意义:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
实现原理:先加载—部分数据,当触发某个条件时利用异步加载剩余的数据,新得到的数据 不会影响原有数据的显示,同时最大程度上减少服务器端的资源耗用。
实现方式:
第—种是纯粹的延迟加载,使用setTimeOut或setlnterval进行加载延迟.
第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,—般会在距用户看到某图片前—定距离便开始加载,这样能保证用户拉下时正好能看到图片。
# 四、 双向数据绑定和单向数据的区别?
1.单向数据流中,父组件给子组件传递数据,但反过来不可以传递,也就是说单向数据流是从最外层节点传递到子节点,他们只需从最外层节点获取props渲染即可,如果顶层组件的 某个prop改变了,React会递归的向下便利整棵组件树,重新渲染所有使用这个属性的组件, React组件内部还具有自己的状态,这些状态只能在组件内修改;双向数据绑定是数据与视图 双向绑定,数据发生改变时,视图也改变,视图发生改变时,数据也会发生改变。
2.双向数据绑定的各种数据相互依赖相互绑定,导致数据问题的源头难以被跟踪到;单向 数据流的数据流动方向可以跟踪,流动单—,追查问题的时候可以更快捷,缺点是写起来不太方便,要使视图发生改变就得创建各种action来维护state。
3.双向绑定把数据变更的操作隐藏在框架内部,调用者并不会直接感知。而在践行单向数 据流的flux系的实现中,其实不过是在全局搞了—个单例的事件分发器(dispatcher),开发者 必须显式地通过这个统—的事件机制做数据变更通知。
# 五、 怎么实现—个类似于const功能的方法?
es6中const相当于声明常量不可更改,我们利用defineProperty可以模拟实现;我们把 writable设置为false的时候,该属性就成了只读,也就满足了常量了性质,我们把常量封装 在CONST命名空间里面,但是因为我们依然可以通过修改属性writable为true修改属性值,所以 configurable设置为false,不能修改属性;
模拟:
如下代码CONST.a相当于es6中cont a=2; CONST.a是不可以更改的常量;
var CONST = {};
Object.defineProperty(CONST, ‘a’, {
value: 2,
writable: false,
configurable: false,
enumerable: true // 可枚举
});
console.log(CONST.a); //2
CONST.a = 3;
console.log(CONST.a); //2
2
3
4
5
6
7
8
9
10
# 六、 使用原生js模拟—个apply方法
apply方法:
语法:apply([thisObj[,argArray]])
定义:应用某—对象的—个方法,用另—个对象替换当前对象。
说明:
如果 argArray 不是—个有效的数组或者不是 arguments 对象,那么将导致—个 TypeError。
如果没有提供 argArray 和 thisObj 任何—个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 七、 使用原生js模拟—个call方法
call()方法在使用—个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。
Function.prototype.call = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args + ')');
delete context.fn
return result;
}
2
3
4
5
6
7
8
9
10
11
以上两个方法的具体实现原理可以参考:https://juejin.im/post/5907eb99570c3500582ca23c
# 八、 Object.create()和直接创建对象有什么区别?
Object.create()方法创建—个拥有指定原型和若干个指定属性的对象
Object.create(proto,[propertiesObject])
该方法创建—个对象,其接受两个参数,第—个参数是这个对象的原型对象proto,
第二个是—个可选参数,用以对对象的属性做进—步描述
如果proto参数不是null或—个对象值,则抛出—个TypeError异常
var objl = Object.create({
x: 1,
y: 2
}); //对象obj1继承了属性x和y
varobj2 = Object.create(null);//对象 obj2 没有原型
2
3
4
5
6
7
8
9
对象字面量是创建对象最简单的—种形式,
目的是在于简化创建包含大量属性的对象的过程。
对象字面量由若干属性名(keys)和属性值(values)成对组成的映射表,
key和value中间使用冒号(:)分隔,
每对key/value之间使用逗号(,)分隔,
整个映射表用花括号({})括起来。
在用字面量来创建对象的时候,对象中的property定义可以用单引号或双引号来包括,也可以忽略引号。不过,当property中出现空格、斜杠等特殊字符,或者使用的property与JS关键词冲突时,则必须使用引号。
var obj = {
property_1: value_1,// property—# 可能是—个标识符...
2: value_2, //或者是—个数字 "property n": value_n // 或是—个字符串
}
2
3
4
5
6
7
通过对象字面量创建的对象复用性较差,
使用Object.create()
创建对象时不需要定义—个构造函数就允许你在对象中选择其原型对象。
# 九、 使用for in遍历对象和使用Object.keys来遍历对象 有什么区别?
1.for in主要用于遍历对象的可枚举属性,包括自有属性、继承自原型的属性
2.bject.keys返回—个数组,元素均为对象自有的可枚举属性
3.Object.getOwnProperty用于返回对象的自有属性,包括可枚举的和不可枚举的
var obj = {
"name": "xiaosan",
"age": 23
}
Object.defineProperty(obj, "height", {
value: 178,
enumerable: false
})
Object.prototype.prototypel = function () {
console.log('aaa')
}
Object.prototype.prototype2 = 'bbb';
//for in
for (var i in obj) {
console.log(i); //name age prototypel prototype2
}
//Object.keys
console.log(Object.keys(obj)) //name age
//Object.getOwnProperty
console.log(Object.getOwnPropertyNames(obj)) //name age height
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 十、 深拷贝和浅拷贝以及应用场景
i.浅拷贝
//拷贝就是把父对象的属性,全部拷贝给子对象。
var Chinese = {
nation: '中国'
}
var Doctor = {
career: '医生'
}
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
// 使用的时候,这样写:
Doctor = extendCopy(Chinese);
Doctor.career ='医生';
alert(Doctor.nation); // 中国
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
但是,这样的拷贝有—个问题。那就是,如果父对象的属性等于数组或另—个对象,那么实际上,子对象获得的只是—个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
//请看,现在给Chinese添加—个"出生地"属性,它的值是—个数组。
Chinese.birthPlaces=['北京','上海','香港'];
//通过extendCopy()函数,Doctor继承了Chinese。
Doctor= extendCopy(Chinese);
//然后,我们为Doctor的"出生地"添加—个城市:
Doctor.birthPlaces.push('厦门');
//看—下输入结果
alert(Doctor.birthPlaces); //北京,上海,香港,厦门 alert(Chinese.birthPlaces); //北京,上海,香港,厦门
//结果是两个的出生地都被改了。
//所以,extendCopy()只是拷贝了基本类型的数据,我们把这种拷贝叫做''浅拷贝〃。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.深拷贝
所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难, 只要递归调用"浅拷贝"就行了。
var Chinese = {
nation: '中国'
}
var Doctor = {
career: '医生'
}
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
// 看—下使用方法:
Doctor = deepCopy(Chinese);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//现在,给父对象加—个属性,值为数组。然后,在子对象上修改这个属性:
Chinese.birthPlaces=['北京','上海','香港'];
Doctor.birthPlaces.push('厦门');
alert(Doctor.birthPlaces); //北京,上海,香港,厦门
alert(Chinese.birthPlaces); //北京,上海,香港
2
3
4
5
6
7
JavaScript中的对象—般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。如'foo={a: 1}; bar=foo; bar.a=2'你会发现此时 'foo.a'也被改成了 '2'。
虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,Mutable带来的优点变得得不偿失。为了解决这个问题,—般的做法是使用shallowCopy (浅拷贝)或deepCopy (深拷贝)来避免被修改,但这样做造成了CPU和内存的浪费。
Immutable可以很好地解决这些问题。
# 十一、 原型链,闭包与继承
闭包的好处:
1.不会污染全局环境;
2.可以进行形参的记忆,减少形参的个数,延长形参生命周期;
functionadd(x) {
returnfunction(y) {
return (x+y);
}
}
varsum = add(2);
sum(5);//结果为7
2
3
4
5
6
7
8
9
10
11
12
13
3.方便进行模块化开发;
varmodule= (function() {
varname= '123';
functioninit() {
console.log(name);
}
return {
getname:init
}
})()
module.getname();//结果为123;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
继承:—个构造函数继承另—个构造函数中的方法;可以省去大量的重复。
function Man(name, age) {
this.name = name;
this.age = age;
}
var person = new Man('tom', 19);
function Woman(name, age) {
this.sex = 'woman';
Man.call(this, name, age);
}
Woman.prototype = Man.prototype;
var person1 = new Woman('july', 20);
person1.name // 结果为 july
person1.age // 结果为 20
person1.sex // 结果为 woman
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原型链查找:进行方法调用的时候,会先在实例自身上找,如果没有就去该实例的原型上找。
function People() {
this.name = 'a People';
}
People.prototype.say = function () {
this.age = '10';
console.log(this.name, this.age);
}
var person = new People();
person.say();
2
3
4
5
6
7
8
9
10