草庐IT

javascript - Chrome requestAnimationFrame 问题

coder 2023-08-01 原文

相关主题:requestAnimationFrame garbage collection

我一直致力于在我为触摸设备构建的小部件中实现流畅的动画,我发现可以帮助我实现此目的的工具之一是 Chrome 内存时间轴屏幕。

评估我在 rAF 循环中的内存消耗对我有点帮助,但我对此时在 Chrome 30 中观察到的行为的几个方面感到困扰。

当最初进入我的页面时,我看到了这个运行 rAF 循环的页面。

看起来不错。如果我完成了我的工作并消除了我的内部循环中的对象分配,就不会有锯齿。这是与链接主题一致的行为,也就是说,每当您使用 rAF 时,Chrome 都会有一个内置的泄漏。 (哎呀!)

当我开始在页面中做各种事情时,它会变得更有趣。

我并没有真正做任何不同的事情,只是临时添加了两个元素,这些元素让 CSS3 3D 变换样式应用于几帧,然后我停止与它们交互。

我们在这里看到的是 Chrome 报告突然间每次 rAF 触发(16 毫秒)都会导致 Animation Frame Fired x 3

这种重复及其重复的速率单调增加,直到页面刷新。

您已经可以在第二个屏幕截图中看到,在从 Animation Frame Fired 初始跳跃到 Animation Frame Fired x 3 之后,锯齿斜率急剧增加。

不久之后它跳到了 x 21:

看起来我的代码运行了一大堆额外的时间,但所有额外的多次运行只是浪费了热量,丢弃了计算。

当我拍摄第三个屏幕截图时,我的 Macbook 发热非常严重。不久之后,在我能够将时间线刷到结束位(大约 8 分钟)以查看 x 数字增加到什么之前,检查器窗口变得完全没有响应,并提示我我的页面变得无响应,必须终止。

这是在页面中运行的全部代码:

// ============================================================================
// Copyright (c) 2013 Steven Lu

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ============================================================================

// This is meant to be a true velocity verlet integrator, which means sending
// in for the force and torque a function (not a value). If the forces provided
// are evaluated at the current time step then I think we are left with plain
// old Euler integration.  This is a 3 DOF integrator that is meant for use
// with 2D rigid bodies, but it should be equally useful for modeling 3d point
// dynamics.

// this attempts to minimize memory waste by operating on state in-place.

function vel_verlet_3(state, acc, dt) {
  var x = state[0],
      y = state[1],
      z = state[2],
      vx = state[3],
      vy = state[4],
      vz = state[5],
      ax = state[6],
      ay = state[7],
      az = state[8],
      x1 = x + vx * dt + 0.5 * ax * dt * dt,
      y1 = y + vy * dt + 0.5 * ay * dt * dt,
      z1 = z + vz * dt + 0.5 * az * dt * dt,  // eqn 1
      a1 = acc(x1, y1, z1),
      ax1 = a1[0],
      ay1 = a1[1],
      az1 = a1[2];
  state[0] = x1;
  state[1] = y1;
  state[2] = z1;
  state[3] = vx + 0.5 * (ax + ax1) * dt,
  state[4] = vy + 0.5 * (ay + ay1) * dt,
  state[5] = vz + 0.5 * (az + az1) * dt; // eqn 2
  state[6] = ax1;
  state[7] = ay1;
  state[8] = az1;
}

// velocity indepedent acc --- shit this is gonna need to change soon
var acc = function(x, y, z) {
  return [0,0,0];
};
$("#lock").click(function() {
  var values = [Number($('#ax').val()), Number($('#ay').val()), Number($('#az').val())];
  acc = function() {
    return values;
  };
});

// Obtain the sin and cos from an angle.
// Allocate nothing.
function getRotation(angle, cs) {
  cs[0] = Math.cos(angle);
  cs[1] = Math.sin(angle);
}

// Provide the localpoint as [x,y].
// Allocate nothing.
function global(bodystate, localpoint, returnpoint) {
  getRotation(bodystate[2], returnpoint);
  // now returnpoint contains cosine+sine of angle.
  var px = bodystate[0], py = bodystate[1];
  var x = localpoint[0], y = localpoint[1];
  // console.log('global():', cs, [px, py], localpoint, 'with', [x,y]);
  // [ c -s px ]   [x]
  // [ s  c py ] * [y]
  //               [1]
  var c = returnpoint[0];
  var s = returnpoint[1];
  returnpoint[0] = c * x - s * y + px;
  returnpoint[1] = s * x + c * y + py;
}

function local(bodystate, globalpoint, returnpoint) {
  getRotation(bodystate[2], returnpoint);
  // now returnpoint contains cosine+sine of angle
  var px = bodystate[0], py = bodystate[1];
  var x = globalpoint[0], y = globalpoint[1];
  // console.log('local():', cs, [px, py], globalpoint, 'with', [x,y]);
  // [  c s ]   [x - px]
  // [ -s c ] * [y - py]
  var xx = x - px, yy = y - py;
  var c = returnpoint[0], s = returnpoint[1];
  returnpoint[0] = c * xx + s * yy;
  returnpoint[1] = -s * xx + c * yy;
}

var cumulativeOffset = function(element) {
  var top = 0, left = 0;
  do {
    top += element.offsetTop || 0;
    left += element.offsetLeft || 0;
    element = element.offsetParent;
  } while (element);
  return {
    top: top,
    left: left
  };
};

// helper to create/assign position debugger (handles a single point)
// offset here is a boundingclientrect offset and needs window.scrollXY correction
var hasDPOffsetRun = false;
var dpoff = false;
function debugPoint(position, id, color, offset) {
  if (offset) {
    position[0] += offset.left;
    position[1] += offset.top;
  }
  // if (position[0] >= 0) { console.log('debugPoint:', id, color, position); }
  var element = $('#point' + id);
  if (!element.length) {
    element = $('<div></div>')
    .attr('id', 'point' + id)
    .css({
          pointerEvents: 'none',
          position: 'absolute',
          backgroundColor: color,
          border: '#fff 1px solid',
          top: -2,
          left: -2,
          width: 2,
          height: 2,
          borderRadius: 300,
          boxShadow: '0 0 6px 0 ' + color
        });
    $('body').append(
        $('<div></div>')
        .addClass('debugpointcontainer')
        .css({
          position: 'absolute',
          top: 0,
          left: 0
        })
      .append(element)
    );
    if (!hasDPOffsetRun) {
      // determine the offset of the body-appended absolute element. body's margin
      // is the primary offender that tends to throw a wrench into our shit.
      var dpoffset = $('.debugpointcontainer')[0].getBoundingClientRect();
      dpoff = [dpoffset.left + window.scrollX, dpoffset.top + window.scrollY];
      hasDPOffsetRun = true;
    }
  }
  if (dpoff) {
    position[0] -= dpoff[0];
    position[1] -= dpoff[1];
  }
  // set position
  element[0].style.webkitTransform = 'translate3d(' + position[0] + 'px,' + position[1] + 'px,0)';
}

var elements_tracked = [];

/*
var globaleventhandler = function(event) {
  var t = event.target;
  if (false) { // t is a child of a tracked element...

  }
};

// when the library is loaded the global event handler for GRAB is not
// installed. It is lazily installed when GRAB_global is first called, and so
// if you only ever call GRAB then the document does not get any handlers
// attached to it.  This will remain unimplemented as it's not clear what the
// semantics for defining behavior are. It's much more straightforward to use
// the direct API
function GRAB_global(element, custom_behavior) {
  // this is the entry point that will initialize a grabbable element all state
  // for the element will be accessible through its __GRAB__ element through
  // the DOM, and the DOM is never accessed (other than through initial
  // assignment) by the code.

  // event handlers are attached to the document, so use GRAB_direct if your
  // webpage relies on preventing event bubbling.
  if (elements_tracked.indexOf(element) !== -1) {
    console.log('You tried to call GRAB() on an element more than once.',
                element, 'existing elements:', elements_tracked);
  }
  elements_tracked.push(element);
  if (elements_tracked.length === 1) { // this is the initial call
    document.addEventListener('touchstart', globaleventhandler, true);
    document.addEventListener('mousedown', globaleventhandler, true);
  }
}

// cleanup function cleans everything up, returning behavior to normal.
// may provide a boolean true argument to indicate that you want the CSS 3D
// transform value to be cleared
function GRAB_global_remove(cleartransform) {
  document.removeEventListener('touchstart', globaleventhandler, true);
  document.removeEventListener('mousedown', globaleventhandler, true);
}

*/

var mousedownelement = false;
var stop = false;
// there is only one mouse, and the only time when we need to handle release
// of pointer is when the one mouse is let go somewhere far away.
function GRAB(element, onfinish, center_of_mass) {
  // This version directly assigns the event handlers to the element
  // it is less efficient but more "portable" and self-contained, and also
  // potentially more friendly by using a regular event handler rather than
  // a capture event handler, so that you can customize the grabbing behavior
  // better and also more easily define it per element
  var offset = center_of_mass;
  var pageOffset = cumulativeOffset(element);
  var bcrOffset = element.getBoundingClientRect();
  bcrOffset = {
    left: bcrOffset.left + window.scrollX,
    right: bcrOffset.right + window.scrollX,
    top: bcrOffset.top + window.scrollY,
    bottom: bcrOffset.bottom + window.scrollY
  };
  if (!offset) {
    offset = [element.offsetWidth / 2, element.offsetHeight / 2];
  }
  var model = {
    state: [0, 0, 0, 0, 0, 0, 0, 0, 0],
    offset: offset,
    pageoffset: bcrOffset // remember, these values are pre-window.scroll[XY]-corrected
  };
  element.__GRAB__ = model;
  var eventhandlertouchstart = function(event) {
    // set
    var et0 = event.touches[0];
    model.anchor = [0,0];
    local(model.state, [et0.pageX - bcrOffset.left - offset[0], et0.pageY - bcrOffset.top - offset[1]], model.anchor);
    debugPoint([et0.pageX, et0.pageY], 1, 'red');
    event.preventDefault();
    requestAnimationFrame(step);
  };
  var eventhandlermousedown = function(event) {
    console.log('todo: reject right clicks');
    // console.log('a', document.body.scrollLeft);
    // set
    // model.anchor = [event.offsetX - offset[0], event.offsetY - offset[1]];
    model.anchor = [0,0];
    var globalwithoffset = [event.pageX - bcrOffset.left - offset[0], event.pageY - bcrOffset.top - offset[1]];
    local(model.state, globalwithoffset, model.anchor);
    debugPoint([event.pageX, event.pageY], 1, 'red');
    mousedownelement = element;
    requestAnimationFrame(step);
  };
  var eventhandlertouchend = function(event) {
    // clear
    model.anchor = false;
    requestAnimationFrame(step);
  };
  element.addEventListener('touchstart', eventhandlertouchstart, false);
  element.addEventListener('mousedown', eventhandlermousedown, false);
  element.addEventListener('touchend', eventhandlertouchend, false);
  elements_tracked.push(element);
  // assign some favorable properties to grabbable element.
  element.style.webkitTouchCallout = 'none';
  element.style.webkitUserSelect = 'none';
  // TODO: figure out the proper values for these
  element.style.MozUserSelect = 'none';
  element.style.msUserSelect = 'none';
  element.style.MsUserSelect = 'none';
}
document.addEventListener('mouseup', function() {
  if (mousedownelement) {
    mousedownelement.__GRAB__.anchor = false;
    mousedownelement = false;
    requestAnimationFrame(step);
  }
}, false);

function GRAB_remove(element, cleartransform) {}
// unimpld
function GRAB_remove_all(cleartransform) {}

GRAB($('#content2')[0]);

(function() {
  var requestAnimationFrame = window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      window.requestAnimationFrame;
  window.requestAnimationFrame = requestAnimationFrame;
})();

var now = function() { return window.performance ? performance.now() : Date.now(); };
var lasttime = 0;
var abs = Math.abs;
var dt = 0;
var scratch0 = [0,0];
var scratch1 = [0,0]; // memory pool
var step = function(time) {
  dt = (time - lasttime) * 0.001;
  if (time < 1e12) {
    // highres timer
  } else {
    // ms since unix epoch
    if (dt > 1e9) {
      dt = 0;
    }
  }
  // console.log('dt: ' + dt);
  lasttime = time;
  var foundnotstopped = false;
  for (var i = 0; i < elements_tracked.length; ++i) {
    var e = elements_tracked[i];
    var data = e.__GRAB__;
    if (data.anchor) {
      global(data.state, data.anchor, scratch0);
      scratch1[0] = scratch0[0] + data.offset[0];
      scratch1[1] = scratch0[1] + data.offset[1];
      //console.log("output of global", point);
      debugPoint(scratch1,
                 0, 'blue', data.pageoffset);
    } else {
      scratch1[0] = -1000;
      scratch1[1] = -1000;
      debugPoint(scratch1, 0, 'blue');
    }
    // timestep is dynamic and based on reported time. clamped to 100ms.
    if (dt > 0.3) {
      //console.log('clamped from ' + dt + ' @' + now());
      dt = 0.3;
    }
    vel_verlet_3(data.state, acc, dt);
    e.style.webkitTransform = 'translate3d(' + data.state[0] + 'px,' + data.state[1] + 'px,0)' +
        'rotateZ(' + data.state[2] + 'rad)';
  }
  requestAnimationFrame(step);
};

requestAnimationFrame(step);

为了完整起见,这里是测试页面 HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
    <meta http-equiv="pragma" content="no-cache" />
    <title>symplectic integrator test page</title>
    <script src="zepto.js"></script>
    <script src="d3.v3.js"></script>
    <style type='text/css'>
        body {
            position: relative;
            margin: 80px;
        }
        #content {
            width: 800px;
            height: 40px;
            display: inline-block;
            background: lightgreen;
            padding: 20px;
            margin: 30px;
            border: green dashed 1px;
        }
        #content2 {
            top: 200px;
            width: 600px;
            height: 200px;
            display: inline-block;
            background: lightblue;
            padding: 20px;
            margin: 30px;
            border: blue dashed 1px;
        }
    </style>
</head>
<body>
    <div id='scrolling-placeholder' style='background-color: #eee; height: 1000px;'></div>
    <label>dt:<input id='dt' type='number' step='0.001' value='0.016666666' /></label>
    <label>ax:<input id='ax' type='number' step='0.25' value='0' /></label>
    <label>ay:<input id='ay' type='number' step='0.25' value='0' /></label>
    <label>t:<input id='az' type='number' step='0.01' value='0' /></label>
    <button id='lock'>Set</button>
    <button id='zerof' onclick='$("#ax,#ay,#az").val(0);'>Zero forces</button>
    <button id='zerov'>Zero velocities</button>
    <div>
        <span id='content'>content</span>
        <span id='content2'>content2</span>
    </div>
    <div id='debuglog'></div>
    <script src="rb2.js"></script>
</body>
</html>

这应该可以满足任何“向我们展示代码”的请求。

现在我不会赌上我的生命,但我很确定我至少在以正确的方式使用 rAF 方面做得很好。我没有滥用任何东西,到现在为止,我已经改进了代码,使 Javascript 内存分配变得非常轻松。

所以,真的,Chrome 浏览器绝对没有理由接受这个并试图像火箭一样将我的笔记本电脑送入轨道。没有理由。

Safari 通常似乎处理得更好(它最终不会死),而且我会注意到 iOS 通常能够保持 200x600px 的 div 以 60fps 的速度平移和旋转。

但是,我承认我还没有看到 Chrome 真的像这样死去,除非我让它记录了内存时间线。

此时我有点摸不着头脑。这可能只是与此特定开发工具功能(据我所知,这是唯一一种)的一些无意的、不可预见的交互。

然后我尝试了一些新方法,至少可以帮助调查内存时间线额外回调触发的问题:

添加了这些行。

window.rafbuf = [];
var step = function(time) {
  window.rafbuf.push(time);

这基本上会注销我的 rAF 例程(step() 函数)被调用的所有时间。

当它正常运行时,它大约每 16.7 毫秒记录一次时间。

我明白了:

这清楚地表明它正在使用相同的时间输入参数重新运行 step() 至少 22 次,就像时间线试图告诉我的那样。

所以我敢告诉你,互联网,这是有意为之的行为。 :)

最佳答案

我认为您遇到了问题,因为您在每个 mousedownmouseup 事件上都调用了 requestAnimationFrame(step);。因为你的 step() 函数也(它应该)调用 requestAnimationFrame(step); 你基本上为每个 mousedown 开始新的“动画循环”和 mouseup 事件,因为您永远不会阻止它们,所以它们会累积。

我可以看到您还在代码末尾启动了“动画循环”。如果您想在鼠标事件上立即重绘,您应该将绘图移出 step() 函数并直接从鼠标事件处理程序调用它。

像这样:

function redraw() { 
  // drawing logic
}
function onmousedown() {
  // ...
  redraw()
}
function onmouseup() {
  // ...
  redraw()
}

function step() {
  redraw();
  requestAnimationFrame(step);
}
requestAnimationFrame(step);

关于javascript - Chrome requestAnimationFrame 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19395565/

有关javascript - Chrome requestAnimationFrame 问题的更多相关文章

  1. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

  2. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  3. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  4. ruby - Fast-stemmer 安装问题 - 2

    由于fast-stemmer的问题,我很难安装我想要的任何ruby​​gem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=

  5. ruby - 安装 Ruby 时遇到问题(无法下载资源 "readline--patch") - 2

    当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub

  6. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  7. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  8. 【高数】用拉格朗日中值定理解决极限问题 - 2

    首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有,  也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加

  9. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  10. git使用常见问题(提交代码,合并冲突) - 2

    文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g

随机推荐