金色小芝麻

vuePress-theme-reco 金色小芝麻    2021 - 2023
金色小芝麻 金色小芝麻

Choose mode

  • dark
  • auto
  • light
主页
分类
  • JavaScript
  • BUG复盘
  • SVG笔记
  • TypeScript
  • 个人总结
  • CSS笔记
  • 开发工具
  • 前端入门
  • Vue2.0
  • 性能优化
  • 架构学习
  • 每日一题
标签
时间轴
社交
  • 掘金 (opens new window)
author-avatar

金色小芝麻

83

文章

27

标签

主页
分类
  • JavaScript
  • BUG复盘
  • SVG笔记
  • TypeScript
  • 个人总结
  • CSS笔记
  • 开发工具
  • 前端入门
  • Vue2.0
  • 性能优化
  • 架构学习
  • 每日一题
标签
时间轴
社交
  • 掘金 (opens new window)
  • 架构学习

    • Performance性能分析与优化
    • lighthouse
    • 上传npm包
    • 从了解AST到开发Eslint插件

lighthouse

vuePress-theme-reco 金色小芝麻    2021 - 2023

lighthouse

金色小芝麻 2022-05-09

Lighthouse (opens new window) 是一个开源的自动化工具,用于改进网络应用的质量。

Lighthouse (opens new window) 报告分析了加载页面生命周期中的各种性能指标。

https://github.com/GoogleChrome/lighthouse

image-20220510170738983

# 1. 使用入门

您可以将其作为一个 Chrome 扩展程序运行,或从命令行运行。 您为 Lighthouse 提供一个您要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。[1]

# 1.1 使用方式

  • 作为 Chrome 扩展程序运行

    • Lighthouse 直接集成到 Chrome DevTools 的“Lighthouse”面板下。

    • 打开 Chrome DevTools,选择 Lighthouse 面板,然后点击“生成报告”。

  • 作为命令行工具运行

    安装

    npm install -g lighthouse
     #或者使用 yarn: 
    # yarn global add lighthouse
    

    运行

    # lighthouse <url> <options>
    lighthouse http://blog.shenjz.com.cn/
    
    # lighthouse http://blog.shenjz.com.cn/ --locale zh --quiet --chrome-flags="--headless"  --only-categories=performance  
    

# 2. 性能指标

Lighthouse 可以生成 JSON 或 HTML 格式的报告。Lighthouse查看器 (opens new window)

image-20220509161650744

# 2.1 FCP

First Contentful Paint(首次内容渲染) (opens new window) 表示浏览器渲染出第一个DOM内容的时间

  • First Paint(首次渲染) 表示了浏览器从开始请求网站到屏幕渲染第一个像素点的时间。(这个指标表示页面从发起请求到请求成功所花的时间。)
  • First Contentful Paint(首次内容渲染) (opens new window) 表示浏览器渲染出第一个内容的时间,这个内容可以是文本、图片或SVG元素等,不包括iframe内的任何元素和白色背景的canvas元素。(这个指标表示了页面从发起请求到渲染第一个 DOM 内容所花的时间。)

两者区别在于:(FP <= FCP)

FP 指的是第一个像素点渲染的时间点,比如说页面的背景色是灰色的,那么在显示灰色背景时就记录下了 FP 指标。但是此时 DOM 内容还没开始绘制,可能还需要等到其他的文件下载、解析等过程完整后才会开始 DOM 元素的渲染,比如说渲染出了一段文字,此时才会记录下 FCP 指标。**通常会用这两个指标来表示页面的白屏时间和首屏时间。**可以通过 performance.getEntriesByType('paint')来获取 FP 和 FCP 的值。

# 2.1.1 衡量标准

FCP

FCP 时间 (以秒为单位) 颜色编码
0–1.8 绿色(快速)
1.8–3 橙色(中等)
超过 3 红色(慢)

# 2.1.2 如何计算FP和FCP

  • Performance (opens new window)
  • document.readyState (opens new window)
  • PerformanceObserver (opens new window)
  • PerformanceObserver.observe (opens new window)
  • PerformanceEntry.entryType (opens new window)
  • PerformanceObserverEntryList (opens new window)

方法一:从 window 对象取

const performance = window.performance || window.msPerformance || window.webkitPerformance;
const performanceEntries = performance.getEntriesByType('paint') || [];
const firstPaints = {};
performanceEntries.forEach((entry) => {
  if (entry.name === 'first-paint') {
    console.log('first-paint', entry.startTime);
  } else if (entry.name === 'first-contentful-paint') {
    console.log('first-contentful-paint', entry.startTime);
  }
});

方法二:创建监听对象

(function (ready) {
 /**
  * document.readyState
  * + loading(正在加载): 表示文档还在加载中,即处于“正在加载”状态。
  * + interactive(可交互): 文档已被解析,"正在加载"状态结束,DOM元素可以被访问。但是诸如图像,样式表和框架之类的资源仍在加载。
  * + complete(完成): 文档和所有子资源已完成加载。表示 load 状态的事件即将被触发。
  */
  if (document.readyState === "complete" || document.readyState === "interactive") {
    ready();
  } else {
    document.addEventListener("readystatechange", function () {
      if (document.readyState === "complete") {
        ready();
      }
    });
  }
})(function perf () {
  var data = {
    FP: 0,
    FCP: 0
  };
  new PerformanceObserver(function (entryList) {
    var entries = entryList.getEntries() || [];
    entries.forEach(function (entry) {
      if (entry.name === "first-paint") {
        data.FP = entry.startTime;
        console.log("记录FP: " + data.FP);
      } else if (entry.name === "first-contentful-paint") {
        data.FCP = entry.startTime;
        console.log("记录FCP: " + data.FCP);
      }
    });
  }).observe({ type: "paint", buffered: true });
});

#

# 2.1.3 如何改进FCP

想缩短FCP的时间(或者说缩短页面的白屏时间FP、首屏时间FCP),就需要知道在这之前都干了什么 <Performance性能分析与优化>

  • 加快服务器响应速度
    • 升级服务器配置
    • 合理设置缓存
    • 优化数据库索引
  • 加大服务器带宽
  • 服务器开启gzip压缩
  • 开启服务器缓存(redis)
  • 避免重定向操作
  • 使用dns-prefetch进行DNS进行预解析
  • 采用域名分片技术突破同域6个TCP连接限制或者采用HTTP2
  • 使用CDN减少网络跳转
  • 压缩JS和CSS和图片等资源
    • TerserWebpackPlugin (opens new window)
    • purgecss-webpack-plugin (opens new window)
  • 减少HTTP请求,合并JS和CSS,合理内嵌JS和CSS

# 2.2 SI

Speed Index(速度指数) (opens new window) 表明了网页内容的可见填充速度

  • 速度指数衡量页面加载期间内容的视觉显示速度

什么是首屏展现平均值 (Speed Index) 及如何做相应优化措施 (opens new window)

# 2.2.1 衡量标准

SI

速度指数 (秒) 颜色编码
0–3.4 绿色(快速)
3.4–5.8 橙色(中等)
超过 5.8 红色(慢)

# 2.2.2 如何改进SI

# 2.2.2.1 最小化主线程工作

  • 脚本
    • 优化第三方的JS脚本 (opens new window)
    • 对输入进行防抖处理 (opens new window)
    • 使用web workers (opens new window)
  • 样式和布局
    • 缩小样式计算的范围并降低其复杂性 (opens new window)
    • 避免复杂的布局和布局抖动 (opens new window)
  • 渲染
    • 坚持仅合成器的属性和管理层计数 (opens new window)
    • 简化绘制的复杂度、减小绘制区域 (opens new window)
  • 解析HTML和CSS
    • 提取关键CSS (opens new window)
    • 压缩CSS (opens new window)
    • 延迟加载非关键的CSS (opens new window)
  • 脚本解析和编译
    • 通过代码拆分减少JS的负载 (opens new window)
    • 删除未使用的JS (opens new window)
  • 垃圾收集
    • 监控网页的总内存使用情况 (opens new window)

# 2.2.2.2 减少 JavaScript 执行时间

  • 减少 JavaScript 执行时间 (opens new window)
  • 通过代码分割仅发送用户需要的代码 (opens new window)
  • 压缩代码 (opens new window)
  • 删除未使用代码 (opens new window)
  • 使用PRPL模式缓存你的代码来减少网络开销 (opens new window)

# 2.2.2.3 确保文本在 webfont 加载期间保持可见

  • 确保文本在 webfont 加载期间保持可见 (opens new window)
  • 字体通常是需要一段时间才能加载的大文件。一些浏览器在字体加载之前隐藏文本,导致不可见文本 (FOIT) 闪烁。
  • 通过包含font-display: swap在您的@font-face风格中,您可以在大多数现代浏览器中避免 FOIT
@font-face {
  font-family: 'Pacifico';
  font-style: normal;
  font-weight: 400;
  src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2');
  font-display: swap;
}
#

# 2.3 LCP

Largest Contentful Paint(最大内容绘制) (opens new window) 标记了渲染出最大文本或图片的时间

  • 最大内容绘制 (LCP) 是测量感知加载速度的一个以用户为中心的重要指标

最大内容绘制,LCP(Largest Contentful Paint),用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。

# 2.3.1 衡量标准

img

LCP 时间 (以秒为单位) 颜色编码
0-2.5 绿色(快速)
2.5-4 橙色(中等)
超过 4 红色(慢)

# 2.3.2 如何计算LCP

new PerformanceObserver(function (entryList) {
  let entries = entryList.getEntries();
  entries.forEach(entry => {
    if (entry.startTime > data.LCP) {
        console.log('记录LCP', (data.LCP = entry.startTime));
    }
  });
}).observe({ type: 'largest-contentful-paint', buffered: true });

# 2.3.3 如何改进LCP

  • 改进LCP (opens new window)
  • 使用 PRPL 模式做到即时加载
    • 推送(或预加载)最重要的资源 Preload是一个声明性的获取请求,它告诉浏览器尽快请求资源
    • 尽快渲染初始路线 内联首屏JS和CSS推荐其余部分
    • 预缓存剩余资源 Service Worker
    • 延迟加载其他路由和非关键资源
  • 优化关键渲染路径 (opens new window)
  • 优化您的 CSS (opens new window)
  • 优化您的图像 (opens new window)
  • 优化网页字体 (opens new window)
  • 优化您的JavaScript (opens new window)
<link rel="preload" as="style" href="css/style.css">

# 2.4 TTI

Time to Interactive(可交互时间) (opens new window) 指标测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间

  • 虽然 TTI 可以在实际情况下进行测量,但我们不建议这样做,因为用户交互会影响您网页的 TTI,从而导致您的报告中出现大量差异。如需了解页面在实际情况中的交互性,您应该测量First Input Delay 首次输入延迟 (FID)

# 2.4.1 衡量标准

TTI

TTI 指标 (以秒为单位) 颜色编码
0–3.8 绿色(快速)
3.9–7.3 橙色(中等)
超过 7.3 红色(慢)

# 2.4.1 如何改进TTI

  • 缩小JavaScript (opens new window)

  • 预连接到所需的来源 (opens new window)

  • 预加载关键请求 (opens new window)

  • 减少第三方代码的影响 (opens new window)

  • 最小化关键请求深度 (opens new window)

  • 减少 JavaScript 执行时间 (opens new window)

  • 最小化主线程工作 (opens new window)

  • 保持较低的请求数和较小的传输大小 (opens new window)

# 2.5 TBT

total Blocking Time(总阻塞时间) (opens new window) 指标测量First Contentful Paint 首次内容绘制 (FCP)与Time to Interactive 可交互时间 (TTI)之间的总时间,这期间,主线程被阻塞的时间过长,无法作出输入响应

  • 虽然 TBT 可以在实际情况下进行测量,但我们不建议这样做,因为用户交互会影响您网页的 TBT,从而导致您的报告中出现大量差异。如需了解页面在实际情况中的交互性,您应该测量First Input Delay 首次输入延迟 (FID)

长任务是否会延迟您的交互时间? (opens new window)

# 2.5.1 衡量标准

TBT

TBT 时间 (以毫秒为单位) 颜色编码
0–200 绿色(快速)
200-600 橙色(中等)
超过 600 红色(慢)

# 2.5.1 如何改进TBT

  • 减少第三方代码的影响 (opens new window)
  • 减少 JavaScript 执行时间 (opens new window)
  • 最小化主线程工作 (opens new window)
  • 保持较低的请求数和较小的传输大小 (opens new window)

# 2.6 FID

首次输入延迟 (FID) (opens new window) 是测量加载响应度的一个以用户为中心的重要指标

  • 因为该项指标将用户尝试与无响应页面进行交互时的体验进行了量化,低 FID 有助于让用户确信页面是有效的。

首次输入延迟 (FID) 指标有助于衡量您的用户对网站交互性和响应度的第一印象

FID 测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间

# 2.6.1 衡量标准

FID

FID 时间 (以毫秒为单位) 颜色编码
0–100 绿色(快速)
100-300 橙色(中等)
超过 300 红色(慢)

# 2.6.2 如何计算FID

new PerformanceObserver(function (entryList) {
  let entries = entryList.getEntries();
  entries.forEach(entry => {
    //首次用户交互  开始处理的时间减去 开始的交互的时间 就是首次交互延迟的时间
    const FID = entry.processingStart - entry.startTime;
    console.log('FID', FID, entry);
  });
}).observe({ type: 'first-input', buffered: true });

# 2.6.3 如何改进FID

  • 减少第三方代码的影响 (opens new window)
  • 减少 JavaScript 执行时间 (opens new window)
  • 最小化主线程工作 (opens new window)
  • 保持较低的请求数和较小的传输大小 (opens new window)

# 2.7 CLS

累积布局偏移 (CLS) (opens new window) 是测量视觉稳定性的一个以用户为中心的重要指标。(因为它有助于量化用户打开页面时,页面布局变化的频率。)

  • CLS 测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数

# 2.7.1 衡量标准

CLS

# 2.7.2 如何计算CLS

new PerformanceObserver(function (entryList) {
  let entries = entryList.getEntries();
  entries.forEach(entry => {
    data.CLS += entry.value;
    console.log('CLS:', data.CLS);
  });

}).observe({ type: 'layout-shift', buffered: true });

# 2.7.3 如何改进CLS

  • 始终在您的图像和视频元素上包含尺寸属性
  • 首选转换动画,而不是触发布局偏移的属性动画
  • 除非是对用户交互做出响应,否则切勿在现有内容的上方插入内容

# 3. performance面板

# 3.1 面板说明

img

img

# 4. Lighthouse优化

# 4.1 减少未使用的 JavaScript

  • Remove unused JavaScript (opens new window)
  • 请减少未使用的 JavaScript,并等到需要使用时再加载脚本,以减少网络活动耗用的字节数

# 4.2 采用新一代格式提供图片

  • Serve images in modern formats (opens new window)
  • WebP 和 AVIF 等图片格式的压缩效果通常优于 PNG 或 JPEG,因而下载速度更快,消耗的数据流量更少

# 4.3 适当调整图片大小

  • Properly size images (opens new window)
  • 提供大小合适的图片可节省移动数据网络流量并缩短加载用时

# 4.4 推迟加载屏幕外图片

  • Defer offscreen images (opens new window)
  • 建议您在所有关键资源加载完毕后推迟加载屏幕外图片和处于隐藏状态的图片,从而缩短可交互前的耗时

# 4.5 移除阻塞渲染的资源

  • Eliminate render-blocking resources (opens new window)
  • 资源阻止了系统对您网页的首次渲染。建议以内嵌方式提供关键的 JS/CSS,并推迟提供所有非关键的 JS/样式

# 4.6 减少未使用的 CSS

  • Remove unused CSS (opens new window)
  • 请从样式表中减少未使用的规则,并延迟加载首屏内容未用到的 CSS,以减少网络活动耗用的字节数

# 4.7 使用视频格式提供动画内容

  • Use video formats for animated content (opens new window)
  • 使用大型 GIF 提供动画内容会导致效率低下。建议您改用 MPEG4/WebM 视频(来提供动画)和 PNG/WebP(来提供静态图片)以减少网络活动消耗的字节数

# 4.8 预先连接到必要的来源

  • Preconnect to required origins (opens new window)
  • 建议添加 preconnect 或 dns-prefetch 资源提示,以尽早与重要的第三方来源建立连接

# 4.9 应避免向新型浏览器提供旧版JavaScript

  • Deploying ES2015+ Code in Production Today (opens new window)
  • Polyfill 和 transform 让旧版浏览器能够使用新的 JavaScript 功能。不过,其中的很多函数对新型浏览器而言并非必需。对于打包的 JavaScript,请采用现代脚本部署策略,以便利用 module/nomodule 功能检测机制来减少传送到新型浏览器的代码量,同时保留对旧版浏览器的支持

# 4.10 确保文本在网页字体加载期间保持可见状态

  • Ensure text remains visible during webfont load (opens new window)
  • 利用 font-display 这项 CSS 功能,确保文本在网页字体加载期间始终对用户可见

# 4.11 未使用被动式监听器来提高滚动性能

  • Use passive listeners to improve scrolling performance (opens new window)
  • 建议您将触摸和滚轮事件监听器标记为 passive,以提高页面的滚动性能,passive不会对事件的默认行为说 no

# 4.12 图片元素没有明确的width和height

  • 请为图片元素设置明确的宽度值和高度值,以减少布局偏移并改善 CLS

# 4.13 注册“unload”事件监听器

  • Legacy lifecycle APIs to avoid (opens new window)
  • unload事件不会可靠地触发,而且监听该事件可能会妨碍系统实施“往返缓存”之类的浏览器优化策略。建议您改用pagehide或visibilitychange事件

# 4.14 最大限度地减少主线程工作

  • Minimize main thread work (opens new window)
  • 建议您减少为解析、编译和执行 JS 而花费的时间。您可能会发现,提供较小的 JS 负载有助于实现此目标

# 4.15 采用高效的缓存策略提供静态资源

  • Serve static assets with an efficient cache policy (opens new window)
  • 延长缓存期限可加快重访您网页的速度

# 4.16 缩短 JavaScript 执行用时

  • Reduce JavaScript execution time (opens new window)
  • 建议您减少为解析、编译和执行 JS 而花费的时间。您可能会发现,提供较小的 JS 负载有助于实现此目标

# 4.17 避免链接关键请求

  • Avoid chaining critical requests (opens new window)
  • 下面的关键请求链显示了以高优先级加载的资源。请考虑缩短链长、缩减资源的下载文件大小,或者推迟下载不必要的资源,从而提高网页加载速度

# 4.18 请保持较低的请求数量和较小的传输大小

  • Use Lighthouse for performance budgets (opens new window)
  • performancebudget (opens new window)
  • 若要设置页面资源数量和大小的预算,请添加 budget.json 文件

# 4.19 最大内容渲染时间元素

  • Largest Contentful Paint (opens new window)
  • 这是此视口内渲染的最大内容元素

# 4.20 请避免出现大幅度的布局偏移

  • 这些 DOM 元素对该网页的 CLS 影响最大

# 4.21 应避免出现长时间运行的主线程任务

  • Are long JavaScript tasks delaying your Time to Interactive? (opens new window)
  • 列出了主线程中运行时间最长的任务,有助于识别出导致输入延迟的最主要原因

# 4.22 避免使用未合成的动画

  • Avoid non-composited animations (opens new window)
  • 未合成的动画可能会卡顿并增加 CLS

# 4.23 缩减 CSS

  • Minify CSS (opens new window)
  • 缩减 CSS 文件大小可缩减网络负载规模

# 4.24 缩减 JavaScript

  • Minify JavaScript (opens new window)
  • 如果缩减 JavaScript 文件大小,则既能缩减负载规模,又能缩短脚本解析用时

# 4.25 对图片进行高效编码

  • Efficiently encode images (opens new window)
  • 如果图片经过了优化,则加载速度会更快,且消耗的移动数据网络流量会更少

# 4.26 启用文本压缩

  • Enable text compression (opens new window)
  • 对于文本资源,应先压缩(gzip、deflate 或 brotli),然后再提供,以最大限度地减少网络活动消耗的字节总数

# 4.27 初始服务器响应用时较短

  • Reduce server response times (TTFB) (opens new window)
  • 请确保服务器响应主文档的用时较短,因为这会影响到所有其他请求的响应时间

# 4.28 避免多次网页重定向

  • Avoid multiple page redirects (opens new window)
  • 重定向会在网页可加载前引入更多延迟

# 4.29 预加载关键请求

  • Preload key requests (opens new window)
  • 建议使用 <link rel=preload> 来优先提取当前在网页加载后期请求的资源

# 4.30 使用 HTTP/2

  • Does not use HTTP/2 for all of its resources (opens new window)
  • HTTP/2 提供了许多优于 HTTP/1.1 的益处,包括二进制标头和多路复用

# 4.31 请移除 JavaScript 软件包中的重复模块

  • 从软件包中移除重复的大型 JavaScript 模块可减少网络传输时不必要的流量消耗

# 4.32 预加载 LCP 元素所用图片

  • 优化 Largest Contentful Paint 最大内容绘制 (opens new window)
  • 请预加载 Largest Contentful Paint (LCP) 元素所用的图片,以缩短您的 LCP 用时

# 4.33 避免网络负载过大

  • Avoid enormous network payloads (opens new window)
  • 网络负载过大不仅会让用户付出真金白银,还极有可能会延长加载用时

# 4.34 避免 DOM 规模过大

  • Avoid an excessive DOM size (opens new window)
  • 大型 DOM 会增加内存使用量、导致样式计算用时延长,并产生高昂的布局重排成本

# 4.35 User Timing 标记和测量结果

  • User Timing marks and measures (opens new window)
  • 建议使用 User Timing API 检测您的应用,从而衡量应用在关键用户体验中的实际性能

# 4.36 尽量减少第三方使用

  • Loading Third-Party JavaScript (opens new window)
  • 第三方代码可能会显著影响加载性能。请限制冗余第三方提供商的数量,并尝试在页面完成主要加载后再加载第三方代码

# 4.37 使用 Facade 延迟加载第三方资源

  • Lazy load third-party resources with facades (opens new window)
  • 您可以延迟加载某些第三方嵌入代码。不妨考虑使用 Facade 替换这些代码,直到您需要使用它们为止

# 4.38 Largest Contentful Paint 所对应的图片未被延迟加载

  • The performance effects of too much lazy-loading (opens new window)
  • 被延迟加载的首屏图片会在页面生命周期内的较晚时间呈现,这可能会致使系统延迟渲染最大内容元素

# 4.39 请勿使用 document.write()

  • Uses document.write() (opens new window)
  • 对于连接速度较慢的用户,通过 document.write() 动态注入的外部脚本可将网页加载延迟数十秒

# 4.40 具有包含 width 或 initial-scale 的 标记

  • Does not have a tag with width or initial-scale (opens new window)
  • <meta name="viewport"> 不仅会针对移动设备屏幕尺寸优化您的应用,还会阻止系统在响应用户输入前出现 300 毫秒的延迟

# 参考

  • web-worker (opens new window)
  • https://crux-compare.netlify.app (opens new window)
  • https://web.dev (opens new window)
  • https://developer.mozilla.org/zh-CN/docs/Web/API/performance_property
  • IntersectionObserver懒加载 (opens new window)
  • https://app.requestmetrics.com (opens new window)
  • performancebudget (opens new window)
  • readystatechange_event (opens new window)
  • readyState (opens new window)
  • 使用 RAIL 模型衡量性能 (opens new window)

  • web 性能优化到底要怎么搞 (opens new window)

关于 document.readyState 的一点疑问

document.readyState === "complete" 的时候DOM加载完成,此时页面开始渲染了吗?

  • document.readyState 为 interactive 就代表 dom树完全构建完毕。 document.readyState 为 complete 表示这个网页里的资源都响应完毕(比如css js 图片 字体文件等)

  • dom树构建完毕不代表可以渲染了,我们现在只看下面这种情况: HTML 文件中只有 css 和 其他 HTML 标签和图片,假设 css 资源非常慢,那么 dom树构建完毕,还不能渲染 因为需要css。此时界面是空白的

欢迎来到 金色小芝麻
看板娘