Gần đây tôi đã trả lời một số cuộc phỏng vấn và hầu hết tất cả những người phỏng vấn đều hỏi những câu hỏi tương tự về JavaScript. Vì vậy, tôi muốn chia sẻ kiến thức của mình với tất cả các bạn thông qua blog này
- JS là đơn luồng hoặc đa luồng
- JS là đồng bộ hoặc không đồng bộ
- JS được truyền theo giá trị hoặc được truyền theo tham chiếu
- Palăng là gì
- Phạm vi là gì
- đóng cửa là gì
- gọi lại là gì
- Không đồng bộ/Đang chờ
JS là đơn luồng hoặc đa luồng
Javascript là một luồng có nghĩa là nó chỉ có một ngăn xếp cuộc gọi. Ngăn xếp cuộc gọi giống như cấu trúc dữ liệu ngăn xếp và các ngăn xếp là FILO, nhập trước xuất trước. Tương tự, trong ngăn xếp cuộc gọi, bất cứ khi nào một dòng mã vào bên trong ngăn xếp cuộc gọi, nó sẽ được thực thi và di chuyển ra khỏi ngăn xếp. Theo cách này, JavaScript là ngôn ngữ đơn luồng vì chỉ có một ngăn xếp cuộc gọi
JS là đồng bộ hoặc không đồng bộ
Vì JavaScript là một ngôn ngữ đơn luồng nên về bản chất nó là đồng bộ. Như tên cho thấy đồng bộ có nghĩa là trong một trình tự, tôi. e. mọi câu lệnh của mã được thực thi từng cái một.
JavaScript chỉ ở dạng không đồng bộ, chẳng hạn như xử lý hình ảnh hoặc tạo yêu cầu qua mạng như lệnh gọi API.
JS được truyền theo giá trị hoặc được truyền theo tham chiếu
Trong JavaScript, tất cả các đối số của hàm luôn được truyền theo giá trị. Điều đó có nghĩa là JavaScript sao chép các giá trị của các biến truyền vào các đối số bên trong hàm. Bất kỳ thay đổi nào bạn thực hiện đối với các đối số bên trong hàm đều không ảnh hưởng đến các biến truyền bên ngoài hàm.
Nhưng khi bạn truyền một đối tượng và thay đổi các thành phần của nó, những thay đổi đó vẫn tồn tại bên ngoài hàm. Điều này làm cho nó trông giống như được chuyển qua tham chiếu. Nhưng nếu bạn thực sự thay đổi giá trị của biến đối tượng, bạn sẽ thấy rằng sự thay đổi không tồn tại, chứng tỏ nó thực sự được truyền theo giá trị.
Palăng là gì
Hoisting là một cơ chế JavaScript trong đó các khai báo biến và hàm được di chuyển lên đầu phạm vi của chúng trước khi thực thi mã. Điều này có nghĩa là bất kể chức năng và biến được khai báo ở đâu, chúng sẽ được chuyển lên đầu phạm vi của chúng bất kể phạm vi của chúng là toàn cục hay cục bộ.
Nhưng cơ cấu cẩu chỉ di chuyển phần khai báo. Các nhiệm vụ được để lại tại chỗ. Để đọc thêm về nó, hãy nhấp vào đây
Phạm vi là gì
Phạm vi là khả năng truy cập của các biến, hàm và đối tượng trong một số phần cụ thể của mã của bạn trong thời gian chạy. Nói cách khác, phạm vi xác định mức độ hiển thị của các biến và các tài nguyên khác trong các vùng mã của bạn.
Có hai loại phạm vi.
Phạm vi toàn cầu. Một biến nằm trong phạm vi Toàn cầu nếu nó được xác định bên ngoài một hàm.
Phạm vi cục bộ. Các biến được định nghĩa bên trong một hàm nằm trong phạm vi cục bộ.
Để đọc thêm về phạm vi, hãy tham khảo liên kết này
đóng cửa là gì
Một bao đóng là sự kết hợp của một hàm và môi trường từ vựng trong đó hàm đó được khai báo. Nói cách khác, Đóng là một hàm bên trong có quyền truy cập vào các biến của hàm bên ngoài [kèm theo].
Các bao đóng được sử dụng để mở rộng hành vi của các hàm bên ngoài và rất hữu ích khi làm việc với các sự kiện.
Để tìm hiểu sâu về đóng cửa, hãy tham khảo phần này
gọi lại là gì
Gọi lại là một chức năng thực thi sau khi một chức năng khác đã thực thi. Các cuộc gọi lại đảm bảo rằng một chức năng sẽ không chạy trước khi một tác vụ hoàn thành mà sẽ chạy ngay sau khi tác vụ hoàn thành. Tác vụ này có thể là bất kỳ lệnh gọi API nào hoặc bất kỳ tác vụ nào dựa trên bộ hẹn giờ
chắc chắn roll_die[]. trả lại ngẫu nhiên. randint[1, 6] def roll_two_and_sum[]. tổng = 0 tổng += roll_die[] tổng += roll_die[] in tổng roll_two_and_sum[]
Đầu tiên, chương trình của chúng ta gọi rollTwoAndSum . Nó đi vào ngăn xếp cuộc gọi.
rollTwoAndSum
Hàm đó gọi rollDie , hàm này được đẩy lên đầu ngăn xếp cuộc gọi.
rollDie
rollTwoAndSum
Bên trong rollDie , chúng tôi gọi ngẫu nhiên. randint . Đây là ngăn xếp cuộc gọi của chúng tôi trông như thế nào sau đó.
random.randint
rollDie
____0Khi nào ngẫu nhiên. randint kết thúc, chúng tôi quay lại rollDie bằng cách xóa ["popping"] random.randint .
rollDie
rollTwoAndSum
Điều tương tự khi rollDie trả về.
rollTwoAndSum
Chúng ta chưa hoàn thành. rollTwoAndSum gọi lại rollDie .
rollDie
rollTwoAndSum
Cuộc gọi nào ngẫu nhiên. randint lại.
random.randint
rollDie
____0ngẫu nhiên. randint trả về, sau đó rollDie trả lại, đưa chúng ta trở lại rollTwoAndSumrollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . rollTwoAndSumrollTwoAndSum . :
rollTwoAndSum
Cuộc gọi nào in .
rollDie
5rollTwoAndSum
Những gì được lưu trữ trong một khung ngăn xếp?
Điều gì thực sự diễn ra trong khung ngăn xếp của phương thức ?
Một khung ngăn xếp thường lưu trữ
- Biến cục bộ
- Các đối số được truyền vào phương thức
- Thông tin về khung ngăn xếp của người gọi
- Địa chỉ trả về—chương trình sẽ làm gì sau khi hàm trả về [i. e. nơi nó sẽ "trở lại"]. Đây thường là một nơi nào đó ở giữa mã của người gọi
Một số chi tiết cụ thể khác nhau giữa các kiến trúc bộ xử lý. Chẳng hạn, bộ xử lý AMD64 [64-bit x86] chuyển một số đối số trong thanh ghi và một số trên ngăn xếp cuộc gọi. Và, bộ xử lý ARM [phổ biến trong điện thoại] lưu trữ địa chỉ trả về trong một thanh ghi đặc biệt thay vì đặt nó vào ngăn xếp cuộc gọi
Chi phí không gian của khung ngăn xếp
Mỗi lệnh gọi phương thức tạo khung ngăn xếp riêng, chiếm dung lượng trên ngăn xếp lệnh gọi. Điều đó quan trọng vì nó có thể ảnh hưởng đến độ phức tạp không gian của thuật toán. Đặc biệt là khi chúng ta sử dụng đệ quy.
Ví dụ: nếu chúng tôi muốn nhân tất cả các số giữa 1 và n, we could use this recursive approach:
public static int product1ToN[int n] { // giả sử n >= 1 return [n > 1] ? . 1;
Ngăn xếp lệnh gọi sẽ như thế nào khi n = 10 ?
Đầu tiên, product1ToN được gọi với n = 10 .
rollDie
7Đây gọi product1ToN với n = 9 .
rollDie
8rollDie
7Gọi product1ToN với n = 8 .
rollTwoAndSum
0____18____17Và cứ thế cho đến khi chúng ta đạt được n = 1 .
rollTwoAndSum
3rollTwoAndSum
4rollTwoAndSum
5rollTwoAndSum
6rollTwoAndSum
7rollTwoAndSum
8rollTwoAndSum
9rollTwoAndSum
0rollDie
8rollDie
7Hãy xem kích thước của tất cả các khung ngăn xếp đó. Toàn bộ ngăn xếp cuộc gọi chiếm không gian. Đúng vậy—chúng tôi có chi phí dung lượng mặc dù bản thân phương pháp của chúng tôi không tạo ra bất kỳ cấu trúc dữ liệu nào.
Điều gì sẽ xảy ra nếu chúng ta sử dụng phương pháp lặp thay vì phương pháp đệ quy?
public static int product1ToN[int n] { // giả sử n >= 1 int result = 1;
Phiên bản này chiếm dung lượng không đổi. Khi bắt đầu vòng lặp, ngăn xếp cuộc gọi trông như thế này
random.randint
3Khi chúng tôi lặp qua vòng lặp, các biến cục bộ thay đổi, nhưng chúng tôi vẫn ở trong cùng một khung ngăn xếp vì chúng tôi không gọi bất kỳ hàm nào khác
random.randint
4random.randint
5random.randint
6Nói chung, mặc dù trình biên dịch hoặc trình thông dịch sẽ đảm nhận việc quản lý ngăn xếp cuộc gọi cho bạn, nhưng điều quan trọng là phải xem xét độ sâu của ngăn xếp cuộc gọi khi phân tích độ phức tạp về không gian của thuật toán
Đặc biệt cẩn thận với các hàm đệ quy. Cuối cùng, họ có thể xây dựng ngăn xếp cuộc gọi khổng lồ
Điều gì xảy ra nếu chúng tôi hết dung lượng? . Trong Java , bạn sẽ gặp Lỗi StackOverflow.
Nếu điều cuối cùng mà một phương thức thực hiện là gọi một phương thức, then its stack frame might not be needed any more. The phương thức có thể giải phóng khung ngăn xếp của nó trước khi thực hiện lệnh gọi cuối cùng, giúp tiết kiệm dung lượng.
Điều này được gọi là tối ưu hóa cuộc gọi đuôi [TCO]. Nếu một hàm đệ quy được tối ưu hóa với TCO, thì nó có thể không kết thúc bằng một ngăn xếp cuộc gọi lớn
Nói chung, hầu hết các ngôn ngữ không cung cấp TCO. Scheme là một trong số ít ngôn ngữ đảm bảo tối ưu hóa cuộc gọi đuôi. Một số triển khai Ruby, C và Javascript có thể làm điều đó. Python và Java quyết định không
Chia sẻ Tweet Chia sẻ
Phỏng vấn sắp tới?
Nhận khóa học xử lý email miễn phí trong 7 ngày. Bạn sẽ học cách suy nghĩ theo thuật toán, vì vậy bạn có thể phá vỡ các câu hỏi phỏng vấn viết mã phức tạp
Không cần đào tạo về khoa học máy tính trước—chúng tôi sẽ giúp bạn bắt kịp tốc độ một cách nhanh chóng, bỏ qua tất cả những nội dung quá hàn lâm