普通视图

发现新文章,点击刷新页面。
今天 — 2025年8月20日首页

跨域江湖:六大绝技🗡️

2025年8月20日 10:05

你是否曾在开发时遇到过“跨域”这个让人头秃的家伙?今天我们就来聊聊前端跨域的那些事儿,带你从原理到实战,轻松掌握各种跨域姿势!

一、同源策略:前端的“护城河” 🏰

同源策略(Same-Origin Policy)是浏览器的一道安全防线。只有协议、域名、端口都相同的两个源,才能愉快地访问彼此的数据。否则,浏览器就会像门神一样把你拦在门外。

同源策略主要保护的是响应数据,而不是请求数据。所以我们可以通过一些骚操作来“曲线救国”。

二、跨域的六大绝技 🌈

1. JSONP:老牌跨域选手,GET请求专属

原理:利用<script>标签的src属性不受同源策略限制,通过动态创建<script>标签向跨域服务器发送请求。前端把回调函数名作为参数传给后端,后端把数据包装成该回调函数的调用形式返回,前端提前定义好同名函数来接收数据。

前端代码

<script>
function jsonp(url, cb) {
    return new Promise((resolve, 
    reject) => {
        const script = document.
        createElement('script')
        script.src = `${url}?cb=$
        {cb}`
        window[cb] = function 
        (data) {
            resolve(data)
        }
        document.body.appendChild
        (script)
    })
}
jsonp('http://localhost:3000''callback').then(res => {
    console.log(res);
})
</script>

服务端代码

const http = require('http')
const server = http.createServer
((req, res) => {
    const query = new URL(req.url, 
    `http://${req.headers.host}`).
    searchParams
    if (query.get('cb')) {
        const cb = query.get('cb')
        const data = 'hello world'
        const result = `${cb}("$
        {data}")`
        res.end(result)
    }
})
server.listen(3000() => {
    console.log('server is 
    running')
})

缺陷

  1. 只支持GET请求
  2. 必须后端配合
  3. 有安全隐患(比如XSS)

2. CORS:现代浏览器的官方跨域方案

原理:后端在响应头中配置允许的源、方法、头信息,告诉浏览器“这家伙我认识,放他进来”。

代码片段

const http = require('http')
const server = http.createServer((req, res) => {
    res.writeHead(200, {
        'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization'
    })
    res.end('hello cors')
})
server.listen(3000, () => {
    console.log('server is running')
})

优点:支持各种HTTP方法,安全性高,灵活配置。

3. 反向代理:nginx/node双剑合璧

原理:利用nginx或node的代理能力,把前端的请求转发到后端服务器,再把响应返回给前端。前端眼里只有代理服务器,后端的真实地址被“隐藏”了。

前端代码

<script>
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://
localhost:3000')
xhr.send()
xhr.onreadystatechange = function 
() {
    if (xhr.readyState === 4 && 
    xhr.status === 200) {
        console.log(xhr.
        responseText);
    }
}
</script>

服务端代码

const http =require('http')
const server = http.createServer
((req, res) => {
    res.writeHead(200, {
        'Access-Control-Allow-Origi
        n''http://127.0.0.
        1:5500',
        'Access-Control-Allow-Metho
        ds''GET, POST, PUT, 
        DELETE, OPTIONS',
        'Access-Control-Allow-Heade
        rs''Content-Type, 
        Authorization'
    })
    http.request({
        host'192.168.1.75',
        port'3000',
        method'GET',
        path'/',
        headers: {}
    }, (proxyRes) => {
        proxyRes.on('data'(data) 
        => {
            res.end(data.toString
            ())
        })
    }).end()
})
server.listen(3000() => {
    console.log('server is running 
    3000')
})

优点:支持各种请求,安全灵活,适合大型项目。

4. WebSocket:实时通信的跨域利器

原理:WebSocket协议可以在浏览器和服务器之间建立持久连接,实现全双工通信,不受同源策略限制。

前端代码

<script>
function myWebSocket(url, params) {
    return new Promise((resolve, 
    reject) => {
        const socket = new 
        WebSocket(url)
        socket.onopen = () => {
            socket.send(JSON.
            stringify(params))
        }
        socket.onmessage = (res) 
        => {
             console.log(`从服务端接
             收到的数据: ${res.data}
             `);
        }
    })
}
myWebSocket('ws://localhost:3000', 
{age18})
</script>

服务端代码

const webSocket = require('ws')
const ws = new webSocket.Server({ 
port3000 })
let count = 0
ws.on('connection'(socket) => {
    socket.on('message'(data) => 
    {
        socket.send('欢迎访问服务端')
        setInterval(() => {
            count++
            socket.send(count)
        }, 5000)
    })
})

优点:实时性强,适合聊天室、游戏等场景。

5. postMessage:iframe页面间的“传话筒”

原理:当一个页面通过iframe嵌套了另一个页面,两个页面要通信时,利用postMessage方法安全地传递消息。

一级页面代码

<script>
let obj = { name: 'xxx', age: 19 }
let frame = document.getElementById
('frame')
frame.onload = function () {
    this.contentWindow.postMessage
    (JSON.stringify(obj), 'http://
    127.0.0.1:8080')
}
window.onmessage = function (e) {
    console.log(`从二级页面接收的数据
    : ${e.data}`);
}
</script>

二级页面代码

<script>
window.onmessage = function(e) {
    console.log(`从一级页面接收的数据
    : ${e.data}`);
    document.getElementById
    ('span').innerHTML = e.data
    const {age} = JSON.parse(e.
    data)
    setTimeout(() => {
        e.source.postMessage(`我是
        二级页面, 我收到了一级页面的消
        息, 我收到的消息中的年龄是: $
        {age}`, e.origin)
    }, 2000)
}
</script>

优点:安全、灵活,支持多窗口、iframe等多种场景。

6. document.domain:二级域名间的“亲兄弟”通信

原理:当两个页面的主域名不同,但二级域名相同,可以通过设置document.domain为相同的主域名,实现跨域通信。

代码片段

// 一级页面和二级页面都加上
document.domain = 'test.com'

注意:只适用于主域名相同的情况,比如:

三、跨域那些坑,你踩过几个?🕳️

  • JSONP只能GET,POST就歇菜了
  • CORS要后端配合,前端单干不行
  • 代理要配置好路径,别把自己绕晕了
  • WebSocket要注意协议和端口
  • postMessage要校验origin,别让坏人钻空子
  • document.domain只适合二级域名

四、跨域的未来:安全与便利并存 🚀

随着前端技术的发展,跨域方案越来越丰富,但安全永远是第一位。无论用哪种方法,都要记得防范XSS、CSRF等安全问题。

五、结语:跨域不再是“拦路虎” 🐯

前端跨域其实没那么可怕,只要掌握了这些绝技,遇到问题就能游刃有余。希望这篇文章能帮你理清思路,少踩坑,多写代码,早日成为跨域老司机!

昨天以前首页

面试官的 JS 继承陷阱,你能全身而退吗?🕳️

2025年8月17日 10:29

继承,是 JS 面试绕不开的灵魂拷问。本文将带你一网打尽 JS 继承的所有姿势,配合代码实例和细致讲解,助你面试不再慌张!

一、什么是继承?

继承,就是让子类可以访问到父类的属性和方法。JS 继承的实现方式多如牛毛,面试官最爱考察各种细节和坑点。

二、原型链继承

原理

子类的原型指向父类的实例。所有子类实例共享同一个父类实例。

代码演示

function Parent() {
    this.name = 'parent'
    this.like = ['a', 'b', 'c']
}
Child.prototype = new Parent()
function Child() {
    this.age = 18
}
let c = new Child()
let d = new Child()
c.like.push('d')
console.log(c.like) // ['a', 'b', 'c', 'd']
console.log(d.like) // ['a', 'b', 'c', 'd']

优缺点

  • 优点:实现简单,能访问父类属性和方法。
  • 缺点:引用类型属性会被所有实例共享,互相影响,容易踩坑。

面试官小贴士

"你能说说原型链继承的缺陷吗?为什么 like 属性会被所有实例共享?"

三、构造函数继承

原理

在子类构造函数中调用父类构造函数,this 指向子类实例。

代码演示

Parent.prototype.say = function () {
    console.log('hello')
}
function Parent() {
    this.name = 'parent'
    this.like = ['a', 'b', 'c']
}
function Child() {
    this.age = 18
    Parent.call(this)
}
let c = new Child()
console.log(c.say) // undefined

优缺点

  • 优点:每个实例独立拥有父类属性,引用类型不再共享。
  • 缺点:无法继承父类原型上的方法(如 say),只能继承构造函数里的属性。

面试官小贴士

"为什么 c.say 是 undefined?如何让子类也能继承父类原型上的方法?"

四、组合继承

原理

原型链继承 + 构造函数继承,双管齐下。

代码演示

Parent.prototype.say = function () {
    console.log('hello')
}
function Parent() {
    this.name = 'parent'
    this.like = ['a', 'b', 'c']
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
function Child() {
    this.age = 18
    Parent.call(this)
}
let c = new Child()
let d = new Child()
d.like.push('d')
console.log(d.like); // ['a', 'b', 'c', 'd']
console.log(c.like); // ['a', 'b', 'c']
console.log(c.say); // function
console.log(c.constructor); // Child

优缺点

  • 优点:既能继承父类属性,又能继承父类原型方法,引用类型不共享。
  • 缺点:父类构造函数会执行两次(一次给原型,一次给实例),有点浪费性能。

面试官小贴士

"组合继承为什么会调用两次父类构造函数?有没有更优的方案?"

五、原型式继承

原理

用 Object.create 或类似方式,以某对象为原型创建新对象。

代码演示

let parent = {
    name: 'parent',
    like: ['a', 'b', 'c']
}
let child1 = Object.create(parent)
let child2 = Object.create(parent)
child1.like.push('d')
console.log(child1.like); // ['a', 'b', 'c', 'd']
console.log(child2.like); // ['a', 'b', 'c', 'd']

优缺点

  • 优点:实现简单,适合对象克隆。
  • 缺点:引用类型属性依然共享。

面试官小贴士

"Object.create(parent) 和 new Object(parent) 有什么区别?"

六、寄生式继承

原理

在原型式继承基础上,增强返回的新对象。

代码演示

let parent = {
    name: 'parent',
    like: ['a', 'b', 'c']
}
function clone(origin) {
    let cloneObj = Object.create(origin)
    cloneObj.getLike = function() {
        return this.like
    }
    return cloneObj
}
let child1 = clone(parent)
let child2 = clone(parent)
child1.like.push('d')
console.log(child1.like); // ['a', 'b', 'c', 'd']
console.log(child2.like); // ['a', 'b', 'c', 'd']
console.log(child1.getLike()); // ['a', 'b', 'c', 'd']
console.log(child2.getLike()); // ['a', 'b', 'c', 'd']

优缺点

  • 优点:可以扩展新对象。
  • 缺点:引用类型属性依然共享。

面试官小贴士

"寄生式继承和原型式继承的本质区别是什么?"

七、寄生组合式继承(最优解)

原理

只继承父类原型,不调用父类构造函数,避免性能浪费。

代码演示

Parent.prototype.getName = function() {
    return this.Name
}
function Parent() {
    this.Name = 'parent'
    this.like = ['a', 'b', 'c']
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
function Child() {
    this.age = 18
    Parent.call(this)
}
let c1 = new Child()
console.log(c1.getName()); // 'parent'
console.log(c1.constructor); // Child

优缺点

  • 优点:只调用一次父类构造函数,性能最佳,继承属性和方法都不落下。
  • 缺点:实现稍复杂,但值得!

面试官小贴士

"为什么寄生组合式继承被称为 JS 继承的终极方案?"

八、ES6 类继承

原理

用 class 和 extends 语法糖,优雅实现继承。

代码演示

class Parent {
    constructor() {
        this.Name = 'parent'
        this.like = ['a', 'b', 'c']
    }
    getName() {
        return this.Name
    }
    static say() {
        console.log('hello');
    }
}
class Child extends Parent {
    constructor() {
        super()
        this.age = 18
    }
}
let p = new Parent()
console.log(p.getName()); // 'parent'
let c = new Child()
console.log(c.getName()); // 'parent'

优缺点

  • 优点:语法简洁,继承关系清晰,原型链自动处理。
  • 缺点:底层依然是原型链,只是语法糖。

面试官小贴士

"class 继承和传统原型链继承的本质区别是什么?"

九、知识点总结与面试答题模板

继承方式对比表

方式 是否共享引用类型 是否继承原型方法 构造函数调用次数 优缺点
原型链继承 1 引用类型共享
构造函数继承 1 不能继承原型方法
组合继承 2 性能浪费
原型式继承 0 引用类型共享
寄生式继承 0 引用类型共享
寄生组合式继承 1 性能最佳
ES6 类继承 1 语法糖

面试高频问题

  • 说说 JS 继承的实现方式及优缺点?
  • 为什么原型链继承会导致引用类型属性共享?
  • 如何实现一个既能继承属性又能继承方法的子类?
  • ES6 的 class 继承和传统继承有什么区别?

十、幽默收尾

JS 继承就像家庭聚会,谁家锅碗瓢盆都能借来用,但有时候大家都用同一个锅,炒出来的菜味道就不一样了!面试官最爱问的那些继承细节,你现在都能用段子和代码轻松拿下!


祝大家面试不再慌张,继承全家桶一把梭!🎉

❌
❌