前端怎么避免写出烂代码

180 阅读14分钟

综述

本文总结了前端工程师常见的代码编写问题,给出可维护性角度上会产生的影响,并提供修改建议。

下列所有的规则都是用 Lint 无法检测或者很难检测的,需要通过 CodeReview 来发现。

本人对于 Vue 使用不够深入,所以框架方面只包含了一些 React 相关的使用问题。

命名

规则:除非在小于 5 行的函数里,否则不要使用单字命名变量

说明:含义不清晰,不能做到「望文生义」

BadCode

var l = data.length;

GoodCode

// 闭包只有一行代码,可以使用单字变量
data.map(d => d.length)

规则:不要使用名词加数字的命名方法

说明:含义不清晰,不能做到「望文生义」

BadCode

var obj = {};
var obj2 = {...obj, key: 1};

GoodCode

var obj = {};
var objWithKey = {...obj, key: 1};

规则:应该且只有方法和函数以动词开头

此处动词没有包含时态 变量名应该是名词或者名词短语。

例外:

  • 回调函数
  • 生命周期函数
  • 框架级别函数
  • getter/setter 说明:
  • 函数的命名需要体现内部工作
  • 值变量以动词命名容易让人误解为是一个匿名函数

BadCode

// 以名词开头,看不明白什么有什么功能
function option() {}

// 时态不对
function updatedTime() {}

GoodCode

function selectOption() {}
function updateTime() {}

规则:避免使用拼音或者缩写命名。

例外:

  • 专有名词:weixin/POI
  • 传统约定:i/j/k 表示循环索引 说明:含义不清晰,不能做到「望文生义」

BadCode

var uo = function updateOrder(){}
var as = [].slice;
var ex = Object.extends;
var nu = number

GoodCode

// weixin/wx 是专有名词
var weixinUser = {};
var wx = weixin;
// POI 是专有名词
var poi = {};

规则:名称长短应与其作用域大小相对应。

例外:专有 API,如 alert 说明:在上层作用域下的代码,会被更多函数使用到。其名称应该尽量长或者通用,以保证能够搜索到。

BadCode

// 在全局变量上定义了一个 item 变量,但是很难从命名上理解其作用是什么。
window.item = {}

GoodCode

window.primaryProductItem = {};

规则:不要在变量/函数尾部加符号、数字

说明:变量中加符号,往往是为了约定其优先级或者作用域。符号应该在变量名前面。

BadCode

function getDot_(){}
function privateFn$$ (){}

GoodCode

function _getDot() {}
function $$privateFn() {}

规则:实例名称要和类名相关

说明:类作为实例的所属,其名称表达的含义要一脉相承

BadCode

class Person() {}
var dog = new Person(); // dog is a Person ?

GoodCode

class Person() {}
var jack = new Person();

规则:避免直白的中英文翻译

说明:粗暴的翻译,更容易造成误解,还不如写拼音

BadCode

// 渲染「页面顶部的执行人」
// 还是渲染「执行砍头的人」?
function renderHeadExecutantPeople(){}

GoodCode

function renderHeader() {}

规则:概念的命名要一以贯之

说明:避免通一个概念在不同的代码用多种不同的单词描述。

BadCode

// 远程请求这个概念,先后用了 get/fetch/query
// 三个名词去描述
function getUserInfo() {}
function fetchProductInfo() {}
function queryPayment() {}

GoodCode

// 统一用 get 描述
// 阅读代码的人能体会到其中的共性
function getUserInfo() {}
function getProductInfo() {}
function getPayment() {}

规则:别用双关语

例外:专有词 说明:双关语容易引起歧义

BadCode

// 订单类型,还是排序类型?
var orderType

GoodCode

var sortType

规则:命名需要和实现一致

说明:命名往往是实现的隐喻,如果存在差异则会让阅读者看不懂代码。

BadCode

// empty 的命名含义和实现截然相反
// (我真的见过这种代码)
function getProduct(id) {
	axios.delete('/product', {id}); 
}

GoodCode

function deleteProduct(id) {
	axios.delete('/product', {id}); 
}

规则:对于布尔值的命名,需要默认其为「真」

说明:布尔变量的名称中,如果加上 「not」之类的否定词,则相当于做了一次了逻辑判断。

BadCode

const notEmpty = !!array.length;

GoodCode

const empty = !array.length;

函数

规则:长度不能超过 20 行

说明:代码太长说明做的事情不够专一,同时也会让阅读变得很困难。

规则:Don't repeat yourself

说明:同样功能的代码不要重复三次

规则:每个函数只做一件事情,并做好这件事

说明:代码里的逻辑分支要尽量少,只做一件事情,并且要处理好边界和异常情况。

规则:尽量减少函数的参数,包括 opitons、config 等参数

说明:函数的输入越多,往往就代表功能约复杂

规则:注释出来项目/业务的坑

说明:对于比较奇怪的业务逻辑,或者因为系统、接口原因而写的比较奇怪的逻辑。要通过注释标注出来

BadCode

framework.doSomeThing();
framework.reset(); // 阅读者内心 OS:这里为啥要做一次 reset?
framework.continueSomeThing();

GoodCode

framework.doSomeThing();
// framework 有个 bug,这里必须要做一次 rest 附链接: http://github.com/issuse/***
framework.reset(); 
framework.continueSomeThing();

规则:函数要尽量「纯」没有副作用

说明:纯函数比较好测试,逻辑也比较清晰,可以放心的引入和删除。

BadCode

let status;
function method() {
  if (status) { ... } 
}

GoodCode

function method(status) {
  if (status) { ... } 
}

规则:函数最好不要修改参数内的数据

说明:修改参数会导致函数的作用变得不可预测

BadCode

function updateObj(obj, value) {
  obj.key = value;
  return obj;
}

GoodCode

function updateObj(obj, value) {
  return {...obj, key: value};
}

规则:除非是 class 的方法,否则不要访问 this

说明:this 的指向经常不固定,会导致代码难以理解。如果调用方不熟悉的话,很容易引起 Bug。

BadCode

function method() {
	console.log(this.value); 
}

method() // 报错
var obj = { method, value: 1}
obj.method() // 输出 1

GoodCode

function method(value) {
	console.log(value); 
}

规则:处理错误

说明:错误也是一种逻辑分支,如果不处理的话,代码就不够健壮。前端代码处理错误的方式一般为提示用户有异常发生。如果错误不影响业务流程,则写入日志里并上报。

BadCode

function method(data) {
	try { return JSON.parse(data) }
  catch (e) {}
}

GoodCode

function method(data) {
	try { return JSON.parse(data) }
  catch (e) {
  	alert('数据处理失败')
  }
}

数据

规则:不要有 Magic Number

说明:magic number 是指直接在代码中硬编码的数字,往往具有一些业务含义。

这样会导致:

  • 数字的意义难以理解
  • 数值要改动时,要改很多地方

BadCode

if (status === 1) {
	...
} else if (type === 4) {
  ...
}

GoodCode

enum Status {
	Closed 
}
enum Type {
	Array 
}

if (status === Status.Closed) {
	...
} else if (type === Type.Array) {
  ...
}

规则:不管是 react state 还是 vue data 存放的业务数据都要具备原子性。

说明:原子性意味着独立,且不可分割。其它属性都由原子业务属性推导、计算而来,这样能保证状态的一致。

BadCode

// 当 status 为 open 的时候展示弹窗
// 其它状态则隐藏弹窗
{
	data() {
    return {
    	showAlert: false,
      status: 'closed',
    }
  },
  onStatusChange() {
  	if (status === 'open') {
    	this.showAlert = true;
    } else {
    	this.showAlert = false; 
    }
  }
}

GoodCode

// showAlert 为非原子的状态
// 其状态可以由 status 推导而来
{
	data() {
    return {
      status: 'closed',
    }
  },
  computed: {
  	showAlert() {
    	return this.status === 'open';
    }
  }
}

规则:对于 react state 和 vue data,应当区分业务状态和 UI 状态

说明:

  • 状态和 UI 存储在一起,有时候传给后端的数据里会夹杂着没有必要的 UI 状态。
  • 业务代码和 UI 代码耦合在一起,业务代码没法复用。

BadCode

// 在一个列表中,用户可以对数据做多选
// 然后删除他们
class extends React.Component {
 	async componentDidMount() {
  	const listData = getData();
    this.setState({ listData })
  }
  
  check = (item) => {
    const listData = this.state.listData.map(i => {
    	if (i === item) {
        return {...item, checked: true}
      }
      return i;
    });
    
    this.setState({ listData });
  }
  
  delete() {
    // 返回给后端的数据结构,会多出一个 checked 字段
    deleteItems(this.state.listData.filter(i => i.checked));
  }
  
	render() {
    const list = this.state.listData.map(item => {
      const className = ['item'];
      if (item.checked) className.push('active');
    	return <label
      	className={className}
      	onClick={() => this.check(item)}
      >{item.naem}</label>;
    });
    
    return <>
      {list}
    	<button onClick={this.delete}>delete</button>
    </>
  }
}

GoodCode

// 在一个列表中,用户可以对数据做多选
// 然后删除他们
class extends React.Component {
 	async componentDidMount() {
  	const listData = getData();
    // 使用独立的 selected 来保存 UI 状态
    this.setState({ listData, selected: [] })
  }
  
  check = (item) => {
    let { selected } = this.state;
    selected = selected.findOrInsert(s => s.id, item);
    this.setState({ selected });
  }
  
  delete() {
    const { selected, listData } = this.state;
    deleteItems(listData.filter(i => selected.includes(i.id))));
  }
  
	render() {
   	const { selected, listData } = this.state;
    const list = listData.map(item => {
      const className = ['item'];
      if (selected.includes(item.id)) className.push('active');
    	return <label
      	className={className}
      	onClick={() => this.check(item)}
      >{item.naem}</label>;
    });
    
    return <>
      {list}
    	<button onClick={this.delete}>delete</button>
    </>
  }
}

规则:对于 react 应用,避免在 render 的时候修改状态

说明:react 的 render 应该是纯函数,在 render 里运行 setState 会导致重复渲染,或者死循环。

BadCode

// 如果 type 为 http 的话,则自动转换为 https
class extends React.Component {
  render() {
  	const { type } = this.state;
    if (type === 'http') {
    	this.setState({ type: 'https'}) 
    }
    return <label>{type}</label>;
  }
}

GoodCode

// 如果 type 为 http 的话,则自动转换为 https
class extends React.Component {
  get type() {
    const { type } = this.state;
    if (type === 'http') return 'https';
    return type;
  }
  render() {
  	const type = this.type;
    return <label>{type}</label>;
  }
}

规则:对于双向绑定应用,避免数据循环依赖。

说明:

  • 循环依赖轻则导致页面相应慢,重则导致出现脏数据。
  • 避免循环依赖的前提是理清业务逻辑,搞清楚数据之间的依赖关系。
  • 循环依赖也是双向绑定技术的诟病之一。

BadCode

// foo 和 bar 互相依赖,导致了死循环
{
	data() {
  	return {
    	foo: 1,
    }; 
  },
  computed: {
    bar() {
    	return this.foo + 1;
    }
  },
  watch() {
    bar() {
    	this.foo = this.bar + 1;
    },
  }
}

规则:访问数据时,需要考虑边界情况和 JS 弱类型的特性。

说明:比如用双等号做判断

BadCode

const foo = '0';
const bar = 0

// 做数据判断时不能用双等号
foo == bar // true
foo ? 1 : 2 // 1
bar ? 1 : 2 // 2

// 仅通过变量有没有 length 来判断是否为数组
if(obj.length) {
	obj.forEach(...) 
}

GoodCode

const foo = '0';
const bar = 0

foo === bar // false

if (Array.isArray(obj)) {
 	obj.forEach(...) 
}

规则:不要在遍历数组的同时,改变数组数据

说明:这样做会导致数据的异常。如果需要做这种操作,最好使用数组函数,或者操作拷贝数据。

BadCode

const array = [1,2,3,4,5,6,7,8,9,10];

// 删除数组中的偶数
for (var i = 0; i < array.length; i++) {
	if (array[i] % 2 == 0) array.splice(i);
}
// array 变成了 [1]

GoodCode

const array = [1,2,3,4,5,6,7,8,9,10];
array.filter(a => !(a % 2))

API

规则:对 setTimeout 调用时,传递的时间参数必须有意义。

说明: 大多数场景下,setTimeout 后面传递一个时间是为了先执行后续的 A 代码,再延后执行代码闭包里的 B 代码,如右边示例代码。

但如果随着业务迭代,A 被改成异步,或者执行时间很长的话。之前做的延迟执行的防御措施就时效了,也许反而 B 会比 A 先执行。

BadCode

// 代码的本来意图是让 B 延后执行
setTimeout(() => {
  B();
}, 1000);
A();

// 代码的意图是让 render 在下一帧执行
// 但是不同设备,一帧时间是不固定的
setTimeout(() => {
  render()
}, 16);

GoodCode

// A 函数内要想办法使用 Promise 串接起来
await A();
b();

// 使用系统提供的 API 执行动画帧
requestAnimationFrame(() => {
 render(); 
});

规则:不要使用陈旧的 API

说明:陈旧的 API 往往有很多问题,比如安全、性能、不易读等。

BadCode

// 判断是否为数组
Object.prototype.toString.call(array) === "[object Array]" 
// 查找数组里第一个偶数
for (var i = 0; i < array.length; i++) {
	if (array[i] % 2 === 0) return array[i]; 
}
// 遍历对象的 key
for (var key in obj) {
	console.log(key); 
}
// 判断字符串/数组是否包含
'some text'.indexOf('some') >= 0
// 去除首位空格
' some text '.replace(/(^\s+|\s+$)/g, '')
// 新建对象/数组
const array = new Array();
const obj = new Object();

GoodCode

Array.isArray(array)

array.find(a => a % 2 === 0);

Object.keys(obj).forEach(console.log)

'some text'.includes('some')

' some text '.trim()
const array = [];
const obj = {};

规则:对于 99.9% 的场景,你都不需要使用 React ref

说明: React Ref 一般是用来处理和原生 DOM 交互的场景,比如 canvas。

大部分对于 React ref 的使用都是错误的,大多都拿来用来控制子元素。这种场景我们更推荐用数据流(redux,mobx)或者用状态提升去做。

React 官方有对「状态提升」的描述 react.docschina.org/docs/liftin…

BadCode

class List extends React.Component {
	async refresh() {
  	this.setState({
      items: getItems(),
    });
  }
  
  render() {
  	return this.state.items.map(i => <label>{i}</label>); 
  }
}
class extends React.Component {
  onRefresh = () => {
    // 用 ref 去调用子元素的方法
    this.list.refresh();
  }
	render() {
    return <>
      <List ref={l => this.list = l}></List>
    	<button onClick={this.onRefresh}/>
    </>;
  	
  }
}

GoodCode

class List extends React.Component {
  render() {
  	return this.props.items.map(i => <label>{i}</label>); 
  }
}
class extends React.Component {
 // 把数据状态提升到父组件做操作                              
  refresh = async () => {
  	this.setState({
      items: getItems(),
    });
  }

	render() {
    return <>
      <List items{this.state.items}></List>
    	<button onClick={this.refresh}/>
    </>;
  	
  }
}

规则:不要用字符串拼接 url

说明: 字符串拼接 url 需要处理 encode 或者 decode 的情况,还有对于 ?和 # 的判断不对的话,很容易造成漏洞或者 Bug。

目前浏览器和 Node 都已经提供了标准的 URL 解析方法。

developer.mozilla.org/en-US/docs/…

BadCode

// 这段代码既没有对 key、value 做 encode
// 也没有考虑 url 中 # 出现的情况
const url = location.href;
if (url.indexOf('?') >= 0) {
	return  url + key + '=' + value;
} else {
  return  url + '?' + key + '=' + value;
}

GoodCode

// 使用标准的 URL 解析,风险会降低很多
const url = new URL(urlStr);
url.searchParams.set(key, value);
return url.toString();

规则:设计缓存的时候要考虑何时删除、何时更新

没有清除缓存的机制,则会带来两个问题:

  1. 内存泄漏
  2. 数据不一致

BadCode

const cache = {};

// 这段代码一直在往 cache 添加内容,没有做清理操作
async function getData(id) {
  if (cache[id]) return cache[id];
  const data = await Get(`/user/${id}`);
  cache[id] = data;
  return data;
}

async function updateData(id, data) {
	const result = await Post(`/user/${id}`, data);
  return result;
}

const id;
const oldValue = getData(id)
update(id, null);
const newValue = getData(id);
// 上一步把 id 对应的值更新为 null 了,这时两个值应该不相等的
// 但结果却是两个值相等
oldValue === newValue;

GoodCode

const cache = {};

// 这段代码一直在往 cache 添加内容,没有做清理操作
async function getData(id) {
  if (cache[id]) return cache[id];
  const data = await Get(`/user/${id}`);
  cache[id] = data;
  return data;
}

async function updateData(id, data) {
  delete cache[id]; // 在更新数据时清除缓存,保证数据一致性
	const result = await Post(`/user/${id}`, data);
  return result;
}

const id;
const oldValue = getData(id)
update(id, null);
const newValue = getData(id);
oldValue === newValue;

规则:绑定事件的同时要考虑何时解绑

没有解绑事件,会导致程序出现非常多奇怪的行为,如:事件会把引用旧逻辑,触发的时候会执行多次。同时还会有内存泄漏的问题。

BadCode

class PageA extends React.Component {
  name = 'PageA';
  // PageA 没有解绑事件的逻辑
 	componentDidMount() {
  	window.addEventListener('mouseDown', this.mouseDown); 
  }
  
  mouseDown = () => {
    console.log(this.name);
  }
  
  render() {
   	return <a href="/b">PageB</a> 
  }
}

class PageB extends React.Component {
  name = 'PageB';
 	componentDidMount() {
  	window.addEventListener('mouseDown', this.mouseDown.bind(this)); 
  }
  
  componentWillUnmount() {
    // bind 每次返回的函数都不同,下面的语句解绑无效
    window.removeEventListener('mouseDown', this.mouseDown.bind(this)); 
  }
  
  mouseDown() {
    console.log(this.name);
  }
  
  render() {
   	return <a href="/a">PageA</a> 
  }
}

// 用户在  AB 页面之间来回切换页面并点击
// 会发现 log 次数会随着切换次数的增多而增多
render(
<Router>
  <Route path="/a" component={PageA}>
  <Route path="/b" component={PageB}>
</Router>
);

GoodCode

class PageA extends React.Component {
  name = 'PageA';

 	componentDidMount() {
  	window.addEventListener('mouseDown', this.mouseDown); 
  }

	componentWillUnmount() {
    window.removeEventListener('mouseDown', this.mouseDown); 
  }
  
  mouseDown = () => {
    console.log(1234);
  }
  
  render() {
   	return <a href="/b">PageB</a> 
  }
}

class PageB extends React.Component {
  name = 'PageB';
  
	constructor() {
    // 提前做 bind 准备好回调函数
  	this.mouseDown = this.mouseDown.bind(this);  
  }

 	componentDidMount() {
  	window.addEventListener('mouseDown', this.mouseDown); 
  }
  
  componentWillUnmount() {
    window.removeEventListener('mouseDown', this.mouseDown); 
  }
  
  mouseDown() {
    console.log(1234);
  }
  
  render() {
   	return <a href="/a">PageA</a> 
  }
}

render(
<Router>
  <Route path="/a" component={PageA}>
  <Route path="/b" component={PageB}>
</Router>
);

规则:使用 React Hook 时所使用的变量都要加到依赖内

  1. 所有函数内使用到的外部变量都要加到依赖里。
  2. 依赖的粒度要细,不要将必然会变化的变量加入到依赖里。

如果没有理清依赖关系,则会出现下列问题:

  1. useEffect 非常容易写出死循环
  2. 滥用 useRef 导致状态不同步。
  3. 依赖中遗漏变量的话,会导致一些奇怪的 bug,比如点击后页面不生效。
  4. 如果将必然变化的变量放入依赖里,则会导致性能变差。

BadCode

const Component = (props) => {
  const onClick = React.useCallback(() => {
    alert(props.title);
  }, [props]);
  // 每次渲染的时候 onClick 的值都会变
  // 导致下面的 Card 组件每次都会重新渲染
  return <Card onClick={onClick}>Show Title</Card>;
}

GoodCode

const Component = (props) => {
  const onClick = React.useCallback(() => {
    alert(props.title);
  }, [props.title]); // 将依赖变为 props.title 可以有效提升性能

  return <Card onClick={onClick}>Show Title</Card>;
}

逻辑

规则:判真不判假

说明: 我们应该期望 if 条件内是个「真」值,而不是一个「假」值。

第二种情况会导致代码不易理解。

解决办法参考 布尔逻辑

BadCode

// if 条件内期望的是一个「假」值
if (!(status !== Closed) { ... }
if (!(status !== Closed || type !== Array)) { ...} 

GoodCode

if (status === Closed) { ... }
if (status === Closed && type === Array) { ... }

规则:if 条件中,不易出现超过 3 个逻辑操作符。

例外:if 条件里可以被 「且」(&&)逻辑拆分成多个子条件 说明:复杂的条件判断会让代码不易理解,逻辑上有漏洞的话容易引起 Bug。 解决办法:声明中间变量

BadCode

if (srcElem != dropElem && (srcElem.nextSibling || srcElem.nextElementSibling) != dropElem) {...}
if (selectedItem || (selectedEmployee && selectedEmployee.empId && selectedEmployee) || employee) { ... }

GoodCode

const nextSibling = srcElem.nextSibling || srcElem.nextElementSibling
if (srcElem != dropElem &&  nextSibling != dropElem ) {
  ...
}
  
// 复杂的逻辑判断可以通过 && 做拆分
if (
     !Array.isArray(cur)
  && cur != null
  && typeof src[key] === 'object'
  && typeof cur === 'object'
) { ... }

规则:不要用嵌套的三元表达式

说明: 人们阅读嵌套三元表达式时,容易混淆语法的优先级,进而导致理解错代码的含义。

对于这种情况,建议改成 if else。

如果是在 react render 里,则建议独立成函数。

BadCode

function render(props) {
    const value = props.value;
    return <>
        {value < 10 ? value > 0 ? value : 200 - value : 100 - value}
    </>;
}

GoodCode

function getValue(value) {
  if (value < 10) {
    if (value > 0) return value;
    return 200 - value;
  }
  return 100 - value;
}

function render(props) {
    const value = props.value;
    return <>
        {getValue(value)}
    </>;
}

规则:if 条件逻辑嵌套不要超过三层

说明:过深的嵌套会导致理解困难。 解决办法:合并判断条件,或者独立成函数。

BadCode

if (status = Opened) {
    if (type = 'array') {
  	if (code = Success) {
            doSomething();
        }
    }
}

GoodCode

if (status = Opened && type = 'array' &&code = Success) {
    doSomething();
}

规则:做条件判断时,需要考虑被判断对象为 undefined 、0、null 时,是否有特殊含义。

js 的逻辑操作符 「&&」、「||」、「!」、「if」都会将 undefined 、0、null 当作 false 处理,如果 undefined 和 0 具有不同含义的话,则会导致逻辑错误。

BadCode

enum Type {
  Dish = 0,
  Combo,
}

const type: Type | undefined = getType();

if (!type) {
  // 此处 type 为 undefined 或者 0 都会进入
}

GoodCode

enum Type {
  Dish = 0,
  Combo,
}

const type: Type | undefined = getType();

if (typeof type === 'undefined') {
}

CSS

规则:不要滥用百分比单位

百分比单位只能设置以下几个值或其倍数

  1. 100%
  2. 25% (1/4)
  3. 20%(1/5)
  4. 16.66666%(1/6)

一般设置百分比的目的是为了按比例分配。 代码中大部分使用奇怪的比例,本意都是为了解决自适应间距的问题,而这类问题用 flex 解决会更好。

BadCode

.parent{
  .child1{
    width: 10%;
  }
  .child2{
  	width: 20%; 
  }
  .child3{
   	width: 70%; 
  }
}

GoodCode

# 复杂的比例布局用 Flex 实现 #
.parent{
  display: flex;
	.child1{
    flex: 1;
  }
  .child2{
  	flex: 2;
  }
  .child3{
   	flex: 7;
  }
}

规则:所有非写死文案的场景,都要考虑文本内容超长后的情况。

在项目实践中我们经常会遇到后端传入的数据、用户输入的数据在页面中展示时,文本超长导致的 Bug。所以对于非前端写死文案的场景,都要考虑文字超长后如何处理。

典型的处理方式:

  1. Text ellipsis,文本省略,tooltip 给出全部文本
  2. Overflow: scroll, 出滚动条
  3. 文本折行
  4. 和产品确认内容长度是否有限制,基于这个限制做宽度适配。

其他非典型场景,则需要和 UED 协商,对布局做调整

BadCase

<table>
    <th>
        <td>产品名称</td>
    </th>
    <tr>
        <td class="name">超级长的字符串...........</td>
    </tr>
</table>
.name{
    # 表格内的字段没有考虑到数字较长的情况,根据经验值给出了一个宽度限制 #
    # 如果文本超长,则会导致显示不正常。 #
    width: 100px;
    display: block;
    white-space: nowrap;
}

GoodCase

<table>
    <th>
        <td>产品名称</td>
    </th>
    <tr>
        <td class="name" title="超级长的字符串...........">超级长的字符串...........</td>
    </tr>
</table>
.name{
    # 在不改变宽度的情况下,PC 上可以允许文本超长省略,同时用 title 展示全部内容。 #
    width: 100px;
    display: block;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}