阅读 371

前端基础技术总结

前端技术总结

HTML相关问题

1.如何理解 DOCTYPE?

声明位于文档中的最前面的位置,处于标签之前。此标签可告知浏览器文档使用哪种HTML或XHTML规范。

该标签可声明三种DTD类型,分别表示严格版本、过渡版本以及基于框架的HTML版本。

<!DOCTYPE>的用法:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
解析:在上面的声明中,声明了文档的根元素是 html,它在公共标识符被定义为 "-//W3C//DTD XHTML 1.0 Strict//EN" 的 DTD 中进行了定义。浏览器将明白如何寻找匹配此公共标识符的 DTD。如果找不到,浏览器将使用公共标识符后面的 URL 作为寻找 DTD 的位置。
-:表示组织名称未注册。Internet 工程任务组(IETF)和万维网协会(W3C)并非注册的 ISO 组织。
+为默认,表示组织名称已注册。
DTD:   指定公开文本类,即所引用的对象类型。 默认为DTD。
HTML:   指定公开文本描述,即对所引用的公开文本的唯一描述性名称。后面可附带版本号。默认为HTML。
URL:  指定所引用对象的位置。
Strict:排除所有 W3C 专家希望逐步淘汰的代表性属性和元素。
复制代码

简单的来说是为了统一规则,共同使用了标准通用标记语言(SGML)的一种约定,来告诉浏览器使用的是哪一种编程规范(DTD)。HTML5 不基于 SGML,所以不需要引用 DTD。HTML5提供的<!DOCTYPE html>是标准模式,向后兼容的, 等同于开启了标准模式,并且向后兼容。

2.HTML语义化

语义化的含义就是用正确的标签做正确的事情,HTML语义化就是让页面的内容结构化,便于对浏览器、搜索引擎解析;在没有样式CCS情况下也以一种文档格式显示,并且是容易阅读的。搜索引擎的爬虫依赖于标记来确定上下文和各个关键字的权重,利于 SEO。使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。

HTML5添加了很多新的语法特征其中包括<video><audio><canvas>元素,同时集成了SVG内容。这些元素是为了更容易的在网页中添加和处理多媒体和图片内容而添加的。其它新的元素如<section><article><header>、和<nav>则是为了丰富文档的数据内容。新的属性的添加也是为了同样的目的。

3.HTML5

HTML5除了更新了很多新的语义化标签也有很多新标签和特性: W3school里边有详细的介绍。可以大致的了解一下。重点关注<video><audio><canvas>localStoragesessionStorage

4.BOM

BOM是指浏览器对象模型,是用于描述这种对象与对象之间层次关系的模型,浏览器对象模型提供了独立的内容的、可与浏览器窗口进行互动的对象结构。 详情参考

5.DOM

DOM全称DocumentObjectModel(文档对象模型),是为HTML和XML提供的API的标准。 本文说的DOM默认为HTMl DOM。

按照不同的类型来分,DOM分为:文档节点、元素节点、属性节点、文本节点、注释节点。 DOM的基本功能为:

  • 查询某个元素
  • 查询某个元素的祖先、兄弟以及后代元素
  • 获取、修改元素属性
  • 获取、修改元素的内容
  • 创建插入和删除元素

CSS相关问题

CSS是前端三大件之一,一个页面是否美观CSS起到了决定性的作用。

1 CSS的常用选择器:

通配符*{color: red;}

元素选择器div{color : red}

类选择器.high{color: red;}

id选择器#test{color: red;}

并联选择器div, .high{color: red;}

复合选择器div.high{background-color: green}

后代选择器div p {background-color: blue;}

子元素选择器h1 > strong {color:red;}

相邻兄弟选择器h1 + p {margin-top:50px;}

属性选择器a[href] {color:red;}

动态伪类选择器:详情请看大漠老师的伪类选择器

.demo a:link {color:gray;}/*链接没有被访问时前景色为灰色*/
.demo a:visited{color:yellow;}/*链接被访问过后前景色为黄色*/
.demo a:hover{color:green;}/*鼠标悬浮在链接上时前景色为绿色*/
.demo a:active{color:blue;}/*鼠标点中激活链接那一下前景色为蓝色*/
复制代码

2 CSS的权重问题。

通俗的讲:从0开始,一个行内样式+1000,一个id+100,一个属性选择器/class或者伪类+10,一个元素名,或者伪元素+1。

可以参考大漠老师的这篇文章你应该知道的一些事情——CSS权重

3 CSS盒子模型

CSS盒子模型是经典面试题之一,回答这个问题首先要了解什么是盒子模型:简单地说每个html标签都是一个方块,然后这个方块又包着几个小方块。分别是:margin、border、padding、content。它们的关系是margin包着border包着padding包着content。就像盒子一层一层地包着一样,这就是我们所说的盒模型。

CSS盒子模型包括w3c标准盒子模型和怪异盒子模型又叫IE盒子模型,他们的差别在于:在w3c和模型中,设置的width/height是content的宽度/高度,在怪异模式中width/height设置的是content+padding+border宽度/高度。

4 px em rem 的区别

  • px绝对长度单位,默认浏览器字体都是16px
  • em相对长度单位,em指字体高度默认为16px,未经调整的浏览器都符合1em=16px。为了简化换算,通常在css中声名font-size=62.5%,这样1rem=10px。因为em的值不固定会继承父级元素的字体大小。如果一个设置了font-size:1.2rem的元素在另一个设置了font-size:1.2rem内,而这个元素又在另一个设置了font-size:1.2rem的元素内,那么最后的计算结果是1.2X1.2X1.2=1.728em。
  • rem是css3的新特性(root em,根em)rem可谓集相对定位和绝对定位于一身,由于它是相对于根元素所以没有这样的问题。

5 不定高度div垂直居中

  • 用一个伪元素和inline-block/vertical-align
  • css3 transform: translate(-50%,-50%);
  • flex布局
  • grid网格布局
  • display:table-cell;
div{display:table-cell; width:1em; height:1em; border:1px solid #beceeb; font-size:144px; text-align:center; vertical-align:middle;} 
div img{vertical-align:middle;}
复制代码

HTTP

JavaScript

1 深拷贝,浅拷贝

原来一直不是很清楚深拷贝和浅拷贝,在掘金上看了一篇文章js 深拷贝 vs 浅拷贝以后豁然开朗。弄明白这两个概念的时候需要了解什么是内存管理,任何一门计算机语言都会有内存管理机制,同样的js也不例外。js的内存管理机制是:JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。js内存空间分为栈(stack)堆(heap)。其中存放变量,存放复杂对象。

js的基础数据类型

这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配内存空间。我们可以直接操作保存内存空间的值,因此基础数据类型都是按值访问,数据在栈内存中的存储与使用方法类似于数据结构中的堆栈数据结构,遵循后进先出的原则。(基础数据类型:Number、String、Null、Undefined、Boolean、symbol)。

js的引用类型(object)

他们值的大小都不是固定的。引用数据类型的值是保存在堆内存中的对象,变量实际上是存放在栈内存的指针,这个指针指向堆内存中的地址。

var a1 = 0; // 栈 
var a2 = 'this is string'; // 栈
var a3 = null; // 栈 
var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中
复制代码

值传递:

很多人认为赋值就是浅拷贝,这是错误的认识;在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再将值赋值到新的栈中。这就是值传递。

var a = 10;
var b = a;

a ++ ;
console.log(a); // 11
console.log(b); // 10
复制代码

址传递:

引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量。

    var obj1 = {
        'name' : 'zhangsan',
        'age' :  '18',
        'language' : [1,[2,3],[4,5]],
    };

    var obj2 = obj1;


    var obj3 = shallowCopy(obj1);
    function shallowCopy(src) {
        var dst = {};
        for (var prop in src) {
            if (src.hasOwnProperty(prop)) {
                dst[prop] = src[prop];
            }
        }
        return dst;
    }

    obj2.name = "lisi";
    obj3.age = "20";

    obj2.language[1] = ["二","三"];
    obj3.language[2] = ["四","五"];

    console.log(obj1);  
    //obj1 = {
    //    'name' : 'lisi',
    //    'age' :  '18',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

    console.log(obj2);
    //obj2 = {
    //    'name' : 'lisi',
    //    'age' :  '18',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

    console.log(obj3);
    //obj3 = {
    //    'name' : 'zhangsan',
    //    'age' :  '20',
    //    'language' : [1,["二","三"],["四","五"]],
    //};
复制代码

先定义个一个原始的对象 obj1,然后使用赋值得到第二个对象 obj2,然后通过浅拷贝,将 obj1 里面的属性都赋值到 obj3 中。也就是说:

obj1:原始数据

obj2:赋值操作得到 obj3:浅拷贝得到

然后我们改变 obj2 的 name 属性和 obj3 的 name 属性,可以看到,改变赋值得到的对象 obj2 同时也会改变原始值 obj1,而改变浅拷贝得到的的 obj3 则不会改变原始对象 obj1。这就可以说明赋值得到的对象 obj2 只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的 obj3 则是重新创建了新对象。 然而,我们接下来来看一下改变引用类型会是什么情况呢,我又改变了赋值得到的对象 obj2 和浅拷贝得到的 obj3 中的 language 属性的第二个值和第三个值(language 是一个数组,也就是引用类型)。结果见输出,可以看出来,无论是修改赋值得到的对象 obj2 和浅拷贝得到的 obj3 都会改变原始数据。 这是因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。所以就会出现改变浅拷贝得到的 obj3 中的引用类型时,会使原始数据得到改变。

深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,

浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象

2 如何实现浅拷贝

  • ES6的Object.assign Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target。用法: Object.assign(target, source1, source2); 所以 copyObj = Object.assign({}, obj); 这段代码将会把obj中的一级属性都拷贝到 {}中,然后将其返回赋给copyObj。 对于Object.assign()而言, 如果对象的属性值为简单类型(string, number),通过Object.assign({},srcObj);得到的新对象为‘深拷贝’;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。这是Object.assign()特别值得注意的地方。
  • ES6扩展运算符:{ ...obj }
  • 使用jq中的$.extend( {}, ...)
  • 第三方lodash方法

3 如何实现深拷贝

  • 使用jq中的$.extend(true, {}, ...)

  • 使用lodash中的_.cloneDeepWith(value, [customizer])

  • JSON对象的parse和stringify

//例1
var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target";  //改变target的name属性
console.log(source.name); //source 
console.log(target.name); //target
target.child.name = "target child"; //改变target的child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
//例2
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
//例3
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
console.log(target.child); //Object {}
复制代码
  • 第三方lodash方法

4 跨域问题

跨域是指一个域下的文档或者脚本试图去请求另一个域下的资源行为。 跨域问题是由于浏览器的同源策略造成的,同源策略分为DOM同源策略和XmlHttpRequest同源策略。其中:

1.DOM同源策略是指禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名下的iframe是限制互相访问的。

2.XMLHttpRequest同源策略是指禁使用XHR对象向不同源的服务器地址发起HTTP请求。

只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。

跨域一览表

如何解决跨域问题?

1.跨域资源共享(CORS)

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源市,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

服务器端对于CORS的支持,主要通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

2.通过jsonp跨域

jsonp能够跨域的原理是用了<script>标签来实现跨域请求的,除了<script>可以跨域的表情还包括<link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链也属于跨域。jsonp由回调函数和数据两部分组成,回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数的json数据。 优点:简单兼容性比较好,可用于解决主流浏览器的跨域访问问题。 缺点:仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

其他几种实现跨域方法

3.document.domain + iframe跨域 4.location.hash + iframe 5.window.name + iframe跨域 6.postMessage跨域 7.nginx代理跨域 8.nodejs中间件代理跨域 9.WebSocket协议跨域

5 闭包

一句话解释闭包:闭包就是能够读取其他函数内部变量的函数。在下面的代码中f2就是闭包。

function f1(){
var n=999;
function f2(){
  alert(n); 
    }
    return f2;
}
var result=f1();
result(); // 999
复制代码

闭包的主要有两大用处,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

6 异步编程

由于JavaScript的语言执行环境是“单线程”,所以如果有一个任务比较长就会造成阻塞,为了解决任务阻塞这个问题,JavaScript将任务的模式分为:同步和异步。 同步模式就是后一个任务等待前一个任务结束后再执行,程序的响应是与任务的排列顺序是一致的、同步的。异步模式完全不同,每一个任务有一个或者多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的顺序与任务的执行殊勋是不一致的、异步的。

异步编程的4种方法:

1.回调函数

回调函数是异步编程的基本方法。

假设有两个函数f1和f2,后者等待前者的执行结果。如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。

function f1(callback){
  setTimeout(function () {
    // f1的任务代码
    callback();
  }, 1000);
 }
复制代码

采用这样的方式,把同步操作改成了异步操作,f1不会阻塞程序运行,相当于先执行程序的主要逻辑,讲耗时的操作推迟执行。 后调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱,而且每个任务只能指定一个回调函数。

2.事件监听

另一种思路就是采用事件驱动模式。任务执行不取决于代码的顺序,而取决与某个事件是否发生。例如:为f1绑定一个事件。

f1.on('done', f2);
复制代码

这样的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。缺点是整个程序都要变成事件驱动,运行流程会变得不清晰。

3.发布/订阅

我们假设纯在一个信号发射函数,当某个任务执行完毕,就像信号中心发布一个信号,其他任务可以向信号中心订阅这个信号,从而知道什么时候自己可以开始执行。这就叫做发布/订阅模式,又称观察者模式。

这个模式有多种实现方式,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。

首先,f2像信号中心的jQuery订阅done信号。

jQuery.subscribe("done", f2);
复制代码

然后f1进行如下改写:

function f1(){

    setTimeout(function () {
    
    //f1的任务代码
    
     jQuery.publish("done");
    
    }, 1000);

}
复制代码

jQuery.publish("done")的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。 此外,f2完成后,也可以取消订阅。

jQuery.unsubscribe("done", f2);
复制代码

这种方法的性质跟事件监听类似,但是明显优于后者,因为我们可以通过查看消息中心,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

4.Promise对象

Promise对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。 简单的说,它的思想就是每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:

f1().then(f2);
复制代码

f1要进行如下改写:

function f1(){

var dfd = $.Deferred();

  setTimeout(function () {

    // f1的任务代码

    dfd.resolve();

    }, 500);

    return dfd.promise;
}
复制代码

这样写的优点在于,回调函数就变成了链式写法,程序的流程可以看得很清楚,还有其他的一些很强大的方法可以使用,Promise其他方法。而且,它还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该函数就会立即执行。所以不用担心是否错过了摸个事件或信号。这种写法的缺点是编写和理解都相对比较困难。

1.Promise对象有三种状态,他们分别是:

  • pending:等待中或进行中,表示还没得到结果
  • resolved:已经完成,表示得到了我们想要的结果,可以继续往下执行
  • rejected:也表示得到结果,但是由于结果并非我们想要的,因此拒绝执行(报错)

这三种状态不受外界影响,而且状态只能从pending改变为resolved或rejected,并且不可逆。在Promise对象的构造函数中,resolved和rejected就是用来处理Promise的状态变化。

2.new Promise()里的函数是立刻执行的

<script>
new Promise(function(resolve,reject){
    $.ajax({
        type:'post',
        dataType: 'jsonp',
        url:'http://api.money.126.net/data/feed/0000001,1399001',
        data:{
        },
        success : function(res){
            console.log(res) ;
            resolve(res) ;
        },
        error:function(res){
            reject(res) ;
        }
    })
});
</script>
复制代码

3.Promise.all()与Promise.race()的用法,更多方法ES6入门

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

<script>
var p1 = function(){
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, 'P1');
    });
} ;
var p2 = function(){
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, 1000, 'P2');
    });
} ;
// 同时执行p1和p2,并在它们都完成后执行then
var start = function(){
    Promise.all([p1(), p2()]).then(function (results) {
        console.log(results); // 获得一个Array: ['P1', 'P2']
    }).catch(function(reason){
        // ...
    });;
}
</script>
复制代码
<script>
var p1 = function(){
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, 'P1');
    });
} ;
var p2 = function(){
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, 1000, 'P2');
    });
} ;
var start = function(){
    Promise.race([p1(), p2()]).then(function (results) {
        console.log(results); // 'P1'
    }).catch(function(reason){
        // ...
    });;
}
</script>
复制代码

5 Generator函数的概念

Promise 的最大问题就是代码冗余,原来的任务被Promise保证一下,不管什么操作,一眼望去都是then,原来的语义变得很不清楚,Generator函数刚好可以解决这样的问题,它可以多个线程互相协作,完成异步任务。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
复制代码

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。

然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。 更多内容产考阮一峰老师的ES6入门 Generator 函数的语法

6 async await

async一句话,它就是 Generator 函数的语法糖。async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。和Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等触发的异步操作完成,再接着执行函数后面的语句。

async function getStockPriceByName(name) {
  var symbol = await getStockSymbol(name);
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result){
  console.log(result);
});
复制代码

Tips:await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。 await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。 如果将 forEach 方法的参数改成 async 函数,也有问题。正确的写法是采用for循环。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}
复制代码

如果确实希望多个请求并发执行,可以使用 Promise.all 方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}
复制代码

7 get post的区别

get和post的区别一直以来都是一个老生常谈的问题,具体的区别为:

“1. GET使用URL或Cookie传参,而POST将数据放在BODY中”,这个是因为HTTP协议用法的约定。并非它们的本身区别。

“2. GET方式提交的数据有长度限制,则POST的数据则可以非常大”,这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。也不是GET和POST本身的区别。

“3. POST比GET安全,因为数据在地址栏上不可见”,这个说法没毛病,但依然不是GET和POST本身的区别。

GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是。这个是它们本质区别,上面的只是在使用上的区别。 理解幂等性

什么是幂等性?幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。

8 原型与原型链

原型(prototype):在JavaScript中,每当定义一个对象(函数)的时候,对象中都会包含一些预定义的属性。其中函数对象的一个属性的就是原型对象prototype。普通对象没有prototype,但有proto属性。

为什么只有函数才有prototype?因为当创建函数时,js会为这个函数自动添加prototype属性,值是一个有constructor属性的对象,不是空对象。而一旦你把这个函数当作构造函数(constructor)调用即通过new关键字调用,那么js就会帮你创建改构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向构造函数的prototype来实现这种继承)。

js正是通过__proto__和prototype的合作实现了原型链,以及对象的继承。

详解原型链中的prototype和 proto

原型链与继承的区别

原型链:JavaScript对象有一个执行一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及对象的原型的原型,依次向上搜寻,直到找到一个名字匹配的属性或到达原型的末尾。

9 Js实现继承的几种方法

首先定义一个父类

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};
复制代码

1.原型链继承 核心:将父类的实例作为子类的原型

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

//&emsp;Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true
复制代码

优点:

  • 1.非常简单纯粹的继承关系,实例是子类的实例,也是父类的实例

  • 2.父类新增的原型方法或原型属性,子类都能访问到

缺点:

  • 1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

  • 2.创建子类实例时,无法向父类构造函数传参

  • 3.无法实现多继承

  • 4.来自原型对象的所有属性被所有实例共享

2.构造继承

原理:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
复制代码

优点:

  • 1.解决了1中,子类实例共享父类引用属性的问题

  • 2.创建子类实例时,可以向父类传递参数

  • 3.可以实现多继承(call多个父类对象)

缺点:

  • 1.实例并不是父类的实例,只是子类的实例

  • 2.只能继承父类的实例属性和方法,不能继承原型属性/方法

  • 3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3.实例继承 原理:为父类实例添加新特性,作为子类实例返回

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
复制代码

优点:

  • 1.不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果 缺点:
  • 1.实例是父类的实例,不是子类的实例
  • 2.不支持多继承

4.拷贝继承

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
复制代码

优点:

  • 1.支持多继承 缺点:

  • 1.效率较低,内存占用高(因为要拷贝父类的属性)

  • 2.无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

5.组合继承 原理:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
复制代码

优点:

  • 1.弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 既是子类的实例,也是父类的实例

  • 2.不存在引用属性共享问题

  • 3.可传参

  • 4.函数可复用 缺点:

  • 1.调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

6.寄生组合继承 原理:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
复制代码

10 Arry对象常用方法

  • concat()链接两个或多个数组;不改变原数组;返回被链接数组的一个副本
  • join()把数组元素放到一个字符串;不改变原数组;返回字符串
  • slice()从已有的数组中返回选定的元素;不改变原数组;返回一个新的数组
  • toString()把数组转为字符串;不改变原数组;返回一个新的数组
  • pop()删除数组最后一个元素,如果数组为空,则不改变数组,返回undefined;改变原有数组;返回被删除的元素
  • push()向数组末尾添加一个或多个元素;改变原数组;返回新数组的长度
  • reverse()颠倒数组中元素的顺序;改变原数组;返回该数组
  • shift()把数组的第一个元素删除,若为空数组,不进行操作,返回undefined;改变原数组;返回第一个元素的值
  • sort()对数组进行排序;改变数组;返回新数组
  • splice()从数组中添加/删除项目;改变数组;返回新数组
  • unshift()向数组的开头添加一个或多个个元素;改变原数组;返回新数组的长度

前端框架

1 前端框架----vue

1 如何理解MVVM

MVVM是Model-View-ViewModel的缩写。

  • Model 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。

  • View 代表UI 组件,它负责将数据模型转化成UI 展现出来。

  • ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。

在MVVM框架下View和Model没有直接的联系,而是通过ViewModel进行交互的,Model和ViewModel的交互是双向的,因此View数据的变化会同步在Model中,而Model 数据的变化也会立即反应到View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

2 vue数据绑定的原理—数据响应原理

vue采用数据劫持组合发布-订阅模式的方式,通过Object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发响应的监听回调。这也是vue不支持IE8以及以更低的浏览器的原因。Vue双向数据绑定将MVVM做为数据绑定入口,整合Observer,Compile和Watcher三者,通过Observer来监听model层的数据变化,然后通过Compile来解析编译模板指令(vue中是用来解析 {{}})最终利用Watcher搭建Observer,Compile之间的桥梁达到数据变化-->视图更新,视图交互-->数据model更新双向绑定效果。

3 vue如何实现对数组的变化监听的

由于数组只要不是重新赋值一个新的数组对象,任何对数组内部的修改都不会触发setter方法的执行。所以vue单独的重新修改监听了 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 7 种方法。

4 vue生命周期、父子传参

vue生命周期、父子传参知识点总结

  • beforeCreate(创建前) 在数据观测和初始化事件还未开始

  • created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来

  • beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。

  • mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。

  • beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。

  • updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  • beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。

  • destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

1.什么是vue生命周期?

答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

2.vue生命周期的作用是什么?

答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

3.vue生命周期总共有几个阶段?

答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

4.第一次页面加载会触发哪几个钩子?

答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。

5.DOM 渲染在 哪个周期中就已经完成?

答:DOM 渲染在 mounted 中就已经完成了。

5 为什么vue实例中的data属性要用函数返回

简单的说是由于js的堆和栈决定的,在js中基本数据类型存放在栈中。当vue初始化一个实例时会初始化一个date对象,如果这个实例在很多地方调用就会照成某个实例改变了状态后影响其他实例。为了解决这个问题vue需要返回一个函数从而返回一个新的数据对象。

当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

6 vue路由

vue路由问题官网介绍的非常详细,点击就可查看 vue路由

vue路由的实现原理:

  • hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用 window.location.hash 读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。

  • history模式:history采用HTML5的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。

7.Vue与Angular以及React的区别?

1、与AngularJS的区别

相同点:都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。

不同点:AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

2、与React的区别

相同点:React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。

不同点:React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

8.vuex是什么?怎么使用?哪种功能场景使用它?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

在main.js引入store,注入。新建了一个目录store就可以使用vuex了。

用到场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车

  • state Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。

  • mutations mutations定义的方法动态修改Vuex 的 store 中的状态或数据。

  • getters 类似vue的计算属性,主要用来过滤一些数据。

  • action actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

const store = new Vuex.Store({ //store实例
      state: {
         count: 0
             },
      mutations: {                
         increment (state) {
          state.count++
         }
          },
      actions: { 
         increment (context) {
          context.commit('increment')
   }
 }
})
复制代码
  • modules 项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
 }
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
 }

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
})
复制代码
关注下面的标签,发现更多相似文章
评论