一:JavaScript
1、闭包是什么?利弊?如何解决弊端?
闭包是什么:JS中内层函数可以访问外层函数的变量,外层函数无法操作内存函数的变量的特性。我们把这个特性称作闭包。
闭包的好处:
- 隔离作用域,保护私有变量;有了闭包才有局部变量,要不然都是全局变量了。
- 让我们可以使用回调,操作其他函数内部;
- 变量长期驻扎在内存中,不会被内存回收机制回收,即延长变量的生命周期;
闭包的弊端:内层函数引用外层函数变量,内层函数占用内存。如果不释放内存,过多时,易引起内存泄露。
解决办法:无法自动销户,就及时手动回收,使用后将函数的引用赋null。
2、深度拷贝
1、深拷贝与浅拷贝的区别?
拷贝的层级不同,深拷贝是指每一层数据的改动都不会影响原对象和新对象,浅拷贝只有第一层的属性变动不互相影响,深层的数据变动还会互相影响。
- 浅拷贝:Object.assign
- 深拷贝:JSON.stringify和JSON.parse
2、JSON的stringify和parse处理的缺点?
- 如果对象中有属性是function或者undefined,处理后会被过滤掉;
- 如果属性值是对象,且由构造函数生成的实例对象,会丢弃对象的
constructor
;
3、$.extend()
使用jquey的extend方法不仅能实现深度拷贝,还能实现深度合并。具体用法
深度拷贝:$.extend({},targetObject) // targetObject是需要复制的对象
深度合并:$.extend(true,{},targetObject1,targetObject2) // 可以将两个对象深度合并后再返回出一个新对象
3、如何判断空对象?如何区分数据类型?
判断空对象
1、用JSON的stringify和parse转成字符串后,跟'{}'对比;
2、用ES6,判断Object.keys(targetObject)返回值数组的长度是否为0;
3、用ES5,判断Object.getOwnPropertyNames(targetObject)返回的数组长度是否为0;
区分数据类型
let a = [1,2]
Object.prototype.toString.call(a) // '[object Array]'
4、如何改变this指向?区别?
- call/apply
let a = {
name: 'sunq',
fn:function(action){
console.log(this.name + ' love ' + action);
}
}
let b = {name:'sunLi'}
// 正常的this指向
a.fn('basketball'); // sunq love basketball
// 改变this指向,并体现call与apply的区别
a.fn.apply(b,['football']); // sunLi love football
a.fn.call(b,'football'); // sunLi love football
// call 和 apply 区别: call 和 apply 都是可以改变this 指向的问题, call 方法中传递参数要求一
// 个 一个传递参数。 但是apply 方法要求传递参数是一个数组形式。
- bind
// 还是上面的示例,bind也可以实现call和apply的效果。
// bind的不同之处在于bind会返回一个新的函数。如果需要传参,需要再调用该函数并传参
a.fn.bind(b)('piano'); // sunLi love piano
5、沙箱隔离怎么做?
使用iframe可以实现,变量隔离
6、浏览器存储,他们的区别?
- localStorage:永久保存,以键值对保存,存储空间5M
- sessionStorage:关闭页签/浏览器时清空
- cookie:随着请求发送,通过设置过期时间删除
- session:保存在服务端
localStorage/sessionStorage是window的属性,cookie是document的方法
7、常用的数组方法有哪些?
- 改变原数组:push、pop、shift、unshift、sort、splice、reverse
- 不改变原属组:concat、join、map、forEach、filter、slice
slice和splice的区别?
- slice切片的意思,根据传入的起始和终止下标,获取该范围数组。
- splice可根据传入参数个数不同实现删除、插入操作,直接操作原数组。第1个参数为起始下标,第2个为删除个数,第3个为要增加的数据。
数组如何滤重?
8、Dom事件流的顺序?什么是事件委托?
当页面上的一个元素被点击时,先从document向下一层层捕获到该元素。然后再向上冒泡,一层层触发。
事件委托是将事件写在父级元素上,e.target是事件捕获时那个最小的元素,即选中的元素。所以可以根据e.target操作选中的元素。这样不需要给每个子元素绑定事件,代码更加简约。
9、对原型链的认识?
js通过原型链模拟实现面向对象,比如通过实例化一个构造函数可以给每个对象挂载自己专属的属性,通过给类的prototype上赋方法是所有对象所共有的方法。每次实例化不再赋值原型链上的方法。
10、防抖/节流的区别?
区别:防抖只会在最后一次事件后执行触发函数,节流不管事件多么的频繁,都会保证在规定时间段内触发事件函数。
- 防抖:
原理是维护一个定时器,将很多个相同的操作合并成一个。规定在delay后触发函数,如果在此之前触发函数,则取消之前的计时重新计时,只有最后一次操作能被触发。例如:实时搜索的input,一直输入就不发送。
let input = document.querySelector("input");
let time = null;//time用来控制事件的触发
input.addEventListener('input',function(){
//防抖语句,把以前的定时删除,只执行最后一次
if(time !== null){
clearTimeout(time);
}
time = setTimeout(() => {
console.log(this.value);//业务实现语句,这里的this指向的是input
},500)
})
- 节流:
原理是判断是否达到一定的时间来触发事件。某个时间段内只能触发一次函数。例如:在指定的时间内多次触发无效
//节流
function throttle(fn, time) {//连续触发事件 规定的时间
let flag = false;
return function () {
//使用标识判断是否在规定的时间内重复触发了函数,没有就触发,有就不触发
if (!flag) {//不为假时 执行以下
fn();//触发事件
flag = true;//为真
setTimeout(() => {//超时调用(在规定的时间内只执行一次)
flag = false;
}, time);
}
}
}
mybtn.onclick = throttle(btn, 3000);//单击事件 节流(btn,3s时间)
二:Html
1、重绘和重排(回流/重构/重载)是什么?如何优化?
- 样式的调整会引起重绘,比如字体颜色、背景色调整等
- Dom的变动会引起重排,比如定位改动、元素宽高调整
避免循环插入dom,比如table的行。可以js循环生成多个dom后,一次性插入。
2、html5有哪些新特性?
- 本地存储,比如localStorage、sessionStorage
- 语义化标签,如header、footer、nav等,使代码结构清晰,利于seo
- canvas
- svg
- web worker,在主线程外再创建一个线程,可与主线程交互
- 拖放功能
三:CSS
1、如何实现一个宽度不固定的上下左右居中的弹框?
方法一:
.pop{
width: 300px;
height: 300px;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
border: 1px solid red;
}
方法二:
.chartLengend { // 父元素
width: 60px;
height: 40px;
position: relative;
.line { // 子元素
width: 100%;
height: 3px;
background-color: #DEA182;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 2px;
}
}
2、伪类和伪元素区别?
- 伪类本质上用于弥补常规css选择器的不足,因为如果没有我们可能需要多写一个class,所以叫伪类
.class:last-child{}
.class:first-child{}
a:link {color:green;}
a:visited {color:green;}
a:hover {color:red;}
a:active {color:yellow;}
- 伪元素本质上是创建了一个有内容的虚拟元素,如::before ::after。因为相当于多了一个元素/节点,所以叫为元素
// :before用于在某个元素之前插入某些内容。
// :after用于在某个元素之后插入某些内容。
css
p:before{
content:"Read this: ";
}
html:
<p>I live in Ducksburg</p>
页面展示:
Read this: I live in Ducksburg
F12看dom中:
before
Read this: I live in Ducksburg
四:Vue
1、单页面应用是什么?优缺点?如何弥补缺点
单页面对一个入口DOM通过路由去更改内容,整个应用只有一个html页面
SPA优点:用户体验好,没有页面切换就没有白屏情况;
SPA缺点:首屏加载慢,不利于SEO
SPA弥补:通过压缩、路由懒加载缓解首屏慢;通过SSR 服务器端渲染解决SEO问题;
2、组件及通信方式有哪些?
2.1、什么是组件?
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用:
声明组件
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
使用组件(把组件当作自定义元素)
<div id="components-demo">
<button-counter></button-counter>
</div>
引入组件
new Vue({ el: '#components-demo' })
2.2、父向子传值
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop 列表中:
组件内部声明prop
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
父组件里调用,并给prop赋值,传递到组件内部
<blog-post title="My journey with Vue"></blog-post>
2.3、父组件监听子组件事件
其实就是通过在父组件声明方法,并绑定在子组件上。以子组件内部触发方法的形式,向父组件传参,实现子向父传值的效果。如下
父组件中声明方法,并绑定在子组件上
<template>
<lineChart v-on:getQuotaVal="getQuotaVal"></lineChart>
</template>
<script>
methods: {
// 本事件用来监听折线图子组件,从子组件拿到指标数据
getQuotaVal:function(obj){
this.lineDateType = obj.lineDateType; // 这样父组件就拿到了,子组件的obj数据
}
},
</script>
子组件触发方法
that.val = {};
that.$emit('getQuotaVal',that.val); // 将子组件的数据发送过去;
** 2.4、兄弟组件间交互**
使用
EventBus(事件总线)
,vue.$bus.on和emit方法。
初始化——全局定义,可以将eventBus绑定到vue实例的原型上,也可以直接绑定到window对象上.
//main.js
Vue.prototype.$EventBus = new Vue();
触发事件
this.$EventBus.$emit('eventName', param1,param2,...)
监听事件
this.$EventBus.$on('eventName', (param1,param2,...)=>{
//需要执行的代码
})
移除监听事件
为了避免在监听时,事件被反复触发,通常需要在页面销毁时移除事件监听。或者在开发过程中,由于热更新,事件可能会被多次绑定监听,这时也需要移除事件监听。
this.$EventBus.$off('eventName');
3、v-if和v-show区别?
v-if控制Dom是否存在,v-show控制样式
4、vuex是什么?使用步骤大概说下
vuex是一个状态管理工具,集中式的管理所有组件的状态数据。统一的去管理组件,将组件的状态抽象为一个store文件,通过commit方法触发mutation里的函数来改变组件属性。
组件中可以使用computed属性监听数据的变化控制组件显隐等。如下举个loading组件的栗子
loading组件中根据Loading数据,控制DOM显隐
<template>
<div class="cover" v-show="Loading">
<div>加载中</div>
</div>
</template>
<script>
import Store from '../../store'
export default {
name: "Loading",
computed:{
Loading(){
return Store.state.Loading;
}
}
}
</script>
vuex集中管理状态,创建一个叫store的js文件
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// Loading组件
Loading:false,
},
mutations: {
// Loading组件
ChangeLoading:function (State,Value) {
State.Loading = Value;
}
},
});
使用loading的组件中,这样操作
import Store from '../../store'
Store.commit("changeFooter",true);
vuex中 mutation和action的区别和使用?
5、vue watch和computed区别?
computed
计算结果并返回,只有当被计算的属性发生改变时才会触发(即:计算属性的结果会被缓存,除非依赖的响应属性变化才会重新及孙)。
如上loading组件也有使用到computed属性。
watch
监听某一个值,当被监听的值发生变化时,执行相关操作。
与computed的区别是,watch更加适用于监听某一个值得变化,并做对应操作,比如请求后台接口等。而computed适用于计算已有的值并返回结果。 监听简单数据类型:
data(){
return{
'first':2
}
},
watch:{
first(){
console.log(this.first)
}
},
6、Vue的虚拟Dom是什么?谈一谈对vue diff算法的认识?key的作用?
7、谈谈对vue的双向绑定原理的理解?
双向绑定主要指修改数据时,无须操作DOM,视图会自动刷新。操作视图时绑定的数据也会跟随变动。
数据 => 视图
vue在初始化实例时,会用Object.defineProperty方法,给所有的数据添加setter函数,实现对数据变更的监听。当数据被修改时,生成新的虚拟DOM树,跟老的虚拟DOM对比,根据对比结果找出需要更新的节点进行更新。
视图 => 数据
从视图到数据较为简单,视图变化后触发监听如oninput等,在绑定的方法中修改数据。
8、vue首屏优化怎么做?
- 使用较轻量的组件,比如echart对应有vue-chart
- vue-cli开启打包压缩 和后台配合 gzip访问;
- 路由懒加载,分包;
- 打包时配置删掉log日志
- 资源过大可以使用cdn模式引入,不再打包到本地
9、vue2的缺陷是什么?如何解决vue2.0数组中某一项改变,页面不改变的情况?
缺陷:数据如果为对象直接新增属性,如果为数组通过下标操作数组项,页面无法触发更新。
原因: Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。关于数组作者通过重写push/pop/shift/unshift/splice/reverse/sort这些方法来实现数据的相应绑定,其余的操作无法触发页面更新;
对策:关于对象可以通过Vue.$set(obj,key,value),组件中通过this.$set(obj,key,value)实现新增,修改属性vue可以相应更新视图。关于数组也可以通过Vue.$set(obj,key,value),或者作者重写的那些方法来操作;
10、异步操作放在created还是mouted?
如果有些数据需要在初始化时就渲染的,比如select下拉框的下拉内容,在mouted中请求。好处如下
- 页面初始化速度更快,减少用户等待时间
- 放在 created 中有助于一致性,因为ssr 不支持 beforeMount 、mounted 钩子函数
11、vue-router的钩子函数有哪些?
组件内部钩子:beforeRouterEnter()、beforeRouterLeave、beforeRouterUpdate
12、页面如何跳转?如何跨页面传参数?
- router-link标签跳转
- 路由如下跳转,顺便把参数传了。如下
this.$router.push({
path: '/url',
query: {
par:parid
}
})
接受参数
var parid = this.$route.query.par;
13、vue子组件的生命周期?子元素在什么时候挂载?
- 父:beforeCreate 首先初始化父原素
- 父:created 父原素挂载数据
- 父:beforeMounte 父原素开始挂载dom,tpl里遇到子组件
- 子:beforeCeate 子组件开始挂载数据
- 子:created 子元素数据挂载成功
- 子:beforeMount 子元素开始挂载dom
- 子:mounted 子元素dom挂载结束
- 父:mounted 父原素dom挂载结束
- 父:beforeUpdate 下面开始类似于dom事件流
- 子:beforeUpdate
- 子:updated
- 父:updated
- 父:beforeDestory
- 子:beforeDestory
- 子:destroyed
- 父:destoryed
子元素在父元素挂载dom时,开始加载。子元素一直到加载完毕dom后,父原素结束dom挂载。后面就类似于dom事件流了。
14、vue的import和node的require区别?
JS支持两种模块化方式,commonjs和ES6。
commonjs用于nodejs,同步加载模块。ES6的import为了不卡顿,异步加载模块。
新版Nodejs也支持使用import,但是需要修改文件后缀名为.mjs,或者在package.json中,制定type字段为module。
五:ES6
1、箭头函数与es5函数区别?
- 箭头函数的this指向是固定的,普通的this指向是可变的
let a = {
name: 'sunq',
fn:function(action){
console.log(this.name + ' love ' + action);
}
}
let b = {name:'sunLi'}
// 正常的this指向调用他的对象
a.fn('basketball'); // sunq love basketball
// 改变this指向
a.fn.apply(b,['football']); // sunLi love football
// 如果将a对象的fn函数改成箭头函数,this.name会是undefined
// 箭头函数的this指向不会改变,且总是指向函数定义生效时所在的对象。
- 不可以当作构造函数,不可以对箭头函数使用
new
命令,否则会抛出一个错误。
var Person = function(name){
this.name = name;
}
let sunq = new Person('sq'); // {name: 'sq'}
var Person = (name) => {
this.name = name;
}
let sunq = new Person('sq'); // 报错 Person is not a constructor
- 无
arguments
对象 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
2、ES6提供的解决异步交互的新方法?区别?
Promise、Genarate、async\await
3、宏任务和微任务有哪些?执行顺序?
4、先并行请求2个接口后,再请求第3个接口,如何处理?
使用Promise.all()方法,将两个promise传入all方法,拿到异步结果再请求第三个
明明知道的语法,面试官一问我偏偏就是跟实际场景联系不到一块,
5、js的数据类型
string number null undefined boolean object bigInt symbol
6、说几个ES6新增的数组的方法 详情
map 实例方法,类似于forEach,但是返回新数组
find和findIndex 实例方法,传入一个匿名函数,ruturn出符合条件的项或下标
... 拓展运算符 基本功能是将一个数组转为用逗号分隔的参数序列
7、for in和for of的区别
- for in适合遍历对象,遍历数组拿到的是下标
- for of适合遍历数组,遍历数组直接拿到数组的项。for of只能遍历具备iterator接口的类型。
8、多个数据请求,如何顺序执行?
使用promise的then方法,或者写多个promise,async中使用await顺序执行。
9、proxy的理解,与defineProperty的区别?
- proxy直接给所有属性设置拦截,defineProperty只给指定的设置
- proxy拦截后要用Proxy实例调用,defineProperty可以直接使用原对象
// es6的proxy
let obj = {name:1,sex:2}
let p = new Proxy(obj,{
get:(value,key)=>{
return 'sq的'+obj[key]
}
});
p.name // sq的1
p.sex // sq的2
// es5的代理
let newObj = {name:1,sex:2}
Object.defineProperty(newObj,'name',{
get:function(val){ //defineProperty中get函数没有入参
return 'sq的' + val
}
})
newObj.name // sq的undefined
newObj.sex // 2
10、谈谈promise的原理?
六:ElementUI
1、如果需要修改样式怎么做?
- 再写一个样式表引入,!important覆盖
- 样式穿透
2、如何通过继承扩展 element-ui 组件的功能?
通过继承扩展 element-ui 组件的功能_elementui扩展组件_在厕所喝茶的博客-CSDN博客
七:Jquery
1、如何获取同一个cl下,最后一个li?
$("#id").children().eq(3).remove();
// 获取多个class中的某一个
$(".className").eq(n).attr("class") // 可行
$(".className")[n].attr("class") // 不可行
2、window.load和$(document).ready()的区别?执行先后顺序?
window.onload
必须等到页面内包括图片的所有元素加载完毕后才能执行。$(document).ready()
是DOM结构绘制完毕后就执行,不必等到加载完毕。通常简写为$()
总结:ready事件在load事件加载之前完成。
3、如何绑定一个点击事件?
// 方式一
$("#id").on('click',function(){});
// 方式二
$("#id").click(function(){});
// 方式三
$("#id").bind("click",function(){});
4、Jquery常用动画?
八:Git的使用
1、常用哪些语句?
pull、commit、push、reset、merge、log、branch、stash
stash如何使用?
- git stash -m 'xxx'
- git stash pop
2、版本回退语句?soft和hard区别?
git reset --soft 版本号
git reset --hard 版本号
soft会退后,代码改动在本地还保存的有。hard会删除本地改动,彻底抹去该版本的痕迹。详情
3、合并分支注意事项?
将自己分支合到目标分支前,最好先将目标分支合到自己分支上处理完冲突,再将自己的分支合回目标分支。
4、如何进行分支管理?
九:敏捷开发
1、什么是敏捷开发?
个人理解,敏捷开发就是把一个大需求拆为多个独立的小需求。每个小需求可独立开发、测试、上线,循序渐进的完成整个系统。每个版本的周期可能比较短,比如2周,或者4周。
比如某公司需要开发维护一个巨大的平台,可能把平台外包给多个公司干。如果用如上方法,并行开发,可显著缩减工期。
如果想要保证每个版本又快又顺利的上线,需要有完善的角色支持和流程规范。
2、敏捷开发的好处?
个人理解,当团队稍大,工期很紧时,如何有条不紊的保证版本质量就需要一套有效的流程了。
比如一个团队同时收到3个需求,每个需求分发给多个前后端开发。作为版本负责人或者项目负责人,如何把控每个人的代码质量、完成进度、过程留痕、风险规避,其实是有难度的。
一个需求如果经过,需求澄清、方案设计、设计评审、需求传递、版本排期/评审、开发划分、版本开发、测试用例评审、内部测试、代码评审、灰度试用&测试、版本发布、业务验收等完整的流程,可以有效地降低犯错的几率。也方便后期的查找责任人,总结各环节的问题,提升团队的工作效率,使交付越来越平稳。
十:开源项目
部分公司面试要求中有写,维护有开源项目且具有50个star者优先。
我平时有维护一个具备管理端/用户端/服务端的个人网站:sunq's blog
面试过程中无话可说时,可以拿出来聊聊,缓解尴尬。
博客的代码全部开源:源码Github地址
十一:网络相关
1、http和https的区别?
- HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。
2、常见状态码
- 200:成功返回
- 304:网页未更改有缓存,未重新请求
- 404:请求资源不在
- 500:服务器内部错误
- 503:服务器超时
十二:webpack
1、dependencies和devDependencies区别
安装时 --save -dev会放在devDependencies中,--save放在dependencies
devDependencies中安装的是开发环境使用的包,比如eslint、vue-cli-serve;
dependencies中安装的是生产和开发环境下都需要使用的包,比如vue、element、vant等
devDependencies中的依赖模块,在生产环境下不会被打入包内
十三:页面优化
1、某个页面加载较慢,从哪些方向分析、解决问题?
- 传统页面
首先判断是接口慢,还是页面慢。如果接口慢,后端优化。
如果前端页面加载慢,看是否是因为图片等资源过大,尝试替换不同格式体积的图片。定位是否是某些数据处理的函数,比较耗时。或者是否循环操作DOM,js生成dom后再批量插入。
如果页面直接卡死,就需要分析是否内存泄漏。比如大屏展示的定时刷新卡死,排查思路可如下:
使用chrome的任务管理器,操作页面观察的内存占用的变化。定位到是哪些操作,哪块代码导致内存占用飙升。
因为js并没有直接释放缓存的语法,只有靠浏览器的垃圾回收机制自动清理。我们需要做的是及时给不需要的变量赋空。
特别注意大数据的循环实例化后,变量是否及时赋空。定时器等闭包方法中是否存在内存泄漏,循环渲染地图时,是否先将之前地图数据清空等。
- 单页面应用
单页面一般不会某个页面加载慢,一般都集中在首屏加载时白屏较久。处理方法可参考上文中。
2、使用缓存
- 有些接口没必要每次打开页面都请求,可用cookie计时。某个时间段内不重新获取。
- 某些数据初始化时加载一次即可,可通过cookie计时,某个时间段内不用请求。
- 某些数据可用localstorage存储,默认填充input,更新时才重写缓存。这个只是用户体验好些
十四:Node.js
1、用过哪些插件(express中间件)?
- cors 解决express的跨域问题
- body-parser 解析post数据
- mongoClient mongodb官方提供的Node.js驱动
- crypto 用来加密,添加签名
- nodemailer 用来发邮件
2、mongodb和mysql的区别?
- mongodb是文档型非关系型数据库,mysql是关系型数据库
- mongodb以json形式存储数据,字段可以随意添加,mysql的字段需要提前确定好
- mysql多表之间只能通过外键建立关系,mysql可以多层嵌套也可拆表并关联
版权归原作者 sun_qqq 所有, 如有侵权,请联系我们删除。