前端基础知识整理汇总(上)

web前端开发

共 15112字,需浏览 31分钟

 ·

2021-02-25 21:27



又是一年跳槽季,最近听到最多的消息就是,我们公司又有同事离职了,所以,如果你想在职场上掌握主动权,你就需要比别人更加努力,更加夯实的技能基础,不然你拿什么去跟别人拼?所以,今天我们跟大家分享一些前端基础知识,希望对你有所帮助。
HTML页面的生命周期

HTML页面的生命周期有以下三个重要事件:

  • DOMContentLoaded —— 浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是像是 // 带有src属性的元素不应该在标签之间包含额外的js代码,即使包含,只会下载并执行外部文件,内部代码也会被忽略。

    与嵌入式js代码一样, 在解析外部js文件时,页面的处理会暂时停止。

    改变脚本行为的方法

    1. defer: 立即下载,延迟执行

    加载和渲染后续文档元素的过程将和脚本的加载并行进行(异步),但是脚本的执行会在所有元素解析完成之后。脚本总会按照声明顺序执行。
    在DOMContentLoaded事件之前执行。

    2. async: 异步脚本

    加载和渲染后续文档元素的过程将和脚本的加载与执行并行进行(异步)。但是async 在下载完毕后的执行会阻塞HTML的解析。脚本加载后马上执行,不能保证异步脚本按照他们在页面中出现的顺序执行。
    一定会在load事件之前执行,可能会在DOMContentLoaded事件之前或之后执行。

    区别:

    meta

    META标签是HTML标记HEAD区的一个关键标签,它提供的信息虽然用户不可见,但却是文档的最基本的元信息。除了提供文档字符集、使用语言、作者等网页相关信息外,还可以设置信息给搜索引擎,目的是为了SEO(搜索引擎优化)。

    HTML 元素表示那些不能由其它 HTML 元相关(meta-related)元素((,

    后台代码:

      $callback = $_GET['callback'];//得到回调函数名  $data = array('a','b','c');//要返回的数据  echo $callback.'('.json_encode($data).')';//输出?>

    CORS - 跨域资源共享

    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

    CORS有两种请求,简单请求和非简单请求。只要同时满足以下两大条件,就属于简单请求。

    1. 请求方法是以下三种方法之一:HEAD,GET,POST

    2. HTTP的头信息不超出以下几种字段:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type【只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain】,没有自定义的HTTP头部。

    简单请求

    1. 浏览器:把客户端脚本所在的域填充到Origin header里,向其他域的服务器请求资源。

    2. 服务器:根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header,返回结果。

    3. 浏览器:比较服务器返回的Access-Control-Allow-Origin Header和请求域的Origin。如果当前域已经得到授权,则将结果返回给页面。否则浏览器忽略此次响应。

    4. 网页:收到返回结果或者浏览器的错误提示。

    对于简单的跨域请求,只要服务器设置的Access-Control-Allow-Origin Header和请求来源匹配,浏览器就允许跨域服务器端设置的`Access-Control-Allow-MethodsAccess-Control-Allow-Headers对简单跨域没有作用。

    非简单请求

    1. 浏览器:先向服务器发送一个OPTIONS预检请求,检测服务器端是否支持真实请求进行跨域资源访问,浏览器会在发送OPTIONS请求时会自动添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。

    2. 服务器:响应OPTIONS请求,会在responseHead里添加Access-Control-Allow-Methods head。这其中的method的值是服务器给的默认值,可能不同的服务器添加的值不一样。服务器还会添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。这些取决于服务器对OPTIONS请求具体如何做出响应。如果服务器对OPTIONS响应不合你的要求,你可以手动在服务器配置OPTIONS响应,以应对带预检的跨域请求。在配置服务器OPTIONS的响应时,可以添加Access-Control-Max-Age head告诉浏览器在一定时间内无需再次发送预检请求,但是如果浏览器禁用缓存则无效。

    3. 浏览器:接到OPTIONS的响应,比较真实请求的method是否属于返回的Access-Control-Allow-Methods head的值之一,还有origin, head也会进行比较是否匹配。如果通过,浏览器就继续向服务器发送真实请求, 否则就会报预检错误:请求来源不被options响应允许,请求方法不被options响应允许或请求中有自定义header不被options响应允许。

    4. 服务器:响应真实请求,在响应头中放入Access-Control-Allow-Origin Header、Access-Control-Allow-MethodsAccess-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头,并返回数据。

    5. 浏览器:接受服务器对真实请求的返回结果,返回给网页

    6. 网页:收到返回结果或者浏览器的错误提示。

    Access-Control-Allow-Origin在响应options请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。 Access-Control-Allow-MethodsAccess-Control-Allow-Headers只在响应options请求时有作用。

    携带cookie

    在 CORS 跨域中,浏览器并不会自动发送 Cookie。对于普通跨域请求只需服务端设置,而带cookie跨域请求前后端都需要设置。

    浏览器,对于跨域请求,需要设置withCredentials 属性为 true。服务端的响应中必须携带 Access-Control-Allow-Credentials: true 。

    除了Access-Control-Allow-Credentials之外,跨域发送 Cookie 还要求 Access-Control-Allow-Origin不允许使用通配符。否则浏览器将会抛出The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' 错误。事实上不仅不允许通配符,而且只能指定单一域名。

    计算 Access-Control-Allow-Origin

    既然Access-Control-Allow-Origin只允许单一域名, 服务器可能需要维护一个接受 Cookie 的 Origin 列表, 验证 Origin 请求头字段后直接将其设置为Access-Control-Allow-Origin的值。在 CORS 请求被重定向后 Origin 头字段会被置为 null, 此时可以选择从Referer头字段计算得到Origin

    具体实现

    服务器端的响应头配置
    Access-Control-Allow-Origin 可以设置为* ,表示可以与任意域进行数据共享。
    // 设置服务器接受跨域的域名"Access-Control-Allow-Origin": "http://127.0.0.1:8080",// 设置服务器接受跨域的请求方法'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST',// 设置服务器接受跨域的headers'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type',// 设置服务器不用再次预检请求时间'Access-Control-Max-Age': 10000,// 设置服务器接受跨域发送Cookie'Access-Control-Allow-Credentials': true

    document.domain

    此方案仅限主域相同,子域不同的跨域应用场景。

    实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

    栗子:

    在父页面 http://xxx.com/a.html 中设置document.domain

    在子页面http://xxx.com/b.html 中设置document.domain

    window.postMessage

    window.postMessage是html5的功能,是客户端和客户端直接的数据传递,既可以跨域传递,也可以同域传递。

    postMessage(data, origin)方法接受两个参数:

    • data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。

    • origin:协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

    栗子:
    假如有一个页面,页面中拿到部分用户信息,点击进入另外一个页面,另外的页面默认是取不到用户信息的,你可以通过window.postMessage把部分用户信息传到这个页面中。(需要考虑安全性等方面。)

    发送消息:

    // 弹出一个新窗口var domain = 'http://haorooms.com';var myPopup = window.open(`${domain}/windowPostMessageListener.html`,'myWindow');
    // 发送消息setTimeout(function(){ var message = {name:"站点",sex:"男"}; console.log('传递的数据是 ' + message); myPopup.postMessage(message, domain);}, 1000);

    接收消息:

    // 监听消息反馈window.addEventListener('message', function(event) {  // 判断域名是否正确  if (event.origin !== 'http://haorooms.com') return;  console.log('received response: ', event.data);}, false);

    如下图,接受页面得到数据

    如果是使用iframe,代码应该这样写:

    // 捕获iframevar domain = 'http://haorooms.com';var iframe = document.getElementById('myIFrame').contentWindow;
    // 发送消息setTimeout(function(){ var message = {name:"站点",sex:"男"}; console.log('传递的数据是: ' + message); iframe.postMessage(message, domain); },1000);

    接收数据并反馈信息:

    // 响应事件window.addEventListener('message',function(event) {    if(event.origin !== 'http://haorooms.com') return;    console.log('message received:  ' + event.data, event);    event.source.postMessage(event.origin);}, false);

    几个比较重要的事件属性:
    source – 消息源,消息的发送窗口/iframe。
    origin – 消息源的URI(可能包含协议、域名和端口),用来验证数据源。
    data – 发送方发送给接收方的数据。

    window.name

    原理:
    window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。

    栗子:
    在子页面(b.com/data.html) 设置window.name:

    /* b.com/data.html */

    在父页面(a.com/app.html)中创建一个iframe,把其src指向子页面。在父页面监听iframe的onload事件,获取子页面数据:

    /* a.com/app.html */

    http-proxy-middleware

    http-proxy-middleware用于把请求代理转发到其他服务器的中间件。
    安装:
    npm install http-proxy-middleware --save-dev
    配置如下:

    module.exports = {  devServer: {    contentBase: path.resolve(__dirname, 'dev'),    publicPath: '/',    historyApiFallback: true,    proxy: {      // 请求到 '/device' 下的请求都会被代理到target:http://target.com中      '/device/*': {        target: 'http://target.com',        secure: false, // 接受运行在https上的服务        changeOrigin: true      }    }  }}

    使用如下:

    fetch('/device/space').then(res => {  // 被代理到 http://target.com/device/space  return res.json();});
    // 使用的url 必须以/开始 否则不会代理到指定地址fetch('device/space').then(res => { // http://localhost:8080/device/space 访问本地服务 return res.json();});

    nginx反向代理

    反向代理(Reverse Proxy)方式是指以代理服务器来接受客户端的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。

    反向代理服务器对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理 的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容 原本就是它自己的一样。

    模块化

    AMD/CMD/CommonJs都是JS模块化开发的标准,目前对应的实现是RequireJS,SeaJs, nodeJs;

    CommonJS:服务端js

    CommonJS 是以在浏览器环境之外构建 javaScript 生态系统为目标而产生的写一套规范,主要是为了解决 javaScript 的作用域问题而定义的模块形式,可以使每个模块它自身的命名空间中执行。

    实现方法:模块必须通过 module.exports 导出对外的变量或者接口,通过 require() 来导入其他模块的输出到当前模块的作用域中;

    主要针对服务端(同步加载文件)和桌面环境中,node.js 遵循的是 CommonJS 的规范;CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。

    • require()用来引入外部模块;

    • exports对象用于导出当前模块的方法或变量,唯一的导出口;

    • module对象就代表模块本身。

    // 定义一个module.js文件var A = () => console.log('我是定义的模块');
    // 1.第一种返回方式module.exports = A; // 2.第二种返回方式 module.exports.test = A// 3.第三种返回方式 exports.test = A;
    // 定义一个test.js文件【这两个文件在同一个目录下】var module = require("./module");
    //调用这个模块,不同的返回方式用不同的方式调用// 1.第一种调用方式module();// 2.第二种调用方式 module.test();// 3.第三种调用方式 module.test();
    // 执行文件node test.js

    AMD:异步模块定义【浏览器端js】

    AMD 是 Asynchronous Module Definition 的缩写,意思是异步模块定义;采用的是异步的方式进行模块的加载,在加载模块的时候不影响后边语句的运行。主要是为前端 js 的表现指定的一套规范。

    实现方法:通过define方法去定义模块,通过require方法去加载模块。
    define(id?,dependencies?,factory): 它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中。没什么依赖,就定义简单的模块(或者叫独立的模块)
    require([modules], callback): 第一个参数[modules],是需加载的模块名数组;第二个参数callback,是模块加载成功之后的回调函数

    主要针对浏览器js,requireJs遵循的是 AMD 的规范;

    // module1.js文件, 定义独立的模块define({    methodA: () => console.log('我是module1的methodA');    methodB: () => console.log('我是module1的methodB');});
    // module2.js文件, 另一种定义独立模块的方式define(() => { return { methodA: () => console.log('我是module2的methodA'); methodB: () => console.log('我是module2的methodB'); };});
    // module3.js文件, 定义非独立的模块(这个模块依赖其他模块)define(['module1', 'module2'], (m1, m2) => { return { methodC: () => { m1.methodA(); m2.methodB(); } };});

    //定义一个main.js,去加载这些个模块require(['module3'], (m3) => { m3.methodC();});

    // 为避免造成网页失去响应,解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:// async属性表明这个文件需要异步加载,避免网页失去响应。// IE不支持这个属性,只支持defer,所以把defer也写上。
    // data-main属性: 指定网页程序的主模块
    // 控制台输出结果我是module1的methodA我是module2的methodB

    CMD:通用模块定义【浏览器端js】

    CMD 是 Common Module Definition 的缩写,通过异步的方式进行模块的加载的,在加载的时候会把模块变为字符串解析一遍才知道依赖了哪个模块;

    主要针对浏览器端(异步加载文件),按需加载文件。对应的实现是seajs

    AMD和CMD的区别

    1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible(尽可能的懒加载,也称为延迟加载,即在需要的时候才加载)。

    2. CMD 推崇依赖就近,AMD 推崇依赖前置。

    // CMDdefine(function(require, exports, module) {    var a = require('./a');    a.doSomething();    // ...    var b = require('./b');   // 依赖可以就近书写    b.doSomething();    // ... })
    // AMDdefine(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething(); // ... b.doSomething(); //...})

    import和require区别

    import和require都是被模块化使用。

    • require是CommonJs的语法(AMD规范引入方式),CommonJs的模块是对象。import是es6的一个语法标准(浏览器不支持,本质是使用node中的babel将es6转码为es5再执行,import会被转码为require),es6模块不是对象。

    • require是运行时加载整个模块(即模块中所有方法),生成一个对象,再从对象上读取它的方法(只有运行时才能得到这个对象,不能在编译时做到静态化),理论上可以用在代码的任何地方。import是编译时调用,确定模块的依赖关系,输入变量(es6模块不是对象,而是通过export命令指定输出代码,再通过import输入,只加载import中导的方法,其他方法不加载),import具有提升效果,会提升到模块的头部(编译时执行)

    export和import可以位于模块中的任何位置,但是必须是在模块顶层,如果在其他作用域内,会报错(es6这样的设计可以提高编译器效率,但没法实现运行时加载)。

    • require是赋值过程,把require的结果(对象,数字,函数等),默认是export的一个对象,赋给某个变量(复制或浅拷贝)。import是解构过程(需要谁,加载谁)。

    require/exports:

    // require: 真正被require出来的是来自module.exports指向的内存块内容const a = require('a') //
    // exports: 只是 module.exports的引用,辅助module.exports操作内存中的数据exports.a = a module.exports = a

    import/export:

    // importimport a from 'a';import { default as a  } from 'a';import  *  as a  from 'a';import { fun1,fun2 } from 'a';// exportexport default a;export const a = 1;export functon a { ... };export { fun1, fun2 };
    http和https

    Http:超文本传输协议(Http,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。设计Http最初的目的是为了提供一种发布和接收HTML页面的方法。它可以使浏览器更加高效。

    Http协议是以明文方式发送信息的,如果黑客截取了Web浏览器和服务器之间的传输报文,就可以直接获得其中的信息。

    Https:是以安全为目标的Http通道,是Http的安全版。Https的安全基础是SSL。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:SSL记录协议(SSL Record Protocol),它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。

    SSL握手协议(SSL Handshake Protocol),它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

    HTTP与HTTPS的区别

    1、HTTP是超文本传输协议,信息是明文传输,HTTPS是具有安全性的SSL加密传输协议。
    2、HTTPS协议需要ca申请证书,一般免费证书少,因而需要一定费用。
    3、HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样。前者是80,后者是443。
    4、HTTP连接是无状态的,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,安全性高于HTTP协议。

    https的优点

    尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:

    1)使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
    2)HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
    3)HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
    4)谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。

    Https的缺点

    1)Https协议握手阶段比较费时,会使页面的加载时间延长近。
    2)Https连接缓存不如Http高效,会增加数据开销,甚至已有的安全措施也会因此而受到影响;
    3)SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
    4)Https协议的加密范围也比较有限。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。

    遍历方法

    for

    在for循环中,循环取得数组或是数组类似对象的值,譬如arguments和HTMLCollection对象。

    不足:

    • 在于每次循环的时候数组的长度都要去获取;

    • 终止条件要明确;

    foreach(),map()

    两个方法都可以遍历到数组的每个元素,而且参数一致;
    forEach(): 对数组的每个元素执行一次提供的函数, 总是返回undefined;
    map(): 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。返回值是一个新的数组;

    var array1 = [1,2,3,4,5];
    var x = array1.forEach((value,index) => { console.log(value); return value + 10;});console.log(x); // undefined
    var y = array1.map((value,index) => { console.log(value); return value + 10;});console.log(y); // [11, 12, 13, 14, 15]

    for in

    经常用来迭代对象的属性或数组的每个元素,它包含当前属性的名称或当前数组元素的索引。
    当遍历一个对象的时候,变量 i 是循环计数器 为 对象的属性名, 以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
    当遍历一个数组的时候,变量 i 是循环计数器 为 当前数组元素的索引

    不足:

    for..in循环会把某个类型的原型(prototype)中方法与属性给遍历出来.

    const array = ["admin","manager","db"]; array.color = 'red';array.prototype.name= "zhangshan"; for(var i in array){    if(array.hasOwnProperty(i)){         console.log(array[i]);  // admin,manager,db,color    }}// hasOwnProperty(): 对象的属性或方法是非继承的,返回true

    for … of

    迭代循环可迭代对象(包括Array,Map,Set,String,TypedArray,arguments 对象)等等。不能遍历对象。只循环集合本身的元素

    var a = ['A', 'B', 'C'];var s = new Set(['A', 'B', 'C']);var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);a.name = 'array';for (var x of a) { console.log(x); //'A', 'B', 'C'}for (var x of s) { console.log(x);//'A', 'B', 'C'}for (var x of m) { console.log(x[0] + '=' + x[1]);//1='x',2='y',3='z'}
    继承

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

    原型链继承

    核心: 将父类的实例作为子类的原型。

    function Dog(age) {  this.age = age;}Dog.protoType = New Animal();Dog.prototype.name = 'dog';
    const dog = new Dog(12);console.log(dog.name);console.log(dog.eat('age'));console.log(dog instanceof Animal); //true console.log(dog instanceof Dog); //true

    new 创建新实例对象经过了以下几步:

    1.创建一个新对象
    2.将新对象的_proto_指向构造函数的prototype对象
    3.将构造函数的作用域赋值给新对象 (也就是this指向新对象)
    4.执行构造函数中的代码(为这个新对象添加属性)
    5.返回新的对象

    // 1. 创建一个新对象var Obj = {};// 2. 将新对象的_proto_指向构造函数的prototype对象Obj._proto_ =  Animal.prototype();// 3. 执行构造函数中的代码(为这个新对象添加属性) Animal.call(Obj);// 4. 返回新的对象return Obj;

    特点:
    1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性
    2.非常纯粹的继承关系,实例是子类的实例,也是父类的实例
    3.父类新增原型方法/原型属性,子类都能访问到

    缺点:
    1.新实例无法向父类构造函数传参。
    2.继承单一。
    3.所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
    4.要想为子类新增原型上的属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

    构造函数继承

    核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

    function Dog(name) {  Animal.apply(this, 'dog');  this.name = name;}
    const dog = new Dog();console.log(dog.name);console.log(dog.eat('age'));console.log(dog instanceof Animal); //false console.log(dog instanceof Dog); //true

    重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

    特点:
    1.只继承了父类构造函数的属性,没有继承父类原型的属性。
    2.解决了原型链继承缺点1、2、3。
    3.可以实现多继承,继承多个构造函数属性(call多个)。
    4.在子实例中可向父实例传参。

    缺点:
    1.能继承父类构造函数的属性。
    2.无法实现构造函数的复用。(每次用每次都要重新调用)
    3.每个新实例都有父类构造函数的副本,臃肿。
    4.实例并不是父类的实例,只是子类的实例

    组合继承(原型链继承和构造函数继承)(常用)

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

    function Cat(name){  Animal.call(this, name);  this.name = name;}Cat.prototype = new Animal();Cat.prototype.constructor = Cat;
    var cat = new Cat();console.log(cat.name);console.log(cat.sleep());console.log(cat instanceof Animal); // trueconsole.log(cat instanceof Cat); // true

    重点:结合了两种模式的优点,传参和复用

    特点:
    1.可以继承父类原型上的属性,可以传参,可复用。
    2.每个新实例引入的构造函数属性是私有的。
    3.既是子类的实例,也是父类的实例

    缺点:
    调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

    原型式继承

        

    重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

    特点:
    类似于复制一个对象,用函数来包装。

    缺点:
    1.所有实例都会继承原型上的属性。
    2.无法实现复用。(新实例属性都是后面添加的)

    寄生式继承

      
      
    重点:就是给原型式继承外面套了个壳子。
    优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
    缺点:没用到原型,无法复用。

    寄生组合式继承(常用)

    寄生:在函数内返回对象然后调用
    组合:
    1、函数的原型等于另一个实例。
    2、在函数中用apply或者call引入另一个构造函数,可传参。

    function Cat(name){  Animal.call(this);  this.name = name || 'Tom';}(function(){  // 创建一个没有实例方法的类  var Super = function(){};  Super.prototype = Animal.prototype;  //将实例作为子类的原型  Cat.prototype = new Super();})();
    var cat = new Cat();Cat.prototype.constructor = Cat; // 需要修复下构造函数console.log(cat.name);console.log(cat.sleep());console.log(cat instanceof Animal); // trueconsole.log(cat instanceof Cat); //true

    重点:修复了组合继承的问题。

    本文完〜

浏览 40
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报