继承-安全-设计模式

继承 与 原型、原型链

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')

创建实例的过程

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

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

首先,创建了一个空白对象 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)

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)

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

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. 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'
}

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标签跳转url</a>

通过 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;
}

5. ClickJacking(点击劫持)

5.1 原理

  1. 访问者被恶意页面吸引。怎样吸引的不重要。
  2. 页面上有一个看起来无害的链接(例如:“变得富有”或者“点我,超好玩!”)。
  3. 恶意页面在该链接上方放置了一个透明的 <iframe>,其 src 来自于 facebook.com,这使得“点赞”按钮恰好位于该链接上面。这通常是通过 z-index 实现的。
  4. 用户尝试点击该链接时,实际上点击的是“点赞”按钮。

5.2 风险

  • 在用户不知情的情况下就对某个网站进行了操作

5.3 避免方案

X-Frame-Options:X-Frame-Options可以说是为了解决ClickJacking而生的,它有三个可选的值:

DENY 拒绝当前页面加载任何frame页面
SAMEORIGIN frame页面的地址只能为同源域名下的页面
ALLOW-FROM origin 定义允许frame加载的页面地址

6.本地存储数据问题

很多开发者为了方便,把一些个人信息不经加密直接存到本地或者cookie,这样是非常不安全的,黑客们可以很容易就拿到用户的信息,所有在放到cookie中的信息或者localStorage里的信息要进行加密,加密可以自己定义一些加密方法或者网上寻找一些加密的插件,或者用base64进行多次加密然后再多次解码,这样就比较安全了。

7. 第三方依赖安全隐患

项目开发,很多都喜欢用别人写好的包,为了方便快捷,很快的就搭建起项目,自己写的代码不到20%,过多的用第三方依赖或者插件,一方面会影响性能问题,另一方面第三方的依赖或者插件存在很多安全性问题,也会存在这样那样的漏洞,所以使用起来得谨慎。

解决办法:手动去检查那些依赖的安全性问题基本是不可能的,最好是利用一些自动化的工具进行扫描过后再用,比如NSP(Node Security Platform),Snyk等等。

8.HTTPS加密传输数据

在浏览器对服务器访问或者请求的过程中,会经过很多的协议或者步骤,当其中的某一步被黑客拦截的时候,如果信息没有加密,就会很容易被盗取。所以接口请求以及网站部署等最好进行HTTPS加密,这样防止被人盗取数据。

观察者与发布订阅者模式

1. 观察者模式

官方给出的观察者模式的解释是这样的:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式实现的,其实就是当目标对象的某个属性发生了改变,所有依赖着目标对象的观察者都将接到通知,做出相应动作。 所以在目标对象的抽象类里,会保存一个观察者序列。当目标对象的属性发生改变生,会从观察者队列里取观察者调用各自的方法。

class Subject {
    constructor() {
        this.observers = [];
    }
 
    add(observer) {
        this.observers.push(observer);
    }
 
    notify(...args) {
        this.observers.forEach(observer => observer.update(...args));
    }
}
 
class Observer {
    update(...args) {
        console.log(...args);
    }
}
 
// 创建观察者ob1
let ob1 =new Observer();
// 创建观察者ob2
let ob2 =new Observer();
// 创建目标sub
let sub =new Subject();
// 目标sub添加观察者ob1 (目标和观察者建立了依赖关系)
sub.add(ob1);
// 目标sub添加观察者ob2
sub.add(ob2);
// 目标sub触发SMS事件(目标主动通知观察者)
sub.notify('I fired `SMS` event');

2. 发布/订阅模式

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

发布/订阅模式相比于观察者模式多了一个中间媒介,因为这个中间媒介,发布者和订阅者的关联更为松耦合

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

也就是说,发布/订阅模式和观察者最大的差别就在于消息是否通过一个中间类进行转发。

classPubSub {
    constructor() {
        this.subscribers = [];
    }
     
    subscribe(topic, callback) {
        letcallbacks =this.subscribers[topic];
        if(!callbacks) {
            this.subscribers[topic] = [callback];
        }else{
            callbacks.push(callback);
        }
    }
     
    publish(topic, ...args) {
        letcallbacks =this.subscribers[topic] || [];
        callbacks.forEach(callback => callback(...args));
    }
}
 
// 创建事件调度中心,为订阅者和发布者提供调度服务
letpubSub =newPubSub();
// A订阅了SMS事件(A只关注SMS本身,而不关心谁发布这个事件)
pubSub.subscribe('SMS', console.log);
// B订阅了SMS事件
pubSub.subscribe('SMS', console.log);
// C发布了SMS事件(C只关注SMS本身,不关心谁订阅了这个事件)
pubSub.publish('SMS','I published `SMS` event');

3. 两种模式的区别

由上,我们就可以得出这两者的区别了:

  • 观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyTRJybN-1666613145312)(https://user-gold-cdn.xitu.io/2019/11/3/16e30c9869b37af4?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)]

4. 两种模式举例

**观察者:**事件处理(一旦触发事件,立即执行)

document.body.addEventListener('click',function(){
alert('我也是一个观察者,你一点击,我就知道了');
});

其中,body是发布者,即目标对象,当被点击的时候,向观察者反馈这一事件;JavaScript中函数也是一个对象,click这个事件的处理函数(alert(‘…’))就是观察者,当接收到目标对象反馈来的信息时进行一定处理。

**发布订阅者:**事件循环 (例如 发布者:ajax请求,发出去返回之后存在消息队列中,订阅者主线程并不会立即处理回调,而是等着事件循环机制在中间层将回调函数推送导致主线程中执行)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狡辉两门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值