跳到主要内容

2 篇博文 含有标签「Javascript」

查看所有标签

在现代桌面应用开发中,Tauri 框架凭借其轻量级、高性能和安全性,成为开发者们构建跨平台应用的首选工具之一。Tauri 允许开发者使用现代前端框架(如 React、Vue 或 Svelte)构建用户界面,同时利用 Rust 语言处理高效且安全的后端逻辑。然而,前后端之间的高效通信是构建功能丰富且稳定的 Tauri 应用的关键。本文将详细介绍 Tauri 中前后端通信的主要方式——Commands(命令)Events(事件)Channels(通道),并通过示例代码帮助您更好地理解和应用这些技术。

目录

  1. Tauri 简介
  2. 前后端通信方式概述
  3. Commands、Events 与 Channels 的对比
  4. 示例代码
  5. 权限配置示例
  6. 错误处理
  7. 最佳实践与安全性
  8. 性能优化
  9. 实际案例
  10. 总结
  11. 参考资源

Tauri 简介

Tauri 是一个用于构建跨平台桌面应用的框架,支持 WindowsmacOSLinux。它利用前端技术(如 HTML、CSS、JavaScript)构建用户界面,并使用 Rust 处理后端逻辑。与 Electron 相比,Tauri 生成的应用体积更小,性能更优,且具备更高的安全性。

前后端通信方式概述

在 Tauri 框架中,前端(通常使用 JavaScript 框架如 React、Vue 或 Svelte)与后端(Rust 编写)之间的通信是实现应用功能的核心。Tauri 提供了多种通信机制,主要包括 Commands(命令)Events(事件)Channels(通道)。除此之外,还有一些其他的通信方式,如在 Rust 中执行 JavaScript 代码。以下将详细介绍这些通信方式、它们的区别及适用场景。

Commands(命令)

Commands 是前端调用后端 Rust 函数的主要方式。通过命令,前端可以请求后端执行特定任务并获取结果。这种通信方式类似于前端发起的远程过程调用(RPC)。

使用场景

  • 执行复杂逻辑��需要后端处理的数据计算、文件操作、数据库交互等。
  • 获取后端数据:例如,从数据库获取数据并在前端展示。
  • 安全性需求:通过命令调用,能够在 Tauri 的安全模型下细粒度地控制权限。

实现步骤

  1. 在 Rust 后端定义命令

    使用 #[tauri::command] 宏定义一个可供前端调用的函数。

    src-tauri/src/lib.rs
    #[tauri::command]
    fn my_custom_command() {
    println!("我被 JavaScript 调用了!");
    }

    fn main() {
    tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![my_custom_command])
    .run(tauri::generate_context!())
    .expect("运行 Tauri 应用时出错");
    }

    :::note命令名称必须唯一。:::

    :::note由于胶水代码生成的限制,在 lib.rs 文件中定义的命令不能标记为 pub。如果将其标记为公共函数,您将看到如下错误:

    error[E0255]: the name `__cmd__command_name` is defined multiple times
    --> src/lib.rs:28:8
    |
    27 | #[tauri::command]
    | ----------------- previous definition of the macro `__cmd__command_name` here
    28 | pub fn x() {}
    | ^ `__cmd__command_name` reimported here
    |
    = note: `__cmd__command_name` must be defined only once in the macro namespace of this module

    :::

  2. 在前端调用命令

    使用 @tauri-apps/api 提供的 invoke 方法调用后端命令。

    前端 JavaScript 代码示例(如 React 组件)
    import { invoke } from '@tauri-apps/api/core';

    async function greetUser() {
    try {
    const greeting = await invoke('my_custom_command');
    console.log(greeting); // 输出: "我被 JavaScript 调用了!"
    } catch (error) {
    console.error('调用命令时出错:', error);
    }
    }

    // 在适当的生命周期钩子中调用 greetUser
  3. 配置权限

    tauri.conf.json 中,通过 CapabilitiesPermissions 配置命令的访问权限,确保命令的安全调用。

    tauri.conf.json 示例
    {
    "$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/cli/schema.json",
    "package": {
    "productName": "Pomodoro Timer",
    "version": "0.1.0"
    },
    "tauri": {
    "windows": [
    {
    "label": "main",
    "title": "ToDo Pomodoro",
    "width": 800,
    "height": 600,
    "resizable": true,
    "fullscreen": false,
    "decorations": true,
    "transparent": false,
    "alwaysOnTop": false,
    "visible": true,
    "url": "http://localhost:3000",
    "webviewAttributes": {
    "webPreferences": {
    "nodeIntegration": false
    }
    }
    }
    ],
    "security": {
    "capabilities": [
    {
    "identifier": "greet-capability",
    "description": "Allows the main window to greet users.",
    "windows": ["main"],
    "permissions": ["core:default"]
    }
    ]
    },
    "bundle": {
    "active": true,
    "targets": "all",
    "identifier": "com.yuxuetr.pomodoro",
    "icon": [
    "icons/32x32.png",
    "icons/128x128.png",
    "icons/128x128@2x.png",
    "icons/icon.icns",
    "icons/icon.ico"
    ]
    }
    }
    }

Events(事件)

Events 是 Tauri 中实现后端向前端推送消息的机制。与 Commands 不同,Events 是单向的,适用于需要实时通知前端的场景。

使用场景

  • 状态更新通知:后端状态变化时通知前端
  • 长时间任务进度:报告后台任务的执行进度
  • 系统事件通知:如系统状态变化、文件变动等

实现步骤

  1. 在 Rust 后端发送事件

    src-tauri/src/lib.rs
    use tauri::Manager;

    #[tauri::command]
    async fn start_process(window: tauri::Window) {
    // 模拟一个耗时操作
    for i in 0..100 {
    window.emit("process-progress", i).unwrap();
    std::thread::sleep(std::time::Duration::from_millis(100));
    }
    }
  2. 在前端监听事件

    import { listen } from '@tauri-apps/api/event';

    // 监听进度事件
    await listen('process-progress', (event) => {
    console.log('Progress:', event.payload);
    });

Channels(通道)

Channels 提供了一种双向的、持久的通信通道,特别适合需要持续数据交换的场景。

使用场景

  • 流式数据传输:如实时日志、数据流
  • 长连接通信:需要保持持续通信的场景
  • 复杂的双向数据交换:需要前后端频繁交互的功能

实现示例

  1. 在 Rust 后端创建通道

    src-tauri/src/lib.rs
    use tauri::plugin::{Builder, TauriPlugin};
    use tauri::{Runtime, State, Window};
    use std::collections::HashMap;
    use std::sync::{Mutex, mpsc};

    #[derive(Default)]
    struct ChannelState(Mutex<HashMap<String, mpsc::Sender<String>>>);

    #[tauri::command]
    async fn create_channel(
    channel_id: String,
    state: State<'_, ChannelState>,
    window: Window,
    ) -> Result<(), String> {
    let (tx, mut rx) = mpsc::channel(32);
    state.0.lock().unwrap().insert(channel_id.clone(), tx);

    tauri::async_runtime::spawn(async move {
    while let Some(message) = rx.recv().await {
    window
    .emit(&format!("channel:{}", channel_id), message)
    .unwrap();
    }
    });

    Ok(())
    }
  2. 在前端使用通道

    import { listen } from '@tauri-apps/api/event';
    import { invoke } from '@tauri-apps/api';

    // 创建并使用通道
    async function setupChannel() {
    const channelId = 'my-channel';
    await invoke('create_channel', { channelId });

    await listen(`channel:${channelId}`, (event) => {
    console.log('Received:', event.payload);
    });
    }

在 Rust 中执行 JavaScript

Tauri 还支持从 Rust 后端直接执行 JavaScript 代码,这提供了另一种前后端交互的方式。

实现示例

src-tauri/src/lib.rs
#[tauri::command]
async fn execute_js(window: tauri::Window) -> Result<String, String> {
// 执行 JavaScript 代码
window
.eval("console.log('从 Rust 执行的 JavaScript')")
.map_err(|e| e.to_string())?;

// 执行带返回值的 JavaScript
let result = window
.eval("(() => { return 'Hello from JS'; })()")
.map_err(|e| e.to_string())?;

Ok(result)
}

Commands、Events 与 Channels 的对比

特性Commands(命令)Events(事件)Channels(通道)
调用方向前端 → 后��后端 → 前端双向
响应类型同步/异步异步异步
使用场景一次性请求响应状态通知持续数据交换
数据流单次请求单次响应单向推送双向持续
适用性通用操作状态更新流式传输

示例代码

Commands 示例

完整的文件操作示例:

src-tauri/src/main.rs
use std::fs;
use tauri::command;

#[command]
async fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(path)
.map_err(|e| e.to_string())
}

#[command]
async fn write_file(path: String, contents: String) -> Result<(), String> {
fs::write(path, contents)
.map_err(|e| e.to_string())
}

fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![read_file, write_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// 前端调用示例
import { invoke } from '@tauri-apps/api/tauri';

async function handleFileOperations() {
try {
// 写入文件
await invoke('write_file', {
path: 'test.txt',
contents: 'Hello, Tauri!',
});

// 读取文件
const content = await invoke('read_file', {
path: 'test.txt',
});
console.log('File content:', content);
} catch (error) {
console.error('File operation failed:', error);
}
}

Events 示例

文件监控示例:

src-tauri/src/main.rs
use notify::{Watcher, RecursiveMode, watcher};
use tauri::Manager;
use std::time::Duration;

#[tauri::command]
async fn watch_directory(window: tauri::Window, path: String) -> Result<(), String> {
let (tx, rx) = std::sync::mpsc::channel();

let mut watcher = watcher(tx, Duration::from_secs(2)).map_err(|e| e.to_string())?;

watcher.watch(&path, RecursiveMode::Recursive).map_err(|e| e.to_string())?;

tauri::async_runtime::spawn(async move {
for res in rx {
match res {
Ok(event) => {
window.emit("file-change", event).unwrap();
}
Err(e) => println!("watch error: {:?}", e),
}
}
});

Ok(())
}
// 前端监听示例
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';

async function setupFileWatcher() {
// 启动文件监控
await invoke('watch_directory', {
path: './watched_folder',
});

// 监听文件变化事件
await listen('file-change', (event) => {
console.log('File changed:', event.payload);
});
}

Channels 示例

实时日志流示例:

src-tauri/src/main.rs
use tokio::sync::mpsc;
use std::collections::HashMap;
use std::sync::Mutex;

struct LogChannel(Mutex<HashMap<String, mpsc::Sender<String>>>);

#[tauri::command]
async fn start_log_stream(
channel_id: String,
state: tauri::State<'_, LogChannel>,
window: tauri::Window,
) -> Result<(), String> {
let (tx, mut rx) = mpsc::channel(100);
state.0.lock().unwrap().insert(channel_id.clone(), tx);

tauri::async_runtime::spawn(async move {
while let Some(log) = rx.recv().await {
window
.emit(&format!("log:{}", channel_id), log)
.unwrap();
}
});

Ok(())
}
// 前端实现
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';

async function setupLogStream() {
const channelId = 'app-logs';

// 创建日志流通道
await invoke('start_log_stream', { channelId });

// 监听日志消息
await listen(`log:${channelId}`, (event) => {
console.log('New log:', event.payload);
});
}

权限配置示例

{
"tauri": {
"security": {
"capabilities": [
{
"identifier": "file-access",
"description": "允许读写文件",
"windows": ["main"],
"permissions": ["fs:default"]
},
{
"identifier": "log-stream",
"description": "允许访问日志流",
"windows": ["main"],
"permissions": ["event:default"]
}
]
}
}
}

错误处理

src-tauri/src/lib.rs
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid input: {0}")]
InvalidInput(String),
}

impl serde::Serialize for Error {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}

#[tauri::command]
async fn handle_with_error() -> Result<String, Error> {
// 业务逻辑
Ok("Success".to_string())
}
// 前端错误处理
import { invoke } from '@tauri-apps/api/tauri';

try {
await invoke('handle_with_error');
} catch (error) {
console.error('Operation failed:', error);
}

最佳实践与安全性

  1. 权限控制

    • 始终使用最小权限原则
    • 明确定义每个命令的权限需求
    • 使用 allowlist 限制可用 API
  2. 数据验证

    • 在前后端都进行数据验证
    • 使用强类型定义接口
    • 处理所有可能的错误情况
  3. 性能优化

    • 使用适当的通信方式
    • 避免频繁的小数据传输
    • 合理使用异步操作

性能优化

  1. 批量处理

    • 合并多个小请求
    • 使用数据缓存
    • 实现请求队列
  2. 数据压缩

    • 大数据传输时使用压缩
    • 选择适当的序列化格式
  3. 异步处理

    • 使用异步命令
    • 实现后台任务
    • 合理使用线程池

实际案例

  1. 文件管理器

    • 使用 Commands 处理文件操作
    • 使用 Events 监控文件变化
    • 使用 Channels 传输大文件
  2. 实时聊天应用

    • 使用 Channels 处理消息流
    • 使用 Events 处理状态更新
    • 使用 Commands 处理用户操作

总结

Tauri 提供了丰富的前后端通信机制,每种方式都有其特定的使用场景:

  • Commands 适合一次性的请求-响应模式
  • Events 适合单向的状态通知
  • Channels 适合持续的双向数据交换

选择合适的通信方式对应用的性能和用户体验至关重要。同时,始终要注意安全性,合理使用权限控制和数据验证。

参考资源

鱼雪
  • 文本要点
    • 对象的基础(字面量对象)
    • 对象的高级创建方式(构造函数)
    • 超强的对象创建方式(原型链)

对象的基础

  • 本节要点
    • 什么是对象
    • 什么是属性
    • 什么是面向对象
    • 如何创建对象
    • 属性的工作原理
    • 给对象添加行为
    • this的工作原理

对象(object)

  • Javascript对象不过是一系列属性(property)而已

属性

  • 每个属性都有名称

面向对象

  • 在面向对象编程中,我们从对象的角度思考问题
  • 对象状态行为

如何创建对象

  • 对象将所有的名称和值(一起表示即属性)组合在一起

  • 创建一个包含一系列属性的对象,并将其赋给了一个变量

    • 以便使用它来访问修改这个对象的属性
  • 我们根据对象文本描述创建了一个货真价实的Javascript对象

    var chevy = {
    make: "Chevy",
    model: "Bel Air",
    year: 1975,
    color: "red",
    passengers: 2,
    convertible: false,
    mileage: 1021
    }

属性的工作原理

  • 如何访问属性

    • 句点表示法
    • objectName.AttrName
  • 如何修改属性

    • objectName.AttrName = newValue
  • 如何添加新属性

    • objectName.NewAttrName = value
  • 如何将属性用于计算

    • 可以像使用变量一样使用对象的属性,但是要用句点表示法来访问属性
  • 删除属性

    • delete objectName.AttrName
  • 句点表示法等价的属性访问方式

    • 使用对象名以及用引号和方括号括起来的属性名
    • objectName["AttrName"]
    • []方式在某些情况下更灵活,如chevy["co" + "lor"]

变量是如何存储对象的

  • 变量

    • 是容器,用于存储值
  • 对象的变量

    • 变量并不实际存储对象
    • 变量存储指向对象的引用
    • 引用就像指针,是对象的存储地址
    • 总之,变量不存储对象本身,而是存储四类指针的东西
    • 当我们使用句点表示法时,Javascript解释器负责根据引用获取对象并访问其属性
    • 变量是获取对象的途径
                                   |----------|
|----------| |-----------| | |
| Variable |---> | Reference |---> | object |
|----------| |-----------| | - attrs |
| |
|----------|
  • 可以多念几遍下面的话来理解
    • 当你将句点表示法用于引用变量时,相当于这样说:
      • 请使用句点前引用来获取相应的对象
      • 再访问句点后指定的属性

函数传递对象

  • 对象作为参数传递
    • 在Javascript中,实参是按值传递的
      • 这意味着传递的是实参的副本
    • 将对象赋值给变量时,变量存储的是指向对象的引用,而不是对象本身
    • 调用函数并向它传递一个对象时,传递的是对象引用的副本
      • 相当于多个引用指向同一个对象
        • 这意味着在函数中修改对象属性时,修改的将是原始对象的属性
        • 当函数结束时,在函数中对对象的修改依然有效

给对象添加行为

  • 对象的行为

    • 对象是活动的,能够做事情
  • 给函数添加函数

    • 可以直接将(匿名)函数赋值给属性
      var fiat = {
      make: "Fiat",
      model: 500,
      drive: function() {
      console.log("Zoom zoom")
      }
      }
  • 调用函数(实际上方法)

    • 对象中的函数(function)称为方法(method)
    • 调用对象的方法drive,使用句点表示法
      fiat.drive()

在方法中访问属性

  • 如何在方法中访问对象的属性
    • Javascript关键字this,可使用它来告诉Javascript,你说的是当前所处的对象
      var fiat = {
      make: "Fiat",
      started: false,
      start: function() {
      this.started = true
      }
      }

关键字this的工作原理

  • this
    • 可将this视为一个变量,指向其方法被调用的对象
      • 如果你使用fiat.start(),并在方法start中使用this
        • 那么this将指向对象fiat
    • 要理解关键字this
      • 关键在于每当方法被调用时,在该方法内都可使用this来引用方法被调用的对象

高级对象的构造技巧

上面所讲的对象构造方式是使用对象字面量来执行其属性来创建对象。

更好的方式我们会使用对象构造函数来构造对象。让所有对象都采用相同的设计蓝图

可以确保所有对象包含相同的属性和方法,通过构造函数编写的代码将简洁的多。

  • 本节要点:
    • 构造函数
    • new操作符
    • 构造函数构造原理
    • 内置构造函数

对象构造函数

  • 简介

    • 犹如一个小型工厂,能够创建无数类似的对象
    • 从代码角度看,构造函数很像返回对象的函数
  • 创建构造函数

    • 先定义构造函数
    • 再使用它来创建对象
  • 定义构造函数

    • 与常规函数没什么两样
    • 给构造函数命名时,采用首字母大写的方式
    • 形参用于给属性赋值
    • 属性名和形参不必相同,但通常相同
    • 构造函数什么都没返回
    function Dog(name, breed, weight) {
    this.name = name
    this.breed = breed
    this.weight = weight
    }
  • 如何使用构造函数

    • 先创建一个构造函数
    • 再使用它
  • 创建构造函数

    • 使用new运算符
    var fido = new Dog("Fido", "Mixed", 30)

构造函数的工作原理

  • var fido = new Dog("Fido", "Mixed", 30)
    1. new首先创建一个新的空对象
    2. 接下来new设置this,其实指向这个新对象
    3. 设置this后,调用函数Dog,并将Fido,Mixed,30作为实参传递给它
    4. 接下来执行这个参数的代码,与大多数函数一样,Dog给信创建的this对象的属性赋值
    5. 构造函数Dog执行完毕后,运算符new返回this--指向新创建的对象的引用
      • 会自动为你返回this,无需在代码中显式地返回
      • 指向新对象的引用被返回后,我们将其赋值给变量fido
|-------| 2.Setting |------| 4.Call  |----------| 
| new |---------->| this |-------->| Function |
|-------| |------| |----------|
| | 3.Pointing |
| V |5.Create New Attrs
| 1.Create |--------------| |
|---------->| empty object |<---------|
|--------------| |
|----------| 7.Assignment Reference | 6.Return Reference
| Variable |<-----------------------------|
|----------|

在构造函数中定义方法

function Dog(name, breed, weight) {
this.name = name
this.breed = breed
this.weight = weight
}
this.bark = function() {
if (this.weight < 25) {
console.log(this.name + " says Woof")
} else {
console.log(this.name + " says Yip")
}
}

理解对象实例

  • 构造函数创建实例

    • 每当使用运算符new调用构造函数时,都将创建一个新的对象实例
  • 确定对象是否是指定构造函数的实例

    • 实际上,创建对象时,运算符new在幕后存储了一些信息
      • 让你随时都能确定对象是由哪个构造函数创建的
    • 运算符instanceof
      • 就是根据这些信息确定对象是否是指定构造函数的实例

创建好的对象,也可以有独特的属性

  • 使用构造函数

    • 创建一致的对象
    • 包含相同属性和方法的对象
  • 使用构造函数创建对象后,可对其进行修改

    • 因为构造函数创建的对象是可以修改的
    var fido = new Dog("Fido", "Mixed", 30)
    fido.owner = "Bob" // 添加新属性
    delete fido.weight // 删除weight属性

Javascript内置构造函数

  • Date()

    • 创建日期对象
  • Array()

    • 创建数组
  • Object()

    • 使用构造函数Object来创建对象
    • 对象字面量表示{}new Object()等价
  • RegExp()

    • 创建正则表达式对象
  • Math()

    • 执行数学运算任务的属性和方法
  • Error()

    • 创建标准错误对象

超强的对象创建能力

我们需要再对象之间建立关系共享代码的方法。Javascript的对象模型**非常强大。

  • Javascript采用的不是基于类的面向对象系统

    • 而是更强大的原型模型,其中的对象可继承和扩展其他对象的行为
  • Javascript没有传统面向对象模型,即从类创建对象模型。

    • 事实上,Javascript根本就没有类。
  • 在Javascript中

    • 对象从其他对象那里继承行为,我们称之为原型式继承(prototypal inheritance)
  • 本节要点

    • 原型(链)

重复的方法是个问题

  • 再谈构造函数

    • 构造函数每次创建一个对象都有独立的属性和一组新方法
      • 这会影响应用程序的性能,占用计算机资源
    • 使用构造函数的目的:试图重用行为
      • 但构造函数在代码层实现重用行为的目的
      • 但在运行阶段,这种解决方案并不好,每个实例对象都获得自己的方法的副本
  • 产生问题的原因

    • 我们没有充分利用Javascript的对象模型
    • Javascript对象模型基于原型的概念
    • 在这种模型中,可通过**扩展其他对象(即原型对象)**来创建对象

原型是什么

  • 原型
    • Javascript对象可从其他对象那里继承属性和行为
    • 被继承的对象称之为原型
    • 对象继承另一个对象后,便可访问其所有方法和属性

继承原型

  • 定义原型
    • 定义所有类型对象(如Dog)都需要的属性和方法
      • 因为所有(Dog)都将继承它们
    function Dog() {
    this.species: "Canine"
    bark: function(){
    console.log("Bar bar bar ...")
    }
    }

继承的工作原理

  • 方法继承
    • 既然方法(bark)并不包含在各个小狗对象中,而是包含在原型
    • 这正是继承的用武之地
    • 对对象调用方法时,如果在对象中找不到,将在原型中查找它

重写原型

  • 继承原型
    • 并不意味着必须与它完全相同
    • 在任何情况下,都可重写原型的属性和方法,为此只需在对象实例中提供它们即可
    • 这是可行的
      • 因为Javascript总是先在对象实例中查找,如果找不到,再在原型中查找

原型从哪里来

  • 访问原型

    • 构造函数的prototype属性,这是一个指向原型的引用
    Dog.prototype
  • 在Javascript中

    • 函数也是对象
    • 几乎所有东西都是对象
    • 因此函数还可以有属性
    • 而构造函数都包含属性prototype

如何设置原型

  • 设置方式
    • 可通过构造函数(Dog)的属性prototype来访问原型对象
    • 换句话说,你需要给原型添加属性和方法,这通常是在使用构造函数前进行的
    function Dog(name, breed, weight) {
    this.name = name
    this.breed = breed
    this.weight = weight
    }
    // 设置小狗原型
    Dog.prototype.species = "Canine"
    Dog.prototype.bark = function() {
    if (this.weight > 25) {
    console.log(this.name + " says Woof!")
    } else {
    console.log(this.name + " says Yip!")
    }
    }
    Dog.prototype.run = function() {
    console.log("Run!")
    }
    Dog.prototype.wag = function() {
    console.log("Wag!")
    }

    var fido = new Dog("Fido", "Mixed", 33)
    var fluffy = new Dog("Fluffy", "Poodle", 22)
    var spot = new Dog("Spot", "Chihuahua", 8)

    fido.bark()
    fido.run()
    fido.wag()

    fluffy.bark()
    fluffy.run()
    fluffy.wag()

    spot.bark()
    spot.run()
    spot.wag()

    // 对象自定义方法
    spot.bark = function() {
    console.log(this.name + " says WOOF!")
    }
    spot.bark()
    spot.run()
    spot.wag()

原型是动态的

  • 在原型中添加新方法

    • 继承该原型的任何对象都能使用这个方法
  • 判断对象的属性是自有的还是原型的

    • 使用objectName.hasOwnProperty("AttrName")
      • spot.hasOwnProperty("species")
      • 返回ture表示是对象自有属性
      • 返回false表示是不是对象自有的属性

建立原型链

  • 原型继承

    • 对象不仅可以继承一个原型的属性
    • 对象还可以继承一个原型链
  • 创建表演犬实例

    • 需要一个继承小狗原型的对象
    • 接下来,将新建一个小狗实例变成表演犬原型
    • 补全原型
    // 1.
    function Dog(name, breed, weight) {
    this.name = name
    this.breed = breed
    this.weight = weight
    }
    Dog.prototype.bark = function() {
    console.log(this.name + " says WOOF")
    }

    // 2
    function ShowDog(name, breed, weight, handler) {
    // this.name = name
    // this.breed = breed
    // this.weight = weight
    // 这里调用Dog.call,导致Dog被调用,这里之所以调用call,而不直接调用Dog
    // - 是因为这样可以控制this的值
    // - 调用构造函数Dog将设置当前对象的属性
    Dog.call(this, name, breed, weight)
    this.handler = handler
    }
    ShowDog.prototype = new Dog()
    ShowDog.prototype.league = "Webvile"
    ShowDog.prototype.stack = function() {
    console.log("Stack")
    }
    ShowDog.prototype.bait = function() {
    console.log("Bait")
    }
    ShowDog.prototype.gait = function(kind) {
    console.log(kind + "ing")
    }
    ShowDog.prototype.groom = function() {
    console.log("Groom")
    }
    // 3.
    var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie")
    // 4.
    scotty.stack()
    scotty.bark()

    // 5.
    if (scotty instanceof Dog) {
    console.log("Scotty is a Dog")
    }
    if (scotty instanceof ShowDog) {
    console.log("Scotty is a ShowDog")
    }

    console.log("Fido constructor is " + fido.constructor)
    console.log("Scotty constructor is " + scotty.constructor)

    // 6.
    ShowDog.prototype = new Dog()
    // 获取表演犬原型,将其属性`constructor`显式地设置为构造函数ShowDog
    ShowDog.prototype.constructor = ShowDog()

Dog.call详解

  • 运算符new
    • 新建一个空对象,并将其赋值给构造函数中的变量this
  • Dog.call
    • 没有使用运算符new,因此它不会返回任何对象
    • 上面代码中this指向了new ShowDog()创建的对象

Object

  • Object是什么

    • 对象始祖
    • 所有对象都是它派生而来的
    • Object实现了多个重要方法,它们是Javascript对象系统的核心部分
  • 充分发挥继承的威力之重写内置行为

    • Object中不可重写的属性
      • constructor
      • hasOwnProperty
      • isPrototypeOf
      • propertyIsEnumerable
    • Object对象中可重写的属性
      • toString
      • toLocaleString
      • valueOf
  • 充分发挥继承的威力之:扩展内置对象

    • 通过给原型添加方法,可给其所有实例添加新功能

总结

|----|       |------------|       |------------------|
| {} | ----> | new Object | ----> | object.prototype |
|----| |------------| |------------------|

object
|---------|
| attrs |
|---------|
| methods |
|---------|
鱼雪