Vue组件渲染和更新过程

渲染组件时,会通过 Vue.extend 办法构建子组件的构造函数,并进行实例化。最终手动调用$mount() 进行挂载。更新组件时会进行 patchVnode 流程,外围就是diff算法

如何在组件中批量应用Vuex的getter属性

应用mapGetters辅助函数, 利用对象开展运算符将getter混入computed 对象中

import {mapGetters} from 'vuex'export default{    computed:{        ...mapGetters(['total','discountTotal'])    }}

个别在哪个生命周期申请异步数据

咱们能够在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。

举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:

  • 能更快获取到服务端数据,缩小页面加载工夫,用户体验更好;
  • SSR不反对 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

action 与 mutation 的区别

  • mutation 是同步更新, $watch 严格模式下会报错
  • action 是异步操作,能够获取数据后调用 mutation 提交最终数据

参考:前端vue面试题具体解答

Vue data 中某一个属性的值产生扭转后,视图会立刻同步执行从新渲染吗?

不会立刻同步执行从新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立刻变动,而是按肯定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只有侦听到数据变动, Vue 将开启一个队列,并缓冲在同一事件循环中产生的所有数据变更。

如果同一个watcher被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和 DOM 操作是十分重要的。而后,在下一个的事件循环tick中,Vue 刷新队列并执行理论(已去重的)工作。

SPA首屏加载速度慢的怎么解决

一、什么是首屏加载

首屏工夫(First Contentful Paint),指的是浏览器从响应用户输出网址地址,到首屏内容渲染实现的工夫,此时整个网页不肯定要全副渲染实现,但须要展现以后视窗须要的内容

首屏加载能够说是用户体验中最重要的环节

对于计算首屏工夫

利用performance.timing提供的数据:

通过DOMContentLoad或者performance来计算出首屏工夫

// 计划一:document.addEventListener('DOMContentLoaded', (event) => {    console.log('first contentful painting');});// 计划二:performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]// 会返回一个 PerformancePaintTiming的实例,构造如下:{  name: "first-contentful-paint",  entryType: "paint",  startTime: 507.80000002123415,  duration: 0,};

二、加载慢的起因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否反复发送申请去加载了
  • 加载脚本的时候,渲染内容梗塞了

三、解决方案

常见的几种SPA首屏优化形式

  • 减小入口文件积
  • 动态资源本地缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件反复打包
  • 开启GZip压缩
  • 应用SSR

1. 减小入口文件体积

罕用的伎俩是路由懒加载,把不同路由对应的组件宰割成不同的代码块,待路由被申请的时候会独自打包路由,使得入口文件变小,加载速度大大增加

vue-router配置路由的时候,采纳动静加载路由的模式

routes:[     path: 'Blogs',    name: 'ShowBlogs',    component: () => import('./components/ShowBlogs.vue')]

以函数的模式加载路由,这样就能够把各自的路由文件别离打包,只有在解析给定的路由时,才会加载路由组件

2. 动态资源本地缓存

后端返回资源问题:

  • 采纳HTTP缓存,设置Cache-ControlLast-ModifiedEtag等响应头
  • 采纳Service Worker离线缓存

前端正当利用localStorage

3. UI框架按需加载

在日常应用UI框架,例如element-UI、或者antd,咱们经常性间接援用整个UI

import ElementUI from 'element-ui'Vue.use(ElementUI)

但实际上我用到的组件只有按钮,分页,表格,输出与正告 所以咱们要按需援用

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';Vue.use(Button)Vue.use(Input)Vue.use(Pagination)

4. 组件反复打包

假如A.js文件是一个罕用的库,当初有多个路由应用了A.js文件,这就造成了反复下载

解决方案:在webpackconfig文件中,批改CommonsChunkPlugin的配置

minChunks: 3

minChunks为3示意会把应用3次及以上的包抽离进去,放进公共依赖文件,防止了反复加载组件

5. 图片资源的压缩

图片资源尽管不在编码过程中,但它却是对页面性能影响最大的因素

对于所有的图片资源,咱们能够进行适当的压缩

对页面上应用到的icon,能够应用在线字体图标,或者雪碧图,将泛滥小图标合并到同一张图上,用以加重http申请压力。

6. 开启GZip压缩

拆完包之后,咱们再用gzip做一下压缩 装置compression-webpack-plugin

cnmp i compression-webpack-plugin -D

vue.congig.js中引入并批改webpack配置

const CompressionPlugin = require('compression-webpack-plugin')configureWebpack: (config) => {        if (process.env.NODE_ENV === 'production') {            // 为生产环境批改配置...            config.mode = 'production'            return {                plugins: [new CompressionPlugin({                    test: /\.js$|\.html$|\.css/, //匹配文件名                    threshold: 10240, //对超过10k的数据进行压缩                    deleteOriginalAssets: false //是否删除原文件                })]            }        }

在服务器咱们也要做相应的配置 如果发送申请的浏览器反对gzip,就发送给它gzip格局的文件 我的服务器是用express框架搭建的 只有装置一下compression就能应用

const compression = require('compression')app.use(compression())  // 在其余中间件应用之前调用

7. 应用SSR

SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器

从头搭建一个服务端渲染是很简单的,vue利用倡议应用Nuxt.js实现服务端渲染

四、小结

缩小首屏渲染工夫的办法有很多,总的来讲能够分成两大部分 :资源加载优化页面渲染优化

下图是更为全面的首屏优化的计划

大家能够依据本人我的项目的状况抉择各种形式进行首屏渲染的优化

Vue 中 computed 和 watch 有什么区别?

计算属性 computed

 (1)**反对缓存**,只有依赖数据发生变化时,才会从新进行计算函数; (2)计算属性内**不反对异步操作**; (3)计算属性的函数中**都有一个 get**(默认具备,获取计算属性)**和 set**(手动增加,设置计算属性)办法; (4)计算属性是主动监听依赖值的变动,从而动静返回内容。

侦听属性 watch

 (1)**不反对缓存**,只有数据发生变化,就会执行侦听函数; (2)侦听属性内**反对异步操作**; (3)侦听属性的值**能够是一个对象,接管 handler 回调,deep,immediate 三个属性**; (3)监听是一个过程,在监听的值变动时,能够触发一个回调,并**做一些其余事件**。

vue-loader是什么?它有什么作用?

答复范例

  1. vue-loader是用于解决单文件组件(SFCSingle-File Component)的webpack loader
  2. 因为有了vue-loader,咱们就能够在我的项目中编写SFC格局的Vue组件,咱们能够把代码宰割为<template><script><style>,代码会异样清晰。联合其余loader咱们还能够用Pug编写<template>,用SASS编写<style>,用TS编写<script>。咱们的<style>还能够独自作用以后组件
  3. webpack打包时,会以loader的形式调用vue-loader
  4. vue-loader被执行时,它会对SFC中的每个语言块用独自的loader链解决。最初将这些独自的块装配成最终的组件模块

原理

vue-loader会调用@vue/compiler-sfc模块解析SFC源码为一个描述符(Descriptor),而后为每个语言块生成import代码,返回的代码相似上面

// source.vue被vue-loader解决之后返回的代码// import the <template> blockimport render from 'source.vue?vue&type=template'// import the <script> blockimport script from 'source.vue?vue&type=script'export * from 'source.vue?vue&type=script'// import <style> blocksimport 'source.vue?vue&type=style&index=1'script.render = renderexport default script

咱们想要script块中的内容被作为js解决(当然如果是<script lang="ts">被作为ts理),这样咱们想要webpack把配置中跟.js匹配的规定都利用到形如source.vue?vue&type=script的这个申请上。例如咱们对所有*.js配置了babel-loader,这个规定将被克隆并利用到所在Vue SFC

import script from 'source.vue?vue&type=script

将被开展为:

import script from 'babel-loader!vue-loader!source.vue?vue&type=script'

相似的,如果咱们对.sass文件配置了style-loader + css-loader + sass-loader,对上面的代码

<style scoped lang="scss">

vue-loader将会返回给咱们上面后果:

import 'source.vue?vue&type=style&index=1&scoped&lang=scss'

而后webpack会开展如下:

import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
  • 当解决开展申请时,vue-loader将被再次调用。这次,loader将会关注那些有查问串的申请,且仅针对特定块,它会选中特定块外部的内容并传递给前面匹配的loader
  • 对于<script>块,解决到这就能够了,然而<template><style>还有一些额定工作要做,比方

    • 须要用 Vue 模板编译器编译template,从而失去render函数
    • 须要对 <style scoped>中的CSS做后处理(post-process),该操作在css-loader之后但在style-loader之前

实现上这些附加的loader须要被注入到曾经开展的loader链上,最终的申请会像上面这样:

// <template lang="pug">import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'// <style scoped lang="scss">import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'

Vue 模板编译原理

Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步

第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构DOM的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)

v-on能够监听多个办法吗?

能够监听多个办法

<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />

v-on 罕用修饰符

  • .stop 该修饰符将阻止事件向上冒泡。同理于调用 event.stopPropagation() 办法
  • .prevent 该修饰符会阻止以后事件的默认行为。同理于调用 event.preventDefault() 办法
  • .self 该指令只当事件是从事件绑定的元素自身触发时才触发回调
  • .once 该修饰符示意绑定的事件只会被触发一次

Computed 和 Methods 的区别

能够将同一函数定义为一个 method 或者一个计算属性。对于最终的后果,两种形式是雷同的

不同点:

  • computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相干依赖产生扭转时才会从新求值;
  • method 调用总会执行该函数。

Vue为什么没有相似于React中shouldComponentUpdate的生命周期

  • 考点: Vue的变动侦测原理
  • 前置常识: 依赖收集、虚构DOM、响应式零碎
根本原因是VueReact的变动侦测形式有所不同
  • 当React晓得发生变化后,会应用Virtual Dom Diff进行差别检测,然而很多组件实际上是必定不会发生变化的,这个时候须要 shouldComponentUpdate 进行手动操作来缩小diff,从而进步程序整体的性能
  • Vue在一开始就晓得那个组件产生了变动,不须要手动管制diff,而组件外部采纳的diff形式实际上是能够引入相似于shouldComponentUpdate相干生命周期的,然而通常正当大小的组件不会有适量的diff,手动优化的价值无限,因而目前Vue并没有思考引入shouldComponentUpdate这种手动优化的生命周期

Vue Ref的作用

  • 获取dom元素this.$refs.box
  • 获取子组件中的datathis.$refs.box.msg
  • 调用子组件中的办法this.$refs.box.open()

Vue 修饰符有哪些

事件修饰符

  • .stop 阻止事件持续流传
  • .prevent 阻止标签默认行为
  • .capture 应用事件捕捉模式,即元素本身触发的事件先在此处解决,而后才交由外部元素进行解决
  • .self 只当在 event.target 是以后元素本身时触发处理函数
  • .once 事件将只会触发一次
  • .passive 通知浏览器你不想阻止事件的默认行为

v-model 的修饰符

  • .lazy 通过这个修饰符,转变为在 change 事件再同步
  • .number 主动将用户的输出值转化为数值类型
  • .trim 主动过滤用户输出的首尾空格

键盘事件的修饰符

  • .enter
  • .tab
  • .delete (捕捉“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

零碎润饰键

  • .ctrl
  • .alt
  • .shift
  • .meta

鼠标按钮修饰符

  • .left
  • .right
  • .middle

v-if、v-show、v-html 的原理

  • v-if会调用addIfCondition办法,生成vnode的时候会疏忽对应节点,render的时候就不会渲染;
  • v-show会生成vnode,render的时候也会渲染成实在节点,只是在render过程中会在节点的属性中批改show属性值,也就是常说的display;
  • v-html会先移除节点下的所有节点,调用html办法,通过addProp增加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

Vue.extend 作用和原理

官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。

其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并

相干代码如下

export default function initExtend(Vue) {  let cid = 0; //组件的惟一标识  // 创立子类继承Vue父类 便于属性扩大  Vue.extend = function (extendOptions) {    // 创立子类的构造函数 并且调用初始化办法    const Sub = function VueComponent(options) {      this._init(options); //调用Vue初始化办法    };    Sub.cid = cid++;    Sub.prototype = Object.create(this.prototype); // 子类原型指向父类    Sub.prototype.constructor = Sub; //constructor指向本人    Sub.options = mergeOptions(this.options, extendOptions); //合并本人的options和父类的options    return Sub;  };}

SPA、SSR的区别是什么

咱们当初编写的VueReactAngular利用大多数状况下都会在一个页面中,点击链接跳转页面通常是内容切换而非页面跳转,因为良好的用户体验逐步成为支流的开发模式。但同时也会有首屏加载工夫长,SEO不敌对的问题,因而有了SSR,这也是为什么面试中会问到两者的区别

  1. SPA(Single Page Application)即单页面利用。个别也称为 客户端渲染(Client Side Render), 简称 CSRSSR(Server Side Render)即 服务端渲染。个别也称为 多页面利用(Mulpile Page Application),简称 MPA
  2. SPA利用只会首次申请html文件,后续只须要申请JSON数据即可,因而用户体验更好,节约流量,服务端压力也较小。然而首屏加载的工夫会变长,而且SEO不敌对。为了解决以上毛病,就有了SSR计划,因为HTML内容在服务器一次性生成进去,首屏加载快,搜索引擎也能够很不便的抓取页面信息。但同时SSR计划也会有性能,开发受限等问题
  3. 在抉择上,如果咱们的利用存在首屏加载优化需要,SEO需要时,就能够思考SSR
  4. 但并不是只有这一种代替计划,比方对一些不常变动的动态网站,SSR反而浪费资源,咱们能够思考预渲染(prerender)计划。另外nuxt.js/next.js中给咱们提供了SSG(Static Site Generate)动态网站生成计划也是很好的动态站点解决方案,联合一些CI伎俩,能够起到很好的优化成果,且能节约服务器资源

内容生成上的区别:

SSR

SPA

部署上的区别

Vue我的项目中有封装过axios吗?次要是封装哪方面的?

一、axios是什么

axios 是一个轻量的 HTTP客户端

基于 XMLHttpRequest 服务来执行 HTTP 申请,反对丰盛的配置,反对 Promise,反对浏览器端和 Node.js 端。自Vue2.0起,尤大发表勾销对 vue-resource 的官网举荐,转而举荐 axios。当初 axios 曾经成为大部分 Vue 开发者的首选

个性

  • 从浏览器中创立 XMLHttpRequests
  • node.js 创立 http申请
  • 反对 Promise API
  • 拦挡申请和响应
  • 转换申请数据和响应数据
  • 勾销申请
  • 主动转换JSON 数据
  • 客户端反对进攻XSRF

根本应用

装置

// 我的项目中装置npm install axios --S// cdn 引入<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

导入

import axios from 'axios'

发送申请

axios({          url:'xxx',    // 设置申请的地址  method:"GET", // 设置申请办法  params:{      // get申请应用params进行参数凭借,如果是post申请用data    type: '',    page: 1  }}).then(res => {    // res为后端返回的数据  console.log(res);   })

并发申请axios.all([])

function getUserAccount() {    return axios.get('/user/12345');}function getUserPermissions() {    return axios.get('/user/12345/permissions');}axios.all([getUserAccount(), getUserPermissions()])    .then(axios.spread(function (res1, res2) {     // res1第一个申请的返回的内容,res2第二个申请返回的内容    // 两个申请都执行实现才会执行}));

二、为什么要封装

axios 的 API 很敌对,你齐全能够很轻松地在我的项目中间接应用。

不过随着我的项目规模增大,如果每发动一次HTTP申请,就要把这些比方设置超时工夫、设置申请头、依据我的项目环境判断应用哪个申请地址、错误处理等等操作,都须要写一遍

这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以保护。为了进步咱们的代码品质,咱们应该在我的项目中二次封装一下 axios 再应用

举个例子:

axios('http://localhost:3000/data', {  // 配置代码  method: 'GET',  timeout: 1000,  withCredentials: true,  headers: {    'Content-Type': 'application/json',    Authorization: 'xxx',  },  transformRequest: [function (data, headers) {    return data;  }],  // 其余申请配置...}).then((data) => {  // todo: 真正业务逻辑代码  console.log(data);}, (err) => {  // 错误处理代码    if (err.response.status === 401) {  // handle authorization error  }  if (err.response.status === 403) {  // handle server forbidden error  }  // 其余错误处理.....  console.log(err);});

如果每个页面都发送相似的申请,都要写一堆的配置与错误处理,就显得过于繁琐了

这时候咱们就须要对axios进行二次封装,让应用更为便当

三、如何封装

  • 封装的同时,你须要和 后端协商好一些约定,申请头,状态码,申请超时工夫.......
  • 设置接口申请前缀:依据开发、测试、生产环境的不同,前缀须要加以辨别
  • 申请头 : 来实现一些具体的业务,必须携带一些参数才能够申请(例如:会员业务)
  • 状态码: 依据接口返回的不同status , 来执行不同的业务,这块须要和后端约定好
  • 申请办法:依据getpost等办法进行一个再次封装,应用起来更为不便
  • 申请拦截器: 依据申请的申请头设定,来决定哪些申请能够拜访
  • 响应拦截器: 这块就是依据 后端`返回来的状态码断定执行不同业务

设置接口申请前缀

利用node环境变量来作判断,用来辨别开发、测试、生产环境

if (process.env.NODE_ENV === 'development') {  axios.defaults.baseURL = 'http://dev.xxx.com'} else if (process.env.NODE_ENV === 'production') {  axios.defaults.baseURL = 'http://prod.xxx.com'}

在本地调试的时候,还须要在vue.config.js文件中配置devServer实现代理转发,从而实现跨域

devServer: {    proxy: {      '/proxyApi': {        target: 'http://dev.xxx.com',        changeOrigin: true,        pathRewrite: {          '/proxyApi': ''        }      }    }  }

设置申请头与超时工夫

大部分状况下,申请头都是固定的,只有少部分状况下,会须要一些非凡的申请头,这里将普适性的申请头作为根底配置。当须要非凡申请头时,将非凡申请头作为参数传入,笼罩根底配置

const service = axios.create({    ...    timeout: 30000,  // 申请 30s 超时      headers: {        get: {          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'          // 在开发中,个别还须要单点登录或者其余性能的通用申请头,能够一并配置进来        },        post: {          'Content-Type': 'application/json;charset=utf-8'          // 在开发中,个别还须要单点登录或者其余性能的通用申请头,能够一并配置进来        }  },})

封装申请办法

先引入封装好的办法,在要调用的接口从新封装成一个办法裸露进来

// get 申请export function httpGet({  url,  params = {}}) {  return new Promise((resolve, reject) => {    axios.get(url, {      params    }).then((res) => {      resolve(res.data)    }).catch(err => {      reject(err)    })  })}// post// post申请export function httpPost({  url,  data = {},  params = {}}) {  return new Promise((resolve, reject) => {    axios({      url,      method: 'post',      transformRequest: [function (data) {        let ret = ''        for (let it in data) {          ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'        }        return ret      }],      // 发送的数据      data,      // url参数      params    }).then(res => {      resolve(res.data)    })  })}

把封装的办法放在一个api.js文件中

import { httpGet, httpPost } from './http'export const getorglist = (params = {}) => httpGet({ url: 'apps/api/org/list', params })

页面中就能间接调用

// .vueimport { getorglist } from '@/assets/js/api'getorglist({ id: 200 }).then(res => {  console.log(res)})

这样能够把api对立治理起来,当前保护批改只须要在api.js文件操作即可

申请拦截器

申请拦截器能够在每个申请里加上token,做了对立解决后保护起来也不便

// 申请拦截器axios.interceptors.request.use(  config => {    // 每次发送申请之前判断是否存在token    // 如果存在,则对立在http申请的header都加上token,这样后盾依据token判断你的登录状况,此处token个别是用户实现登录后贮存到localstorage里的    token && (config.headers.Authorization = token)    return config  },  error => {    return Promise.error(error)  })

响应拦截器

响应拦截器能够在接管到响应后先做一层操作,如依据状态码判断登录状态、受权

// 响应拦截器axios.interceptors.response.use(response => {  // 如果返回的状态码为200,阐明接口申请胜利,能够失常拿到数据  // 否则的话抛出谬误  if (response.status === 200) {    if (response.data.code === 511) {      // 未受权调取受权接口    } else if (response.data.code === 510) {      // 未登录跳转登录页    } else {      return Promise.resolve(response)    }  } else {    return Promise.reject(response)  }}, error => {  // 咱们能够在这里对异样状态作对立解决  if (error.response.status) {    // 解决申请失败的状况    // 对不同返回码对相应解决    return Promise.reject(error.response)  }})

小结

  • 封装是编程中很有意义的伎俩,简略的axios封装,就能够让咱们能够领略到它的魅力
  • 封装 axios 没有一个相对的规范,只有你的封装能够满足你的我的项目需要,并且用起来不便,那就是一个好的封装计划

理论工作中,你总结的vue最佳实际有哪些

从编码格调、性能、平安等方面说几条:

编码格调方面:

  • 命名组件时应用“多词”格调防止和HTML元素抵触
  • 应用“细节化”形式定义属性而不是只有一个属性名
  • 属性名申明时应用“驼峰命名”,模板或jsx中应用“肉串命名”
  • 应用v-for时务必加上key,且不要跟v-if写在一起

性能方面:

  • 路由懒加载缩小利用尺寸
  • 利用SSR缩小首屏加载工夫
  • 利用v-once渲染那些不须要更新的内容
  • 一些长列表能够利用虚构滚动技术防止内存适度占用
  • 对于深层嵌套对象的大数组能够应用shallowRefshallowReactive升高开销
  • 防止不必要的组件形象

平安:

  • 不应用不可信模板,例如应用用户输出拼接模板:template: <div> + userProvidedString + </div>
  • 防止应用v-html:url:style等,防止htmlurl、款式等注入

vue 中应用了哪些设计模式

1.工厂模式 - 传入参数即可创立实例

虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode

2.单例模式 - 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉

3.公布-订阅模式 (vue 事件机制)

4.观察者模式 (响应式数据原理)

5.装璜模式: (@装璜器的用法)

6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略