整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

探索 JavaScript Window 对象:从

探索 JavaScript Window 对象:从 BOM 到本地存储,全面掌握!

JavaScript 的 Window 对象是浏览器对象模型(BOM)的核心,它为开发者提供了操作浏览器窗口和控制用户体验的强大工具。本篇文章将带你深入了解 Window 对象的各个方面:BOM、JavaScript 执行机制、Location 对象、Navigator 对象、History 对象以及本地存储。


1. 浏览器对象模型(BOM)

什么是 BOM?

BOM 全称为浏览器对象模型(Browser Object Model),它提供了与浏览器互动的各种接口和方法。通过 BOM,开发者可以访问和操作浏览器窗口、文档、历史记录等。BOM 的核心对象是 Window 对象。

代码示例:

// 设置窗口的高度和宽度
window.innerHeight=800;
window.innerWidth=1200;

// 打开一个新窗口
let newWindow=window.open('https://www.example.com', '_blank');

2. JavaScript 执行机制

JavaScript 代码在浏览器中的执行机制主要包括两个方面:事件循环和回调队列。

事件循环:

事件循环(Event Loop)是 JavaScript 的执行模型,它确保非阻塞操作能够正常执行。

代码示例:

console.log('任务开始');

setTimeout(()=> {
  console.log('异步任务');
}, 2000);

console.log('任务结束');

执行顺序为:

  1. 任务开始
  2. 任务结束
  3. 异步任务(2秒后输出)

3. Location 对象

用法:

Location 对象包含有关当前 URL 的信息,并提供了一些重定向和导航的方法。

常用属性和方法:

  • location.href:获取或设置当前 URL
  • location.reload():重新加载页面
  • location.assign(url):加载新的 URL

代码示例:

// 获取当前 URL
console.log(location.href);

// 重定向到新的 URL
location.href='https://www.new-url.com';

// 重新加载当前页面
location.reload();

4. Navigator 对象

用法:

Navigator 对象包含有关浏览器的信息,如浏览器名称、版本、以及用户代理信息。

常用属性和方法:

  • navigator.userAgent:返回用户代理字符串
  • navigator.geolocation:提供用户地理位置

代码示例:

// 获取用户代理信息
console.log(navigator.userAgent);

// 获取用户地理位置
navigator.geolocation.getCurrentPosition((position)=> {
  console.log(`Latitude: ${position.coords.latitude}, Longitude: ${position.coords.longitude}`);
});

5. History 对象

用法:

History 对象允许操作浏览器的历史记录(会话历史)。

常用属性和方法:

  • history.back():返回到上一页
  • history.forward():前进到下一页
  • history.go(n):加载历史记录中的某个特定页面

代码示例:

// 返回上一个页面
history.back();

// 前进到下一个页面
history.forward();

// 前往历史记录中的第一个页面
history.go(-1);

6. 本地存储(Local Storage)

用法:

本地存储是一种在客户端存储数据的方式,数据存储在浏览器中,不会随页面刷新而丢失。

常用方法:

  • localStorage.setItem(key, value):存储数据
  • localStorage.getItem(key):获取数据
  • localStorage.removeItem(key):删除数据
  • localStorage.clear():清除所有数据

代码示例:

// 存储数据
localStorage.setItem('username', 'john_doe');

// 获取数据
let username=localStorage.getItem('username');
console.log(username); // 输出: john_doe

// 删除数据
localStorage.removeItem('username');

// 清空所有数据
localStorage.clear();

结论

通过对这些对象和机制的深入了解,你可以更好地利用 JavaScript 提供的各种功能来优化用户体验,增强应用程序的互动性。无论是控制浏览器窗口、获取用户信息,还是操作历史记录和本地存储,掌握这些知识都会让你的开发工作事半功倍。


更多精彩内容,请持续关注!

任何一家Saas企业都需要有自己的低代码平台.在可视化低代码的前端研发过程中, 发现了很多有意思的技术需求, 在解决这些需求的过程中, 往往也会给自己带来很多收获, 今天就来分享一下在研发Dooring过程中遇到的前端技术问题——javascript函数存储.
?

背景介绍

我们都知道要想搭建一个前端页面基本需要如下3个要素:

  • 元素(UI)
  • 数据(Data)
  • 事件/交互(Event)

「数据驱动视图」 的时代, 这三个要素的关系往往如下图所示:

可视化搭建平台的设计思路往往也是基于上面的过程展开的, 我们需要提供编辑器环境给用户来创建「视图」「交互」, 最终用户保存的产物可能是这样的:

{
    "name": "Dooring表单",
    "bgColor": "#666",
    "share_url": "http://xxx.cn",
    "mount_event": [
        {
            "id": "123",
            "func": ()=> {
                // 初始化逻辑
                GamepadHapticActuator();
            },
            "sourcedata": []
        }
    ],
    "body": [
        {
            "name": "header",
            "event": [
                {
                    "id": "123",
                    "type": "click",
                    "func": ()=> {
                        // 组件自定义交互逻辑
                        showModal();
                    }
                }
            ]
        }
    ]
}

那么问题来了, json 字符串我们好保存(可以通过JSON.stringify序列化的方式), 但是如何将「函数」也一起保存呢? 保存好了函数如何在页面渲染的时候能正常让 js 运行这个函数呢?

实现方案思考

我们都知道将 js 对象转化为json 可以用 JSON.stringify 来实现, 但是它也会有局限性, 比如:

  1. 转换值如果有 toJSON() 方法,那么由 toJson() 定义什么值将被序列化
  2. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中
  3. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
  4. undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)
  5. 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们
  6. Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理
  7. NaN 和 Infinity 格式的数值及 null 都会被当做 null
  8. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性

我们可以看到第4条, 如果我们序列化的对象中有函数, 它将会被忽略! 所以常理上我们使用JSON.stringify 是无法保存函数的, 那还有其他办法吗?

也许大家会想到先将函数转换成字符串, 再用 JSON.stringify 序列化后保存到后端, 最后在组件使用的时候再用 eval 或者 Function 将字符串转换成函数. 大致流程如下:

不错, 理想很美好, 但是现实很_______.

接下来我们就一起分析一下关键环节 func2stringstring2func 如何实现的.

js存储函数方案设计

熟悉 JSON API 的朋友可能会知道 JSON.stringify 支持3个参数, 第二个参数 replacer 可以是一个函数或者一个数组。作为函数,它有两个参数,键(key)和值(value),它们都会被序列化。 函数需要返回 JSON 字符串中的 value, 如下所示:

  • 如果返回一个 Number, 转换成相应的字符串作为属性值被添加入 JSON 字符串
  • 如果返回一个 String, 该字符串作为属性值被添加入 JSON 字符串
  • 如果返回一个 Boolean, 则 "true" 或者 "false" 作为属性值被添加入 JSON 字符串
  • 如果返回任何其他对象,该对象递归的序列化成 JSON 字符串,对每个属性调用 replacer 方法。除非该对象是一个函数,这种情况将不会被序列化成 JSON 字符
  • 如果返回 undefined,该属性值不会在 JSON 字符串中输出

所以我们可以在第二个函数参数里对 value类型为函数的数据进行转换。如下:

const stringify=(obj)=> {
    return JSON.stringify(obj, (k, v)=> {
      if(typeof v==='function') {
          return `${v}`
      }
      return v
    })
}

这样我们看似就能把函数保存到后端了. 接下来我们看看如何反序列化带函数字符串的 json.

因为我们将函数转换为字符串了, 我们在反解析时就需要知道哪些字符串是需要转换成函数的, 如果不对函数做任何处理我们可能需要「人肉识别」.

? 「人肉识别」的缺点在于我们需要用正则把具有函数特征的字符串提取出来, 但是函数写法有很多, 我们要考虑很多情况, 也不能保证具有函数特征的字符串一定是函数.
?

所以我换了一种简单的方式, 可以不用写复杂正则就能将函数提取出来, 方法就是在函数序列化的时候注入标识符, 这样我们就能知道那些字符串是需要解析为函数了, 如下:

stringify: function(obj: any, space: number | string, error: (err: Error | unknown)=> {}) {
        try {
            return JSON.stringify(obj, (k, v)=> {
                if(typeof v==='function') {
                    return `${this.FUNC_PREFIX}${v}`
                }
                return v
            }, space)
        } catch(err) {
            error && error(err)
        }
}

this.FUNC_PREFIX 就是我们定义的标识符, 这样我们在用 JSON.parse 的时候就能快速解析函数了. JSON.parse 也支持第二个参数, 他的用法和 JSON.stringify 的第二个参数类似, 我们可以对它进行转换, 如下:

parse: function(jsonStr: string, error: (err: Error | unknown)=> {}) {
        try {
            return JSON.parse(jsonStr, (key, value)=> {
                if(value && typeof value==='string') {
                    return value.indexOf(this.FUNC_PREFIX) > -1 ? new Function(`return ${value.replace(this.FUNC_PREFIX, '')}`)() : value
                }
                return value
            })
        } catch(err) {
            error && error(err)
        }
    }

new Function 可以把字符串转换成 js 函数, 它只接受字符串参数,其可选参数为方法的入参,必填参数为方法体内容, 一个形象的例子:

我们上述的代码中函数体的内容:

new Function(`return ${value.replace(this.FUNC_PREFIX, '')}`)()

之所以要 return 是为了把原函数原封不动的还原, 大家也可以用 eval , 但是出于舆论还是谨慎使用.

以上方案已经能实现前端存储函数的功能了, 但是为了更工程化和健壮性还需要做很多额外的处理和优化, 这样才能让更多人开箱即用的使用你的库.

最后

为了让更多人能直接使用这个功能, 我将完整版 json 序列化方案封装成了类库, 支持功能如下:

  • stringify 在原生JSON.stringify 的基础上支持序列化函数,错误回调
  • parse 在原生JSON.parse 的基础上支持反序列化函数,错误回调
  • funcParse 将js对象中的函数一键序列化, 并保持js对象类型不变

安装方式如下:

# or npm install xijs
yarn add xijs

使用:

import { parser } from 'xijs';

const a={
    x: 12,
    b: function() {
      alert(1)
    }
 }
 
 const json=parser.stringify(a);
 const obj=parser.parse(json);
 // 调用方法
 obj.b();

更多推荐

. 各种存储方案的简单对比

  • Cookies:浏览器均支持,容量为4KB
  • UserData:仅IE支持,容量为64KB
  • Flash:100KB,非HTML原生,需要插件支持
  • Google Gears SQLite :需要插件支持,容量无限制
  • LocalStorage:HTML5,容量为5M
  • SesstionStorage:HTML5,容量为5M
  • globalStorage:Firefox独有的,Firefox13开始就不再支持这个方法

UserData仅IE支持, Google Gears SQLite需要插件,Flash已经伴随着HTML5的出现渐渐退出了历史舞台,因此今天我们的主角只有他们三个:Cookie,LocalStorge,SesstionStorge;

2. Cookie

作为一个前端和Cookie打交道的次数肯定不会少了,Cookie算是比较古老的技术了

1993 年,网景公司雇员 Lou Montulli 为了让用户在访问某网站时,进一步提高访问速度,同时也为了进一步实现个人化网络,发明了今天广泛使用的 Cookie。

2.1 Cookie的特点

我们先来看下Cookie的特点:

  • 1)cookie的大小受限制,cookie大小被限制在4KB,不能接受像大文件或邮件那样的大数据。
  • 2)只要有请求涉及cookie,cookie就要在服务器和浏览器之间来回传送(这解释为什么本地文件不能测试cookie)。而且cookie数据始终在同源的http请求中携带(即使不需要),这也是Cookie不能太大的重要原因。正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。
  • 3)用户每请求一次服务器数据,cookie则会随着这些请求发送到服务器,服务器脚本语言如PHP等能够处理cookie发送的数据,可以说是非常方便的。当然前端也是可以生成Cookie的,用js对cookie的操作相当的繁琐,浏览器只提供document.cookie这样一个对象,对cookie的赋值,获取都比较麻烦。而在PHP中,我们可以通过setcookie()来设置cookie,通过$_COOKIE这个超全局数组来获取cookie。

cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。

2.2 Session

说到Cookie就不能不说Session。

Session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。比如:http://damonare.cn?sessionid=123456还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如:

<form name="testform" action="/xxx">
 <input type="hidden" name="sessionid" value="123456">
 <input type="text">
</form>

实际上这种技术可以简单的用对action应用URL重写来代替。

2.3 Cookie和Session简单对比

Cookie和Session 的区别:

  • 1)cookie数据存放在客户的浏览器上,session数据放在服务器上。
  • 2)cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
  • 3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。
  • 4)单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。5)所以建议:将登陆信息等重要信息存放为SESSION其他信息如果需要保留,可以放在cookie中

2.4 document.cookie的属性

expires属性

指定了coolie的生存期,默认情况下coolie是暂时存在的,他们存储的值只在浏览器会话期间存在,当用户推出浏览器后这些值也会丢失,如果想让cookie存在一段时间,就要为expires属性设置为未来的一个过期日期。现在已经被max-age属性所取代,max-age用秒来设置cookie的生存期。

path属性

它指定与cookie关联在一起的网页。在默认的情况下cookie会与创建它的网页,该网页处于同一目录下的网页以及与这个网页所在目录下的子目录下的网页关联。

domain属性

domain属性可以使多个web服务器共享cookie。domain属性的默认值是创建cookie的网页所在服务器的主机名。不能将一个cookie的域设置成服务器所在的域之外的域。例如让位于http://order.damonare.cn的服务器能够读取http://catalog.damonare.cn设置的cookie值。如果http://catalog.damonare.cn的页面创建的cookie把自己的path属性设置为“/”,把domain属性设置成“.damonare.cn”,那么所有位于http://catalog.damonare.cn的网页和所有位于http://orlders.damonare.cn的网页,以及位于http://damonare.cn域的其他服务器上的网页都可以访问这个cookie。

secure属性

它是一个布尔值,指定在网络上如何传输cookie,默认是不安全的,通过一个普通的http连接传输

2.5 cookie实战

这里我们使用javascript来写一段cookie,借用w3cschool的demo:

function getCookie(c_name){
 if (document.cookie.length>0){
 c_start=document.cookie.indexOf(c_name + "=")
 if (c_start!=-1){
 c_start=c_start + c_name.length+1
 c_end=document.cookie.indexOf(";",c_start)
 if (c_end==-1) c_end=document.cookie.length
 return unescape(document.cookie.substring(c_start,c_end))
 }
 }
 return "";
}
function setCookie(c_name,value,expiredays){
 var exdate=new Date()
 exdate.setDate(exdate.getDate()+expiredays)
 document.cookie=c_name+ "=" +escape(value)+
 ((expiredays==null) ? "" : "; expires="+exdate.toUTCString())
}
function checkCookie(){
 username=getCookie('username')
 if(username!=null && username!=""){alert('Welcome again '+username+'!')}
 else{
 username=prompt('Please enter your name:',"")
 if (username!=null && username!=""){
 setCookie('username',username,355)
 }
 }
}

注意这里对Cookie的生存期进行了定义,也就是355天

3. localStorage

这是一种持久化的存储方式,也就是说如果不手动清除,数据就永远不会过期。

它也是采用Key - Value的方式存储数据,底层数据接口是sqlite,按域名将数据分别保存到对应数据库文件里。它能保存更大的数据(IE8上是10MB,Chrome是5MB),同时保存的数据不会再发送给服务器,避免带宽浪费。

3.1 localStorage的属性方法

下表是localStorge的一些属性和方法

属性方法 说明 localStorage.length 获得storage中的个数 localStorage.key(n) 获得storage中第n个元素对的键值(第一个元素是0) localStorage.getItem(key) 获取键值key对应的值 localStorage.key 获取键值key对应的值 localStorage.setItem(key, value) 添加数据,键值为key,值为value localStorage.removeItem(key) 移除键值为key的数据 localStorage.clear() 清除所有数据

3.2 localStorage的缺点

  • ① localStorage大小限制在500万字符左右,各个浏览器不一致
  • ② localStorage在隐私模式下不可读取
  • ③ localStorage本质是在读写文件,数据多的话会比较卡(firefox会一次性将数据导入内存,想想就觉得吓人啊)
  • ④ localStorage不能被爬虫爬取,不要用它完全取代URL传参

4. sessionStorage

和服务器端使用的session类似,是一种会话级别的缓存,关闭浏览器会数据会被清除。不过有点特别的是它的作用域是窗口级别的,也就是说不同窗口间的sessionStorage数据不能共享的。使用方法(和localStorage完全相同):

属性方法 说明 sessionStorage.length 获得storage中的个数 sessionStorage.key(n) 获得storage中第n个元素对的键值(第一个元素是0) sessionStorage.getItem(key) 获取键值key对应的值 sessionStorage.key 获取键值key对应的值 sessionStorage.setItem(key, value) 添加数据,键值为key,值为value sessionStorage.removeItem(key) 移除键值为key的数据 sessionStorage.clear() 清除所有数据

5. sessionStorage和localStorage的区别

  • sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。当用户关闭浏览器窗口后,数据立马会被删除。
  • localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。第二天、第二周或下一年之后,数据依然可用。

5.1 测试

sessionStorage:

if (sessionStorage.pagecount){
 sessionStorage.pagecount=Number(sessionStorage.pagecount) +1;
}else{
 sessionStorage.pagecount=1;
}
console.log("Visits "+ sessionStorage.pagecount + " time(s).");

测试过程:我们在控制台输入上述代码查看打印结果

控制台首次输入代码:

关闭窗口,控制台再次输入代码:

所谓的关闭窗口即销毁,就是这样,关闭窗口重新打开输入代码输出结果还是上面图片的样子,也就是说关闭窗口后sessionStorage.pagecount即被销毁,除非重心创建。或者从历史记录进入才会相关数据才会存在。好的,我们再来看下localStorge表现:

if (localStorage.pagecount){
 localStorage.pagecount=Number(localStorage.pagecount) +1;
}else{
 localStorage.pagecount=1;
 }
console.log("Visits "+ localStorage.pagecount + " time(s).");

控制台首次输入代码:

关闭窗口,控制台再次输入代码:

6. web Storage和cookie的区别

Web Storage(localStorage和sessionStorage)的概念和cookie相似,区别是它是为了更大容量存储设计的。Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。

除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像cookie需要前端开发者自己封装setCookie,getCookie。

但是Cookie也是不可以或缺的:Cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生