• 继承-安全-设计模式


    继承 与 原型、原型链

    1. 继承是什么?

    • 继承就是一个对象可以访问另外一个对象中的属性和方法

    2. 继承的目的?

    • 继承的目的就是实现原来设计与代码的重用

    3. 继承的方式

    • java、c++等:class
    • **javaScript: 原型链 ** ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 的继承依然和基于类的继承没有一点关系

    4. 原型与原型链

    JavaScript 只有一种结构:对象。

    JavaScript 的每个对象都包含了一个隐藏属性__proto__,我们就把该隐藏属性 proto 称之为该对象的原型 (prototype),proto 指向了内存中的另外一个对象,我们就把 proto 指向的对象称为该对象的原型,那么该对象就可以直接访问其原型对象的方法或者属性。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzx6Lxsa-1666612964587)(02.png)]

    看到使用 C.name 和 C.color 时,给人的感觉属性 name 和 color 都是对象 C 本身的属性,但实际上这些属性都是位于原型对象上,我们把这个查找属性的路径称为原型链

    每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

    总结:继承就是一个对象可以访问另外一个对象中的属性和方法,在JavaScript 中,我们通过原型和原型链的方式来实现了继承特性。

    5. 继承的方式

    5.1 构造函数如何创建对象

    function DogFactory(type, color) {
        this.type = type;
        this.color = color
    }
    
    var dog = new DogFactory('Dog','Black')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建实例的过程

    var dog = {};
    dog.__proto__ = DogFactory.prototype;
    DogFactory.call(dog,'Dog','Black');
    
    • 1
    • 2
    • 3

    观察上图,我们可以看到执行流程分为三步:

    首先,创建了一个空白对象 dog;

    然后,将 DogFactory 的 prototype 属性设置为 dog 的原型对象,这就是给 dog 对象设置原型对象的关键一步;

    每个函数对象中都有一个公开的 prototype 属性,当你将这个函数作为构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的 prototype 属性,所以通过该构造函数创建的任何实例都可以通过原型链找到构造函数的prototype上的属性

    最后,再使用 dog 来调用 DogFactory,这时候 DogFactory 函数中的 this 就指向了对象 dog,然后在 DogFactory 函数中,利用 this 对对象 dog 执行属性填充操作,最终就创建了对象 dog。

    实例的proto属性 == 构造函数的proyotype

    5.2 原型链继承

    原理: 实现的本质是通过将子类的原型指向了父类的实例,

    优点:

    • 父类新增原型方法/原型属性,子类都能访问到
    • 简单容易实现

    缺点:

    • 不能实现多重继承
    • 来自原型对象的所有属性被所有实例共享
    • 创建子类实例时,无法向父类构造函数传参
      在这里插入图片描述
    //父类型
    function Person(name, age) {
        this.name = name,
        this.age = age,
        this.play = [1, 2, 3]
        this.setName = function () { }
    }
    Person.prototype.setAge = function () { }
    //子类型
    function Student(price) {
        this.price = price
        this.setScore = function () { }
    }
    Student.prototype = new Person('wang',23) // 子类型的原型为父类型的一个实例对象
    var s1 = new Student(15000)
    var s2 = new Student(14000)
    console.log(s1,s2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.3 借用构造函数实现继承

    原理:在子类型构造函数中通用call()调用父类型构造函数

    特点

    • 解决了原型链继承中子类实例共享父类引用属性的问题
    • 创建子类实例时,可以向父类传递参数
    • 可以实现多重继承(call多个父类对象)

    缺点

    • 实例并不是父类的实例,只是子类的实例
    • 只能继承父类的实例属性和方法,不能继承父类原型属性和方法
    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
    function Person(name, age) {
        this.name = name,
        this.age = age,
        this.setName = function () {}
      }
      Person.prototype.setAge = function () {}
      function Student(name, age, price) {
        Person.call(this, name, age) 
        // 相当于: 
        /*
        this.Person(name, age)
        this.name = name
        this.age = age*/
        this.price = price
      }
      var s1 = new Student('Tom', 20, 15000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5.4 原型链+借用构造函数的组合继承

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

    优点

    • 可以继承实例属性/方法,也可以继承原型属性/方法
    • 不存在引用属性共享问题
    • 可传参
    • 父类原型上的函数可复用

    缺点

    • 调用了两次父类构造函数,生成了两份实例
    function Person(name, age) {
        this.name = name,
        this.age = age,
        this.setAge = function () { }
    }
    Person.prototype.setAge = function () {
        console.log("111")
    }
    function Student(name, age, price) {
        Person.call(this,name,age)
        this.price = price
        this.setScore = function () { }
    }
    Student.prototype = new Person()
    Student.prototype.constructor = Student//组合继承也是需要修复构造函数指向的
    var s1 = new Student('Tom', 20, 15000)
    var s2 = new Student('Jack', 22, 14000)
    console.log(s1)
    console.log(s1.constructor) //Student
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5.5 ES6 class继承

    **原理:**ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

    优点

    • 语法简单易懂,操作更方便

    缺点

    • 并不是所有的浏览器都支持class关键字
    class Person {
        //调用类的构造方法
        constructor(name, age) {
            this.name = name
            this.age = age
        }
        //定义一般的方法
        showName() {
            console.log("调用父类的方法")
            console.log(this.name, this.age);
        }
    }
    let p1 = new  Person('kobe', 39)
    console.log(p1)
    //定义一个子类
    class Student extends Person {
        constructor(name, age, salary) {
            super(name, age)//通过super调用父类的构造方法
            this.salary = salary
        }
        showName() {//在子类自身定义方法
            console.log("调用子类的方法")
            console.log(this.name, this.age, this.salary);
        }
    }
    let s1 = new Student('wade', 38, 1000000000)
    console.log(s1)
    s1.showName()
    
    • 1
    • 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

    安全

    1. CSRF攻击

    1.1 什么是CSRF攻击?

    ​ CSRF全称为跨站请求伪造(Cross-site request forgery)

    ​ 攻击者盗用了你的身份信息,以你的名义发送恶意请求,

    ​ 对服务器来说这个请求是你发起的,却完成了攻击者所期望的一个操作

    1.2 危害

    ​ 修改用户信息,修改密码,以你的名义发送邮件、发消息、盗取你的账号等

    1.3 攻击条件

    ​ 用户已登录存在CSRF漏洞的网站

    ​ 用户需要被诱导打开攻击者构造的恶意网站

    1.4 防范

    1.4.1 验证HTTP Referer字段

    ​ referer字段表明了请求来源,通过在服务器端添加对请求头字段的验证拒绝一切跨站请求,

    ​ 但是请求头可以绕过,XHR对象通过setRequestHeader方法可以伪造请求头

    1.4.2 添加token验证

    ​ 客户端令牌token通常作为一种身份标识,由服务器端生成的一串字符串,当第一次登录后,

    ​ 服务器生成一个token返回给客户端,以后客户端只需带上token来请求数据即可,无需再次带上用户名和密码。

    ​ 如果来自浏览器请求中的token值与服务器发送给用户的token不匹配,或者请求中token不存在,

    ​ 则拒绝该请求,使用token验证可以有效防止CSRF攻击,但增加了后端数据处理的工作量

    1.4.3 验证码

    ​ 发送请求前需要输入基于服务端判断的验证码,机制与token类似,

    ​ 验证码强制用户与web完成交互后才能实现正常请求,最简洁而有效的方法,但影响用户体验

    2. XSS攻击

    2.1 什么是xss攻击

    XSS又叫CSS(Cross Site Script),跨站脚本攻击:攻击者在目标网站植入恶意脚本(js / html),用户在浏览器上运行时可以获取用户敏感信息(cookie / session)、修改web页面以欺骗用户、与其他漏洞相结合形成蠕虫等。

    对特殊字符进行转译就好了(vue/react等主流框架已经避免类似问题,vue举例:不能在template中写script标签,无法在js中通过ref或append等方式动态改变或添加script标签)

    2.2 危害

    • 使网页无法正常运行
    • 获取cookie信息
    • 劫持流量恶意跳转

    2.3 攻击条件

    • 网页内部有输入框,内容可存储在服务器上

    2.4 防御措施(对用户输入内容和服务端返回内容进行过滤和转译)

    • 现代大部分浏览器都自带 XSS 筛选器,vue / react 等成熟框架也对 XSS 进行一些防护
    • 过滤,对诸如

    3. iframe

    3.1 如何让自己的网站不被其他网站的 iframe 引用?

    // 检测当前网站是否被第三方iframe引用
    // 若相等证明没有被第三方引用,若不等证明被第三方引用。当发现被引用时强制跳转百度。
    if(top.location != self.location){
        top.location.href = 'http://www.baidu.com'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2 如何禁用,被使用的 iframe 对当前网站某些操作?

    sandbox是html5的新属性,主要是提高iframe安全系数。iframe因安全问题而臭名昭著,这主要是因为iframe常被用于嵌入到第三方中,然后执行某些恶意操作。
    现在有一场景:我的网站需要 iframe 引用某网站,但是不想被该网站操作DOM、不想加载某些js(广告、弹框等)、当前窗口被强行跳转链接等,我们可以设置 sandbox 属性。如使用多项用空格分隔。

    • allow-same-origin:允许被视为同源,即可操作父级DOM或cookie等
    • allow-top-navigation:允许当前iframe的引用网页通过url跳转链接或加载
    • allow-forms:允许表单提交
    • allow-scripts:允许执行脚本文件
    • allow-popups:允许浏览器打开新窗口进行跳转
    • “”:设置为空时上面所有允许全部禁止

    4. opener

    4.1 原理

    在项目中需要 打开新标签 进行跳转一般会有两种方式,通过这两种方式打开的页面可以使用 window.opener 来访问源页面的 window 对象

    HTML ->

    JS -> window.open(‘http://www.baidu.com’)

    场景:A 页面通过 或 window.open 方式,打开 B 页面。但是 B 页面存在恶意代码如下:

    • window.opener.location.replace(‘https://www.baidu.com’) 【此代码仅针对打开新标签有效】,此时,用户正在浏览新标签页,但是原来网站的标签页已经被导航到了百度页面。

    4.2 风险

    • 新打开的地址无限模仿用户要打开的网站,用户输入用户名密码或者交易等都在恶意网站上进行,风险极高,即使在跨域状态下 opener 仍可以调用 location.replace 方法。

    4.3 避免方案

    <a target="_blank" href="" rel="noopener noreferrer nofollow">a标签跳转urla>
    
    • 1

    通过 rel 属性进行控制:
    noopener:会将 window.opener 置空,从而源标签页不会进行跳转(存在浏览器兼容问题)
    noreferrer:兼容老浏览器/火狐。禁用HTTP头部Referer属性(后端方式)。
    nofollow:指示搜索引擎不要追踪(即抓取)网页上的任何出站链接

    <button onclick='openurl("http://www.baidu.com")'>click跳转</button>
    
    function openurl(url) {
        var newTab = window.open();
        newTab.opener = null;
        newTab.location = url;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5. ClickJacking(点击劫持)

    5.1 原理

    1. 访问者被恶意页面吸引。怎样吸引的不重要。
    2. 页面上有一个看起来无害的链接(例如:“变得富有”或者“点我,超好玩!”)。
    3. 恶意页面在该链接上方放置了一个透明的